Links: Architecture: docs/Architecture/Overview.md Modules: ClaudeThread.cs, ClaudeExec.cs, ThreadEventParser.cs ADRs: 001-claude-cli-wrapper.md, 002-protocol-parsing-and-thread-serialization.md
Provide deterministic thread-based execution over Claude Code CLI so C# consumers can run turns, stream events, and resume existing conversations safely.
Runtime source of truth for this feature is the observed non-interactive Claude Code protocol:
claude -p --output-format jsonclaude -p --output-format stream-json --verbose
The upstream reference repository anthropics/claude-code is tracked in this repo as a submodule for change monitoring, but the SDK contract follows actual CLI behavior first.
- Turn execution (
RunAsync,RunStreamedAsync) for plain-text prompts andIReadOnlyList<UserInput>requests that currently normalize text-only input. - Conversion of Claude JSONL stream into typed
ThreadEvent/ThreadItemmodels. - ClaudeThread identity tracking across
thread.startedandresumeflows. - Failure/cancellation handling and typed structured output.
- Network transport reimplementation of Claude protocol (SDK uses CLI process).
- Image/file upload transport in print mode.
- Multi-thread merge semantics between separate
ClaudeThreadinstances.
- Only one active turn per
ClaudeThreadinstance. RunAsyncreturns only completed items and latest assistant text asFinalResponse.RunAsync<TResponse>returnsRunResult<TResponse>with deserializedTypedResponse; typed runs require an output schema via either directoutputSchemaoverload parameter orTurnOptions.OutputSchema.- Typed run API supports both concise overloads (
RunAsync<TResponse>(..., outputSchema, ...)) and full options overloads (RunAsync<TResponse>(..., turnOptions)); for AOT-safe typed deserialization passJsonTypeInfo<TResponse>. - Convenience typed overloads without
JsonTypeInfo<TResponse>are explicitly marked as AOT-unsafe withRequiresDynamicCodeandRequiresUnreferencedCode. resultevents withis_error: truemust surface asTurnFailedEvent, andRunAsync(...)must raiseThreadRunException.- Invalid JSONL event lines must fail fast with parse context.
- Protocol tokens are parsed via constants, not inline literals.
- Optional
ILogger(Microsoft.Extensions.Logging) receives process lifecycle diagnostics (start/success/failure/cancellation). - Structured output uses typed
StructuredOutputSchemamodels (including DTO property selectors) that are serialized inline to--json-schema. LocalImageInputexists in the SDK model layer but is currently rejected in Claude print mode withNotSupportedException.TurnOptions.ReplayUserMessagesis currently rejected because the SDK only supports text input, not Claude stream-json input mode.- Claude executable resolution is deterministic: prefer npm-vendored CLI entry or
node_modules/.bin/claude, then PATH lookup; on Windows PATH lookup checksclaude.exe,claude.cmd,claude.bat, thenclaude. - Thread options map current Claude Code print-mode flags (
--model,--permission-mode, tool allow/deny lists, system prompts, MCP config, resume/session flags, budget, settings, plugins, betas), plus rawAdditionalCliArgumentspassthrough for future non-transport flags. SDK-managed transport flags are reserved and rejected if passed manually. - Execution failures are surfaced to the caller with the raw Claude event context preserved in exception chains where available.
- Start and run turn
- Actor: SDK consumer
- Trigger:
StartThread().RunAsync(...) - Steps: build
claude -pargs -> execute Claude Code CLI -> parse stream-json output -> collect result - Result:
RunResultwith items, usage, final assistant response
- Start and run typed structured turn
- Actor: SDK consumer
- Trigger:
StartThread().RunAsync<TResponse>(..., outputSchema, ...)(orTurnOptionsvariant) - Steps: run regular turn -> deserialize final JSON response to
TResponseusing providedJsonTypeInfo<TResponse>when needed - Result:
RunResult<TResponse>with typed payload inTypedResponse
- Resume existing thread
- Actor: SDK consumer
- Trigger:
ResumeThread(id).RunAsync(...) - Steps: include
--resume <id>args -> parse events - Result: turn executes in existing Claude conversation
- Malformed JSON line ->
InvalidOperationExceptionwith raw line context resultevent withis_error: true->ThreadRunException- cancellation token triggered -> execution interrupted and surfaced to caller
flowchart LR
Caller["Caller"] --> ClaudeThread["ClaudeThread.RunAsync / RunStreamedAsync"]
ClaudeThread --> ExecArgs["ClaudeExecArgs"]
ExecArgs --> Cli["claude -p --output-format stream-json"]
Cli --> Stream["JSONL events"]
Stream --> Parser["ThreadEventParser"]
Parser --> Result["RunResult / streamed events"]
- build:
dotnet build ManagedCode.ClaudeCodeSharpSDK.slnx -c Release -warnaserror - test:
dotnet test --solution ManagedCode.ClaudeCodeSharpSDK.slnx -c Release - format:
dotnet format ManagedCode.ClaudeCodeSharpSDK.slnx - coverage:
dotnet test --solution ManagedCode.ClaudeCodeSharpSDK.slnx -c Release -- --coverage --coverage-output-format cobertura --coverage-output coverage.cobertura.xml
- ClaudeThread behavior: ClaudeThreadTests.cs
- Protocol parsing: ThreadEventParserTests.cs
- CLI argument mapping: ClaudeExecTests.cs
- Client lifecycle: ClaudeClientTests.cs
- Public thread APIs stay aligned with current Claude Code CLI contracts and documented in repository feature/architecture docs.
- All listed tests pass.
- Typed structured output API keeps explicit AOT-safe (
JsonTypeInfo<TResponse>) and convenience overload contracts documented and covered by tests. - Docs remain aligned with code and CI workflows.