Assemblies and namespaces
CrystalCode
Section titled “CrystalCode”The terminal host and the only executable. Owns CLI commands, the terminal
host loop (alternate screen, key loop, Ctrl+C), the headless crystal run
entry, and the projection of engine events onto CrystalCode.Display. It also
owns the terminal surfaces for the engine’s prompts: approval cards and keys,
the question overlay, and the saved-session picker. crystal run supplies
its own observer, approval prompt, question prompt, and session chooser. It
does not own session logic, approval policy, prompts, storage, or tools, and
it does not own the frame painter, composer buffer, or transcript log.
crystal version and crystal --version print the build identity as plain
text and exit. The build section is Crystal Code (this repository’s
commit), Crystal (the Crystal library repository’s commit), SDK (the
.NET SDK that compiled the executable), and Configuration (the MSBuild
configuration stamped on the executable). A blank line separates it from
the host section: Runtime (the shared framework hosting the process) and
OS (RuntimeInformation.OSDescription plus the operating-system
architecture). A line is omitted when that value was not recorded. An empty
section is omitted, including the blank line. Commits are the 40-character
source revision the SDK appends to the assembly informational version. The
assembly version placeholder is left unread. SDK is the
NETCoreSdkVersion assembly metadata stamped by Directory.Build.targets
for projects in this repository. Configuration is
AssemblyConfigurationAttribute. The host passes
RuntimeInformation.FrameworkDescription, OSDescription, and
OSArchitecture. The description is kept whole and the architecture is
appended in lower case. The report does not start a process. The outbound
User-Agent stays Crystal Code.
CrystalCode.Tools
Section titled “CrystalCode.Tools”The contract external dotnet tools may reference when a call needs host
facts. It defines ToolHostContext, IHostTool,
IHostMultimodalTool, and the optional IHostToolApprovalClassifier.
ToolHostContext carries the workspace root, the session id, the approval mode,
and the extension’s global and project runtime-data directories. An approval
classifier receives the model call and a context carrying the workspace root,
session id, and approval mode before invocation and returns the call’s risk
and authority; the engine merges that proposal with its own classification and
it can only raise risk or authority, never lower them. ClassifyApprovalAsync
receives user/turn cancellation and an independent two-second timeout. Its
entry is dispatched on a worker so blocking before returning a task does not
block approval. After timeout or cancellation that classifier instance stays
disabled until the external catalog reloads; late work is observed and its
result is discarded. In-process code still cannot be forcibly terminated.
Null results, exceptions, timeouts, disabled instances, and either invalid
classification field fail closed to Privileged + PrivilegedEscalation, merged
with the host floor. User/turn cancellation propagates without approval.
Classification summaries are authored by the host; a tool’s Summary is not
sent to the operator or the reviewer. The engine awaits IApprovalClassifier
ClassifyAsync; its default implementation adapts existing synchronous plugin
classifiers. Synchronous external classification fails closed when a tool has
an asynchronous pre-classifier. It references only
Crystal and Crystal.Tools. It does not reference the engine, the display, the
executable, or a terminal library. Tools that implement only Crystal’s ITool
or IMultimodalTool do not reference it. There is no paired test project;
dispatch, approval classification, and load-context identity are tested in
CrystalCode.Engine.Tests.
CrystalCode.Plugins
Section titled “CrystalCode.Plugins”The contract a disk plugin references. It defines IPlugin, contribution
lists, the hook and raw hook interfaces, the hook context types, the model
and environment views, and IPluginDataDirectory for an extension’s runtime
data directories. It references only Crystal and
Crystal.Tools. It does not reference the engine, the display, the
executable, or a terminal library. There is no paired test project;
discovery, the load context, and hook order are tested in
CrystalCode.Engine.Tests.
CrystalCode.Engine
Section titled “CrystalCode.Engine”The front-end-neutral class library. Owns session and turn execution,
Plan/Work catalogs, approval policy, context compaction, ~/.crystal
storage, built-in coding tools, operator tool sets, operator plugins,
prompts, in-process plugin contracts, slash commands, and the event stream a front end observes.
It never reads a key, writes to the console, or paints. See
Engine contract.
CrystalCode.Engine.Tests
Section titled “CrystalCode.Engine.Tests”Engine tests: workspace fencing, approval classification, compaction selection, session serialization, command behavior, external tool sets, a headless session driven by a scripted model, and the dependency guard. Does not reference the host executable.
CrystalCode.Display
Section titled “CrystalCode.Display”The terminal UI library. Owns the alternate-screen frame, input routing,
composer, transcript viewport, markdown paint, and queue card. Spectre
widgets are rasterized into frame rows. AnsiConsole.Live is not the
session shell. Terminal.Gui is a parked package reference for
supply-chain review and is not called. The host loop stays in this
assembly’s shell types; a later migration to Terminal.Gui would replace
that loop inside this project only.
Committed transcript entries may carry a Spectre IRenderable alongside a
plain-text fallback. The display engine measures the widget at the current
terminal width when its cache is rebuilt, so tables, grids, panels, and other
high-level components participate in resize, scrollback, and repaint like
text. Non-interactive output renders the same widget directly. The shell
progress region owns transient indeterminate progress and elapsed time;
static command reports remain committed transcript widgets.
CrystalCode.Display.Tests
Section titled “CrystalCode.Display.Tests”Display tests: layout, chrome, input decoder, scroll policy, composer, paint, transcript log, queue card, and the dependency guard. Does not reference the host executable.
CrystalCode.Providers
Section titled “CrystalCode.Providers”Model adapters. Each adapter implements IChatClient and, when the provider
can stream, IStreamingChatClient. Image-capable adapters additionally
implement IStreamingMultimodalChatClient. Provider options, wire DTOs, and
transport exceptions stay in this assembly. No tools, no UI, no home-directory
layout.
CrystalCode.Tests
Section titled “CrystalCode.Tests”Terminal host tests: session renderer, event-to-slash-option mapping, status
and tool-list widgets, tool-call and progress text, transcript replay, the
session picker, approval cards and keys, the image-marker contract with
Display, and crystal run against a scripted model.
CrystalCode.Providers.Tests
Section titled “CrystalCode.Providers.Tests”Provider adapter tests. One folder per vendor, mirroring
CrystalCode.Providers. Does not reference the host executable.
Namespace ownership
Section titled “Namespace ownership”Root identifier is CrystalCode, matching the existing solution. Folder
path under a project root equals the namespace after the project name, so
engine namespaces read CrystalCode.Engine.Sessions and so on.
CrystalCode (executable):
| Folder | Owns |
|---|---|
Commands |
Spectre.Console.Cli commands. Bare crystal stays the terminal. space opens the operator space under the data directory. run is the headless task command. version prints the build identity |
Run |
Unattended front end for crystal run: plain-text or JSON-lines log, denied operator prompts, dismissed questions, process-only setting overrides, and exit codes |
Terminal |
Host loop, event projection, session renderer, status and tool-list widgets, progress and tool-call text, transcript replay, session picker, question overlay, slash-option mapping |
Terminal/Approvals |
Approval prompt, card, diff preview, and keys |
CrystalCode.Engine (namespaces below are relative to CrystalCode.Engine):
| Folder | Owns |
|---|---|
Configuration |
Loaded options, defaults, thinking and mode labels, display-case helpers |
Events |
Immutable session events, observer interface, chrome and preference snapshots |
Home |
~/.crystal paths and file I/O; DiskSettingsStore is the default config.json and providers.json implementation |
Home/Interfaces |
Configuration persistence contracts such as ISettingsStore |
Sessions |
Session (CodingSession), transcript, ledger, streaming turn, slash commands and completions, chat client lifetime, status, usage, and tool-list text, front-end contract (SessionFrontEnd, ISessionChooser) |
Approvals |
Risk, authority, grants, policy, review transcript |
Approvals/Interfaces |
Prompt and reviewer contracts |
Compaction |
Window accounting and summary substitution |
Tools |
Workspace fence and built-in ITool types |
Tools/External |
Operator tool sets (tools.json), exec and isolated ITool / IMultimodalTool loaders, and host-context dispatch |
Prompts |
Caller-owned system text. Built-in Work and Plan identify the assistant as Crystal Code |
Skills |
OpenCode-compatible SKILL.md discovery and catalog |
Plugins |
In-process registry and built-in contributions |
Plugins/Interfaces |
Contribution contracts |
Plugins/Providers |
Built-in DeepSeek, OpenAI-compatible, Responses, Anthropic, Gemini, and Ollama client factories |
Version |
Build identity for crystal version: product commit, Crystal commit, compiling SDK, compile configuration, host runtime, and host operating system. Two plain-text sections |
CrystalCode.Display:
| Folder | Owns |
|---|---|
Input |
Decode ReadKey bursts into keys, paste, and wheel events |
Shell |
Alternate screen, painter, layout, key burst, scroll policy, status and progress chrome |
Composer |
Prompt buffer, keys, slash picker |
Cards |
Queue overlay |
Transcript |
Viewport log, role cards, sequential fallback |
Paint |
Markup, markdown, theme, wrapping |
Quick prompts keep the same dependency direction: Engine Home owns
QuickPromptStore, bounded UTF-8 file reads, validation, and immutable catalog
values; Display Composer owns the isolated quick-prompt picker and preview.
The terminal maps catalog values, routes F6 only outside modal prompts, and
inserts Fill at the current composer cursor. Send uses
CodingSession.SubmitQuickPromptAsync, bypasses slash parsing, and never
consumes pending or composer draft images. Ordinary queued submissions bind
pending images at enqueue time so later quick sends cannot steal them. Quick
queue entries do not request cancellation after tools; explicit queue release
and ordinary follow-ups retain their existing interrupt behavior.
Provider types live under CrystalCode.Providers plus one folder per wire
family (DeepSeek, OpenAI, Responses, Anthropic, Gemini, and Ollama). Shared
OpenAI-compatible Chat Completions request and stream parsing lives in
Compatible. Shared direct-HTTP lifecycle and SSE framing for Responses and
Messages, Gemini GenerateContent, and Ollama Chat lives in Protocol; each
wire codec still owns its request and event semantics. Gemini uses SSE and
replays signed response parts so function calls retain thought signatures.
Opaque reasoning state is provider-owned: an adapter replays state only in
its own format. A block it cannot replay keeps its readable text instead
of failing (assistant text, or the protocol’s readable reasoning field
where it has one).
Ollama uses JSON lines and native tool_name results. Anthropic Messages
marks prompt-cache breakpoints (cache_control: ephemeral) on the last
system block and the last message content block of every request, so the
tool definitions, the system prompt, and the conversation prefix written by
the previous round are read from the provider’s prompt cache; the multimodal
codec keeps the message breakpoint when image expansion splits a marked text
block. Other adapters rely on their providers’ automatic prefix caching and
send no cache directives. Chat Completions, Responses, Anthropic Messages,
Gemini, and Ollama map an error payload, an event whose type is error,
and a JSON body returned where an event stream or JSON lines were expected to
provider faults carrying the provider code and message. Gemini uses
error.status when error.code is a number. A non-streaming JSON response
with an error payload maps the same way even when the HTTP status is 200.
Framed Responses and Anthropic error events keep the provider message as the
fault text. Session retry then reports the cause instead of treating the
truncated stream as retryable. Outbound chat requests send a constant User-Agent of Crystal Code
with no version. Provider SDKs are not used.