Phase 1 adapter lane — AnthropicAdapter (1.C) + LlmError (1.I) + conformance harness (1.F) + capabilities (1.D)#8
Conversation
The Wave-1 seam trio — 1.A (LLMProvider seam types), 1.B (CostTracker + pricing), 1.E (ToolNormalizer) — merged in PR #7. Flip their headings to ✅ Done, update the phase Status line, and point current.md's next-steps at the adapter lane (1.C → {1.D ‖ 1.F}, with 1.I) running parallel to the 1.L parser. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ndency 1.I — the classification the FallbackChain (1.K) narrows on, normalized inside every adapter before crossing the seam (error-handling.md): - RETRYABLE_KINDS (rate_limit / overloaded / timeout / transport) + isRetryable. - kindFromHttpStatus — the shared status→kind baseline adapters reuse and refine. - makeLlmError — builds a normalized LlmError with `retryable` derived from `kind` (so the two can never disagree); constructs directly, never throws on the error path. Pre-work for the adapter lane: add `@anthropic-ai/sdk` (catalog ^0.101.0) — the official SDK already chosen by ADR-0011 + tech-stack.md, imported only inside packages/llm/src/adapters/* (the seam fence); no new ADR. OpenAI/Gemini SDKs land with 1.G/1.H. +4 tests (52 total in @relavium/llm). Full gate green; fence airtight. Refs: ADR-0011 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reference adapter over @anthropic-ai/sdk — the seam fence's first real consumer and the first place a vendor SDK is imported (only under src/adapters/*). It establishes the normalization patterns the conformance harness (1.F) will enforce on every adapter; no vendor type crosses back out (generate → LlmResult, stream → StreamChunk, failures → classified LlmError via 1.I). Normalization (Anthropic wire ↔ canonical): - generate + stream against the seam; key read at call time, attached inside the adapter. - system → top-level; ToolDef → input_schema (via 1.E toWire); tool_use/tool_result round-trip; stop reasons → the 5-value enum; Usage extracted with input net of cache (seam §6). - stream folds the typed event stream (message_start / content_block_start|delta|stop / message_delta / message_stop) into StreamChunks: tool-arg input_json_delta concatenated and the tool-call id tracked by content-block index; a final `stop` chunk carries stopReason + usage. - max_tokens defaulted; AbortSignal threaded; SDK errors classified (abort/timeout/transport, then status→kind). generate throws LlmProviderError (added to 1.I); stream yields an `error` chunk. - createAnthropicAdapter(deps) supports client injection so 1.F's replayer can drive it offline. Exposed via the new `@relavium/llm/adapters` entry point — kept separate from the seam barrel so the engine (which gets providers injected) consumes only the platform-free seam from `@relavium/llm`. Toolchain: the package now needs @types/node for the SDK's Node globals. tsconfig.json (types: ["node"]) covers the whole package for ESLint + the main typecheck; the new tsconfig.seam.json (types: []) is the platform-free GUARD that still fails on a stray Node/DOM type in seam code (adapters/conformance excluded). content.ts's AbortSignalLike note updated to match. +4 tests (56 total). Full gate green (both typecheck configs); seam fence airtight; build emits dist/adapters; no DB drift. Refs: ADR-0011 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tions (1.D) 1.F — the single conformance spec every adapter must pass, the biggest leverage point for the in-house abstraction (testing.md): - conformance/replay.ts: RecordedResponse + replayFetch (serve a recorded reply offline) + recordFetch (capture live responses to mint fixtures). Drives an adapter end-to-end — real SDK parsing + our normalization — with no network or key, via the adapter's fetch DI. - conformance/spec.ts: defineConformanceSuite asserts the canonical seam behaviour — text + tool_call (non-empty id) + usage + canonical stop reasons + a classified, retryable LlmError; streaming yields start/delta/end with one stable id then a terminal stop. Reusable across adapters. - conformance/fixtures/anthropic.ts: hand-authored Messages-API fixtures (JSON + SSE transcripts), the documented wire format — regenerate, don't hand-edit, once a live key records fresh ones. - conformance/anthropic.conformance.test.ts: PR mode (replay, offline) green, plus a live-nightly test wired but skipped until ANTHROPIC_API_KEY is provisioned. The AnthropicAdapter passes the full spec — the seam is proven against a real provider. Excluded from the build (it imports vitest) and from the seam fence's vendor zone (it imports no SDK — the adapter's fetch DI keeps the SDK inside src/adapters/*). 1.D — keep the common path narrow + honest: - capabilities.ts: requiredCapabilities / supportsRequest / assertSupported / assertStreamable. A request needing a feature the provider lacks fails fast with a typed UnsupportedCapabilityError (added to errors.ts) instead of being silently dropped; supportsRequest lets the FallbackChain skip an unfit provider. The adapter calls the guards at generate/stream entry. - providerOptions escape hatch: the adapter merges req.providerOptions onto the Anthropic body (provider-specific params like `thinking` reach the wire); `raw` passthrough already returns the provider response. Both stay out of the engine. +11 tests (67 total, 1 live-skipped). Full gate green (seam-guard + node typecheck); fence airtight; build emits dist/adapters; no DB drift. Refs: ADR-0011 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…conformance bite, coverage)
A 7-dimension adversarial review of the 1.C/1.I/1.F/1.D batch surfaced 17 confirmed findings
(1 high, 8 medium, rest low/info; 5 false-positives filtered). Fixed:
- [HIGH] the seam platform-purity guard was defeated: tsconfig.seam.json included `*.test.ts`,
whose `vitest` import transitively pulls in @types/node ambient globals — so `types: []` had no
teeth and a stray `process`/`Buffer`/`AbortSignal` in seam code would have passed. Exclude the
tests from the guard. Verified: planting `process.env` in a seam file now fails tsc -p
tsconfig.seam.json (TS2591), and is clean once removed.
- [MED secret/seam] anthropicErrorToLlmError no longer attaches the raw SDK error as LlmError.cause
— a raw vendor object on a seam-crossing field was a vendor-shape leak + latent secret-exposure
surface (error-handling.md). Added a test asserting the API key never appears in the surfaced error.
- [MED] providerOptions is now spread BEFORE the mapped body (`{...providerOptions, ...body}`), so a
caller can only ADD provider-specific params, never override model/messages/max_tokens/tools.
- [LOW] LlmError.code now comes from the SDK error `type` (e.g. 'rate_limit_error'), not `err.name`
(which the SDK never sets, so code was always "Error"); status-less errors (a mid-stream `error`
event) classify via the error type (new kindFromErrorType), aligned to the SDK's ErrorType union.
- [MED conformance] the spec now asserts CONCRETE values (exact stop reason, token counts, tool
name) so a normalization regression fails the suite, and adds a streamed mid-stream `error`
scenario exercising the stream error path.
Coverage: anthropic.ts to 96% stmt / 90% branch (was 79% branch); all other engine files 100%.
Added unit tests for error classification (each SDK error class + the error-type table), secret
safety, request building (tool_call/tool_result/tool_choice/system/temperature/stopSequences),
stream edge cases (init error, unknown event, untracked tool index), AbortSignal threading, and
mapContent's off-common-path skip. Tracked the cwd-sensitive coverage-glob gotcha in deferred-tasks.
Full gate green (both typecheck configs); seam fence airtight; no DB drift.
Refs: ADR-0011
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewer's GuideImplements the first concrete LLM provider adapter (Anthropic) against the frozen seam, introduces centralized LlmError classification and capability gating, and adds a generic conformance harness with recorded fixtures to prove adapters normalize provider behavior correctly while enforcing a platform-free seam boundary. Sequence diagram for AnthropicAdapter.generate with capability gating and LlmError classificationsequenceDiagram
participant Engine as FallbackChain
participant Adapter as anthropicAdapter
participant SDK as Anthropic
Engine->>Adapter: generate(req, apiKey)
Adapter->>Adapter: assertSupported(PROVIDER, SUPPORTS, req)
Adapter->>SDK: messages.create(buildCommonBody(req), buildRequestOptions(req))
alt SDK throws
SDK-->>Adapter: error
Adapter->>Adapter: anthropicErrorToLlmError(err)
Adapter->>Adapter: makeLlmError(...)
Adapter-->>Engine: throw new LlmProviderError(llmError)
else success
SDK-->>Adapter: message
Adapter->>Adapter: mapContent(message.content)
Adapter->>Adapter: mapStopReason(message.stop_reason)
Adapter->>Adapter: mapUsage(message.usage)
Adapter-->>Engine: LlmResult
end
Sequence diagram for AnthropicAdapter.stream with conformance replay and error chunkingsequenceDiagram
participant Conform as defineConformanceSuite
participant Factory as createAnthropicAdapter
participant Replay as replayFetch
participant Adapter as anthropicAdapter.stream
participant SDK as Anthropic
Conform->>Replay: replayFetch(recorded)
Replay-->>Conform: fetch
Conform->>Factory: createAnthropicAdapter({ fetch, maxRetries: 0 })
Factory-->>Conform: adapter
Conform->>Adapter: stream(req, apiKey)
Adapter->>Adapter: assertSupported(PROVIDER, SUPPORTS, req)
Adapter->>Adapter: assertStreamable(PROVIDER, SUPPORTS)
Adapter->>SDK: messages.create({...stream:true}, buildRequestOptions(req))
alt initial create throws
SDK-->>Adapter: error
Adapter->>Adapter: anthropicErrorToLlmError(err)
Adapter-->>Conform: yield { type: 'error', error }
else stream events
SDK-->>Adapter: RawMessageStreamEvent*
Adapter-->>Conform: text_delta / tool_call_* / stop chunks
opt mid-stream error
SDK-->>Adapter: error event
Adapter->>Adapter: anthropicErrorToLlmError(err)
Adapter-->>Conform: yield { type: 'error', error }
end
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughAdds an Anthropic LLM adapter (generate + stream), normalized LLM error model and wrapper, capability-gating with UnsupportedCapabilityError, deterministic conformance replay/spec/fixtures and tests, package/tsconfig/export wiring, and roadmap/docs updates. ChangesAnthropic Adapter & Testing Infrastructure
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="packages/llm/src/conformance/spec.ts" line_range="114-121" />
<code_context>
+ expect(result.stopReason).toBe(expected.toolGenerate.stopReason);
+ });
+
+ it('stream: yields text_delta(s) then a terminal stop with the exact reason + usage', async () => {
+ const chunks = await collect(
+ makeReplayAdapter(fixtures.textStream).stream(TEXT_REQUEST, KEY),
</code_context>
<issue_to_address>
**suggestion (testing):** Consider asserting streamed `inputTokens` in addition to `outputTokens` to fully cover usage normalization
Since streaming adapters (e.g., Anthropic) derive `usage` by combining `message_start` (input) and `message_delta` (output) events, it would be useful for `ConformanceExpectations.textStream` to also track `inputTokens` and assert them on the final `stop` chunk. This would help detect regressions where providers drop or mis-attribute input-token counts in streaming, which affects billing accuracy.
```suggestion
expect(chunks.some((chunk) => chunk.type === 'text_delta')).toBe(true);
expect(chunks.every((chunk) => chunk.type !== 'error')).toBe(true);
const last = chunks.at(-1);
expect(last?.type).toBe('stop');
if (last?.type === 'stop') {
expect(last.stopReason).toBe(expected.textStream.stopReason);
expect(last.usage.inputTokens).toBe(expected.textStream.inputTokens);
expect(last.usage.outputTokens).toBe(expected.textStream.outputTokens);
}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
This pull request implements the AnthropicAdapter (1.C), establishing the first concrete provider adapter behind the LLM provider seam, along with its conformance test suite, capability gating, and error classification. Feedback on the implementation highlights that Anthropic's Messages API does not natively support a 'none' tool choice type, which would trigger a 400 Bad Request error. To resolve this, it is recommended to omit both the 'tools' and 'tool_choice' fields from the request body entirely when 'toolChoice' is set to 'none'.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| function toAnthropicToolChoice(choice: ToolChoice): Anthropic.ToolChoice { | ||
| if (choice === 'auto') { | ||
| return { type: 'auto' }; | ||
| } | ||
| if (choice === 'none') { | ||
| return { type: 'none' }; | ||
| } | ||
| if (choice === 'required') { | ||
| return { type: 'any' }; | ||
| } | ||
| return { type: 'tool', name: choice.name }; | ||
| } |
There was a problem hiding this comment.
Anthropic's Messages API does not support a none tool choice type (only auto, any, and tool are supported). Passing { type: 'none' } will result in a 400 Bad Request error from the Anthropic API. \n\nTo disable tool execution natively in Anthropic, we should omit both tools and tool_choice from the request body entirely. We can exclude 'none' from the toAnthropicToolChoice signature and handle the omission at the request-building level in buildCommonBody.
function toAnthropicToolChoice(choice: Exclude<ToolChoice, 'none'>): Anthropic.ToolChoice {\n if (choice === 'auto') {\n return { type: 'auto' };\n }\n if (choice === 'required') {\n return { type: 'any' };\n }\n return { type: 'tool', name: choice.name };\n}| if (req.tools !== undefined) { | ||
| body.tools = req.tools.map(toAnthropicTool); | ||
| } | ||
| if (req.toolChoice !== undefined) { | ||
| body.tool_choice = toAnthropicToolChoice(req.toolChoice); | ||
| } |
There was a problem hiding this comment.
Since Anthropic does not support a none tool choice type, we must omit both tools and tool_choice from the request body when toolChoice is set to 'none'. This achieves the exact semantic meaning of disabling tool execution in a way that Anthropic natively supports without triggering API errors.
if (req.tools !== undefined && req.toolChoice !== 'none') {\n body.tools = req.tools.map(toAnthropicTool);\n }\n if (req.toolChoice !== undefined && req.toolChoice !== 'none') {\n body.tool_choice = toAnthropicToolChoice(req.toolChoice);\n }There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
packages/llm/src/adapters/anthropic.test.ts (1)
76-76: ⚡ Quick winReplace unsafe test assertions with
satisfiesand runtime narrowing helpers.These
asassertions bypass strict typing in multiple places. Prefer small parse guards for JSON bodies andsatisfiesfor fixture literals.Example direction
- sentBody = JSON.parse(raw) as Record<string, unknown>; + const parsed: unknown = JSON.parse(raw); + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + sentBody = parsed as Record<string, unknown>; + } else { + throw new Error('expected JSON object body'); + } - ] as Anthropic.ContentBlock[]); + ] satisfies Anthropic.ContentBlock[]);As per coding guidelines
**/*.{ts,tsx}must use TypeScript strict mode with no unsafe type assertions usingas.Also applies to: 173-176, 351-354, 387-387
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/llm/src/adapters/anthropic.test.ts` at line 76, Replace the unsafe "as" assertions when parsing JSON in the tests: instead of "sentBody = JSON.parse(raw) as Record<string, unknown>", create and call a small runtime parse guard (e.g., assertIsRecord/parseJsonRecord or parseJsonBody<T>) that validates the parsed value is an object/map and narrows its type, and replace fixture literals with the TypeScript "satisfies" operator so literals type-check without unsafe casts; update the other occurrences mentioned (around lines 173–176, 351–354, 387) to use the same parse guard for runtime JSON bodies and "satisfies" for static fixtures, referencing the test variable sentBody and any fixtures used in the same file (anthropic.test.ts) so the assertions are both type-safe and runtime-checked.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/llm/src/adapters/anthropic.ts`:
- Around line 294-356: streamChunks is too complex; extract the big switch
handlers into small helper functions to reduce complexity: create dedicated
handlers like handleMessageStart(event, usageRef),
handleContentBlockEvent(event, toolIdByIndex, yieldFn) (split into
start/delta/stop or one that switches on content_block_*), and
handleMessageDelta(event, usageRef, stopReasonRef) and call them from the
for-await loop in streamChunks so the main loop only delegates; ensure each
helper receives and mutates the same state (toolIdByIndex Map, usage object,
stopReason) or returns updated state, preserve existing yields (error mapping
via anthropicErrorToLlmError, tool call start/delta/end, text_delta, stop with
usage/stopReason) and keep request creation/try-catch behavior unchanged;
reference functions/variables: streamChunks, anthropicErrorToLlmError, mapUsage,
mapStopReason, toolIdByIndex, usage, stopReason, sdkStream.
- Around line 134-165: The anthropicErrorToLlmError function exceeds complexity
limits; refactor by extracting the class-instance checks and the APIError
mapping into small helper functions: create a helper isAnthropicErrorType(err:
unknown) or individual guards (e.g., isApiUserAbortError,
isApiConnectionTimeoutError, isApiConnectionError) that perform the instanceof
checks against Anthropic.APIUserAbortError, Anthropic.APIConnectionTimeoutError,
and Anthropic.APIConnectionError, and another helper mapAnthropicApiError(err:
Anthropic.APIError) that computes status, code, derives kind via
kindFromErrorType and kindFromHttpStatus and returns the object fed to
makeLlmError; then simplify anthropicErrorToLlmError to call these
guards/helpers and return the appropriate makeLlmError result, keeping function
names anthropicErrorToLlmError, kindFromErrorType, kindFromHttpStatus,
makeLlmError, and Anthropic.APIError referenced unchanged.
- Around line 268-270: The function buildRequestOptions uses an unsafe cast
(req.signal as AbortSignal); change it to a proper type guard that verifies
req.signal is actually an AbortSignal before returning { signal: req.signal },
e.g. check typeof AbortSignal !== "undefined" and req.signal instanceof
AbortSignal (or use a custom isAbortSignal predicate) so the returned object is
correctly typed; update references in buildRequestOptions and ensure
LlmRequest's signal handling remains compatible without using `as`.
In `@packages/llm/src/conformance/spec.ts`:
- Around line 124-144: The test currently checks the tool_call_start/delta/end
chunk types and stable ids but never asserts the terminal stop reason; update
the test inside the same it('stream: a tool call yields...') block to assert the
end chunk's stopReason matches expected.toolStream.stopReason by adding an
assertion like expect(end.stopReason).toBe(expected.toolStream.stopReason)
(after the existing type checks and inside the if that guards start/delta/end)
so regressions in stop-reason normalization are caught.
---
Nitpick comments:
In `@packages/llm/src/adapters/anthropic.test.ts`:
- Line 76: Replace the unsafe "as" assertions when parsing JSON in the tests:
instead of "sentBody = JSON.parse(raw) as Record<string, unknown>", create and
call a small runtime parse guard (e.g., assertIsRecord/parseJsonRecord or
parseJsonBody<T>) that validates the parsed value is an object/map and narrows
its type, and replace fixture literals with the TypeScript "satisfies" operator
so literals type-check without unsafe casts; update the other occurrences
mentioned (around lines 173–176, 351–354, 387) to use the same parse guard for
runtime JSON bodies and "satisfies" for static fixtures, referencing the test
variable sentBody and any fixtures used in the same file (anthropic.test.ts) so
the assertions are both type-safe and runtime-checked.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 22de3c2c-8080-4db4-9113-114f9eb0ced8
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
docs/roadmap/current.mddocs/roadmap/deferred-tasks.mddocs/roadmap/phases/phase-1-engine-and-llm.mdpackages/llm/package.jsonpackages/llm/src/adapters/anthropic.test.tspackages/llm/src/adapters/anthropic.tspackages/llm/src/adapters/index.tspackages/llm/src/capabilities.test.tspackages/llm/src/capabilities.tspackages/llm/src/conformance/anthropic.conformance.test.tspackages/llm/src/conformance/fixtures/anthropic.tspackages/llm/src/conformance/replay.tspackages/llm/src/conformance/spec.tspackages/llm/src/errors.tspackages/llm/src/index.tspackages/llm/src/llm-error.test.tspackages/llm/src/llm-error.tspackages/llm/tsconfig.build.jsonpackages/llm/tsconfig.jsonpackages/llm/tsconfig.seam.jsonpackages/shared/src/content.tspnpm-workspace.yaml
| name: wire.name, | ||
| // The canonical JSON-Schema is a valid Anthropic input schema; bridge the narrower SDK type | ||
| // at this vendor boundary. | ||
| input_schema: wire.input_schema as Anthropic.Tool.InputSchema, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
repo_root="."
file="packages/llm/src/adapters/anthropic.ts"
# Basic sanity: file exists
test -f "$file"
# Print a small window around the cited lines (with line numbers)
nl -ba "$file" | sed -n '190,250p'
echo "----"
nl -ba "$file" | sed -n '250,320p'
# Show all occurrences of " as " in the file (with line numbers)
echo "---- occurrences of ' as ' ----"
rg -n --fixed-string " as " "$file" || true
# Show any TypeScript assertions patterns that are likely unsafe
echo "---- occurrences of type assertions 'as' other patterns ----"
rg -n --regexp "\\sas\\s+[A-Za-z0-9_\\.]+\\b" "$file" || true
# If file uses `any`, report it
echo "---- occurrences of any ----"
rg -n --fixed-string ": any" "$file" || true
rg -n --fixed-string " as any" "$file" || true
rg -n --fixed-string "<any>" "$file" || trueRepository: HodeTech/Relavium
Length of output: 105
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="packages/llm/src/adapters/anthropic.ts"
test -f "$file"
echo "---- window around line 225 ----"
awk 'NR>=200 && NR<=255 {printf "%d\t%s\n", NR, $0}' "$file"
echo "---- window around line 269 ----"
awk 'NR>=255 && NR<=305 {printf "%d\t%s\n", NR, $0}' "$file"
echo "---- occurrences of ` as ` ----"
rg -n --fixed-string " as " "$file" || true
echo "---- occurrences of type assertions 'as' patterns ----"
rg -n --regexp "\\sas\\s+[A-Za-z0-9_\\.]+\\b" "$file" || true
echo "---- occurrences of any ----"
rg -n --fixed-string ": any" "$file" || true
rg -n --fixed-string " as any" "$file" || true
rg -n --fixed-string "<any>" "$file" || trueRepository: HodeTech/Relavium
Length of output: 6828
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="packages/llm/src/adapters/anthropic.ts"
echo "---- window around line 134 ----"
awk 'NR>=110 && NR<=170 {printf "%d\t%s\n", NR, $0}' "$file"
echo "---- window around line 294 ----"
awk 'NR>=285 && NR<=330 {printf "%d\t%s\n", NR, $0}' "$file"
echo "---- all occurrences of ' as ' ----"
rg -n --fixed-strings " as " "$file" || true
echo "---- type assertion patterns 'as <Type>' ----"
rg -n --regexp "\\sas\\s+[A-Za-z0-9_\\.]+\\b" "$file" || true
echo "---- occurrences of 'as unknown' or 'as any' ----"
rg -n --regexp "\\sas\\s+(unknown|any)\\b" "$file" || trueRepository: HodeTech/Relavium
Length of output: 5750
Remove unsafe as casts in the Anthropic adapter boundary
- Line 225: replace
wire.input_schema as Anthropic.Tool.InputSchemawith runtime narrowing/type guard, then assign withoutas. - Line 269: replace
req.signal as AbortSignalwith a runtime check (e.g.,req.signal instanceof AbortSignal) or a structural type guard before returning{ signal }.
Proposed refactor (for `input_schema`)
+function isAnthropicInputSchema(value: unknown): value is Anthropic.Tool.InputSchema {
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
+}
+
function toAnthropicTool(toolDef: ToolDef): Anthropic.Tool {
const wire = toWire(toolDef, PROVIDER);
if (!('input_schema' in wire)) {
throw new Error('unreachable: the Anthropic wire shape always carries input_schema');
}
+ if (!isAnthropicInputSchema(wire.input_schema)) {
+ throw new Error(`tool '${wire.name}' input_schema is not a valid Anthropic input schema`);
+ }
const tool: Anthropic.Tool = {
name: wire.name,
- input_schema: wire.input_schema as Anthropic.Tool.InputSchema,
+ input_schema: wire.input_schema,
};Source: Coding guidelines
…+ harden adapter
Review pass (CodeRabbit / Sourcery / SonarCloud / a security bot) plus an internal gap review.
Verified each against the code; fixed the valid ones, kept changes minimal.
Adapter (anthropic.ts):
- Cut the two SonarCloud cognitive-complexity criticals: extract mapAnthropicApiError out of
anthropicErrorToLlmError, and hoist streamChunks to module scope, splitting the event switch into
contentBlockToChunk + mergeDeltaUsage helpers (also "move to outer scope").
- buildRequestOptions: replace the `req.signal as AbortSignal` cast with an isAbortSignal type guard.
- mapStopReason: a stop_reason the pinned SDK doesn't type now degrades to 'stop' instead of throwing
(the live API can outpace the SDK).
- Streaming usage: message_delta now merges the cumulative cache/input token fields the SDK delivers,
not just output_tokens (more accurate streamed usage).
- Flip the SonarCloud "negated condition" smells to positive form.
- mapAnthropicApiError typed by the structural subset it reads (no `any` leak from APIError generics).
Errors (llm-error.ts): kindFromHttpStatus maps 409 / 413 to bad_request (were 'unknown').
Conformance:
- replay.ts: recordFetch refuses to record a body that looks like a secret (looksLikeSecret), and
replayFetch fails loud on a malformed (non-JSON) request body instead of serving blind.
- spec.ts: assert the streamed inputTokens and the tool-stream terminal stop reason, so a usage /
stop-reason regression fails the suite.
Tests: hoist `collect`, replace the `JSON.parse(...) as Record` casts with a runtime parseJsonBody
guard, and cover the new paths — unknown stop_reason, message_delta cache merge, 409/413,
recordFetch secret-refusal, replayFetch malformed-request. 95 tests; anthropic.ts to 92% branch,
replay.ts 92%, others 100%.
Skipped (with reason): the bot's "Anthropic rejects tool_choice {type:'none'}" — @anthropic-ai/sdk
0.101 types ToolChoiceNone, so the API supports it and our mapping is correct.
Full gate green (both typecheck configs); seam fence airtight (verified the purity guard still
fails on a planted Node global); no DB drift.
Refs: ADR-0011
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/llm/src/conformance/replay.ts (1)
24-24: ⚡ Quick winTighten the bearer-token regex character class to clear the static-analysis warning.
Line 24 uses a case-insensitive regex (
/i), so[a-zA-Z...]is redundant and triggers the duplicate-character-class warning. Use a single case range.Suggested diff
- /\bBearer\s+[a-zA-Z0-9._-]{16,}/i, // bearer tokens + /\bBearer\s+[a-z0-9._-]{16,}/i, // bearer tokens🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/llm/src/conformance/replay.ts` at line 24, The bearer-token regex uses an explicit case class ([a-zA-Z0-9._-]) together with the /i flag which triggers a duplicate-character-class warning; update the regex literal in replay.ts (the pattern /\bBearer\s+[a-zA-Z0-9._-]{16,}/i) to use a single case range (e.g., [a-z0-9._-]) while keeping the /i flag so the character class is not redundant.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/llm/src/conformance/replay.ts`:
- Line 24: The bearer-token regex uses an explicit case class ([a-zA-Z0-9._-])
together with the /i flag which triggers a duplicate-character-class warning;
update the regex literal in replay.ts (the pattern
/\bBearer\s+[a-zA-Z0-9._-]{16,}/i) to use a single case range (e.g.,
[a-z0-9._-]) while keeping the /i flag so the character class is not redundant.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b4edcb0c-8a81-426a-a8b0-4f332646442f
📒 Files selected for processing (8)
packages/llm/src/adapters/anthropic.test.tspackages/llm/src/adapters/anthropic.tspackages/llm/src/conformance/fixtures/anthropic.tspackages/llm/src/conformance/replay.test.tspackages/llm/src/conformance/replay.tspackages/llm/src/conformance/spec.tspackages/llm/src/llm-error.test.tspackages/llm/src/llm-error.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/llm/src/llm-error.ts
- packages/llm/src/conformance/fixtures/anthropic.ts
- packages/llm/src/conformance/spec.ts
- packages/llm/src/adapters/anthropic.test.ts
- packages/llm/src/llm-error.test.ts
- packages/llm/src/adapters/anthropic.ts
- Format tsconfig.seam.json per the root `prettier --check .` (the CI format:check covers JSON; my earlier local check only globbed src/**/*.ts and missed it). - replay.ts: the case-insensitive Bearer pattern had `a-z` AND `A-Z` in one character class — redundant under the `/i` flag (SonarCloud). Drop `A-Z`; `a-z` matches both cases. - gitignore the vitest v8 `coverage-tmp/` working dir so the local coverage run can't leak an unformatted artifact into `prettier --check .` or git. Full gate green; root format:check clean; fence airtight. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
@coderabbitai review all |
|
🧠 Learnings used✅ Action performedFull review finished. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/llm/src/adapters/anthropic.test.ts (1)
58-58: ⚡ Quick winReplace remaining unsafe test casts with typed narrowing helpers.
Lines 58, 227, and 452 rely on unsafe
asassertions. Please switch these to runtime narrowing (isRecord/array guards) orsatisfies-based fixture typing so tests stay strict-mode safe without assertion bypass.As per coding guidelines,
**/*.{ts,tsx}must use strict mode with no unsafeasassertions and should prefer type guards over unsafe casts.Also applies to: 227-227, 452-452
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/llm/src/adapters/anthropic.test.ts` at line 58, Tests currently use unsafe `as` assertions (e.g., the call to mapStopReason with 'future_reason' as Anthropic.StopReason) which bypass strict-mode checks; replace these casts with runtime type narrowing or fixture typing. Update the failing assertions to either (1) use a type-guard helper like isRecord/isArray to validate the test input at runtime before calling mapStopReason, or (2) declare test fixtures with `satisfies Anthropic.StopReason` so the compiler validates types without `as`; apply the same change to the other occurrences referenced (the casts around the tests that exercise mapStopReason and similar adapters) so no `as` assertions remain in the test file. Ensure you reference mapStopReason (and any other adapter helper used in those tests) when adding the guard or fixture so the call sites are updated accordingly.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/llm/src/adapters/anthropic.test.ts`:
- Line 58: Tests currently use unsafe `as` assertions (e.g., the call to
mapStopReason with 'future_reason' as Anthropic.StopReason) which bypass
strict-mode checks; replace these casts with runtime type narrowing or fixture
typing. Update the failing assertions to either (1) use a type-guard helper like
isRecord/isArray to validate the test input at runtime before calling
mapStopReason, or (2) declare test fixtures with `satisfies
Anthropic.StopReason` so the compiler validates types without `as`; apply the
same change to the other occurrences referenced (the casts around the tests that
exercise mapStopReason and similar adapters) so no `as` assertions remain in the
test file. Ensure you reference mapStopReason (and any other adapter helper used
in those tests) when adding the guard or fixture so the call sites are updated
accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5f3c8e6e-2159-4774-a54b-c8abb890079b
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (24)
.gitignoredocs/roadmap/current.mddocs/roadmap/deferred-tasks.mddocs/roadmap/phases/phase-1-engine-and-llm.mdpackages/llm/package.jsonpackages/llm/src/adapters/anthropic.test.tspackages/llm/src/adapters/anthropic.tspackages/llm/src/adapters/index.tspackages/llm/src/capabilities.test.tspackages/llm/src/capabilities.tspackages/llm/src/conformance/anthropic.conformance.test.tspackages/llm/src/conformance/fixtures/anthropic.tspackages/llm/src/conformance/replay.test.tspackages/llm/src/conformance/replay.tspackages/llm/src/conformance/spec.tspackages/llm/src/errors.tspackages/llm/src/index.tspackages/llm/src/llm-error.test.tspackages/llm/src/llm-error.tspackages/llm/tsconfig.build.jsonpackages/llm/tsconfig.jsonpackages/llm/tsconfig.seam.jsonpackages/shared/src/content.tspnpm-workspace.yaml
…#8 merged The @relavium/llm adapter lane landed in PR #8 (2026-06-06): AnthropicAdapter (1.C), LlmError classification (1.I), the shared conformance harness (1.F), and capabilities + providerOptions (1.D). The seam fence now has its first real consumer (@anthropic-ai/sdk under src/adapters/), proven end-to-end against recorded fixtures. Update the Phase-1 status banner + the four workstream headers, and re-point current.md's next steps to the remaining adapters (1.G ‖ 1.H → 1.J = M1) and 1.K, alongside the engine lane (1.L). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overage Verified findings from the Opus review round of step 1 (no blockers/correctness defects were found; these are the low/nit fixes): - Canonical home (CLAUDE.md #8): document `session:compacted` / `session:trimmed` where the wire contract lives — sse-event-schema.md's SessionEvent union + the "complete stream" prose (five→seven), and chat-session.md's `--json` NDJSON enumeration. Fix the stale "five session:* events" comments in session-handle.ts + agent-session.ts, and soften an over-stated run-event.ts comment (consumers use a `default` arm — there is no `assertNever` over the union; a new arm is a forward-compatible no-op until each opts in). - Coverage: estimateRequestTokens adversarial cases (empty→0, mixed/empty-text parts, and a pinned CJK under-count documenting the heuristic's known imprecision — real usage stays authoritative); a summary-less /trim marker db round-trip (empty content + boundary); session:compacted nonNegativeInt count rejects; and a type-level SessionEvent-discriminant pin mirroring RunEvent's. - ADR-0062 scope bullet corrected to name `session:trimmed` as a distinct cost-free arm. Toolchain + format:check green. Refs: ADR-0062 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ements Verified findings from the Sonnet review round of step 1 (HIGH: canonical-doc drift, flagged by both reviewers; the rest are integration-seam clarifications): - Update the four canonical reference homes CLAUDE.md #8 requires (the shapes this step changed): llm-provider-seam.md (the three new LlmProvider methods), database-schema.md (the compaction_dropped_through_sequence column), agent-session-spec.md (the SessionMessage.compaction field), and config-spec.md (auto_compact / compact_threshold + prose). - Reconcile the 2.5.F roadmap scope: /compact is no longer Phase-3-deferred — it is built now under ADR-0062 (engine primitive + automatic compaction). Removed the stale "recognized-but-deferred stub" plan and the Phase-3 deferral bullet; noted the 2.5.H context-overflow hint is now the secondary net (auto-compaction pre-empts). - Append "Refined during step-1 review" notes to ADR-0062: the role-filtered write-side boundary algorithm (avoids a silent-data-loss off-by-one when a prior marker row is interleaved), using result.model (not agent.model) for the threshold, the resolve.ts config plumbing obligation, disambiguation from the reserved agent:context_compacted steering event, and the marker-persistence sign-off. format:check green (docs-only). Refs: ADR-0062 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ doc accuracy + surrogate-safe cap Applied the security + Opus review findings (both returned 0 blocker / 0 high; all five guarantees traced + confirmed). Fixes: - MEDIUM (canonical-home drift, rule #8): agent-runner.md's failure-ladder table still called ToolPolicyError unconditionally "not fed back" — corrected to note the Step-14 `media_scope_denied` recoverable exception, linking the tool-registry.md taxonomy note (tool-registry.md was updated in D-3; the linked ladder home was missed). - LOW: two stale "`[c]` … deferred follow-up" comments (chat-store.ts PendingApproval, chat-mode-host.ts MakeChatModeEnv.prompt) — `[c]` landed in D-1; updated both to `[c] reason` + the reject-reason seam. - NIT: `sanitizeApprovalReason` capped by UTF-16 code unit — could split an astral pair into a lone surrogate at the 300-char boundary. Back off one unit when the cap lands on a high surrogate (+ a regression test). - Doc accuracy (security-review finding): the `recoverToolFailures` recovery surface is the chat-read-write host (chat / Home / one-shot `agent run`) — not "interactive chat only"; corrected in tool-registry.md, the ADR-0057 amendment, and security-review.md. Documented the accepted, secret-free scope-boundary probe residual (bounded by maxToolCorrections; the same round-bounded feedback pre-existed for an idempotent not-found read) + the createDirs idempotent-mkdir exception to "before any side effect". - deferred-tasks.md: marked the four now-landed 2.5.E entries DONE (bidi strip, `[c]`, scope-recovery, non-TTY policy); recorded the two optional Step-14 review follow-ups (approval-consent-line zero-width hardening; extract the `[c]` capture to a shared reducer for ChatApp test parity). Accepted as-is (documented): the `createChatModeControl` `interactive` default (true = the ink-REPL convention; the sole production caller always passes the computed flag; off-TTY the worst case is a hang, never an auto-approve). Tests: 67 projection cases pass; typecheck/eslint/prettier/fence green. Refs: docs/roadmap/phase-2.5-close-plan.md (Step 14, Batch D); ADR-0057 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gaps The Step-3 Sonnet round (after the Opus fold) found two MAJOR defects and three doc/coverage gaps. Verified each fix by breaking production and confirming the test fails, then reverting. - FLAKY SINGLE-FLUSH (MAJOR): the single `await flush()` (one setImmediate) is NOT reliable for frame assertions — React 19's commit can be deferred past one macrotask under parallel-file CPU contention (the agent reproduced ~11/15 then 4/8 failures at the Home "Working… 3s" assertion running the three .test.tsx files together; a second flush fixed 12/12). Replaced the fixed single yield with `waitFor(predicate, maxYields)` — POSITIVE assertions poll until the commit lands (fast when quick, robust when slow); NEGATIVE ones (security, perf) give the bad outcome a bounded window to manifest, then confirm it did not. Struck the false "a second yield is never needed" claim in harness-util.ts. (Ran the suite 10× post-fix: 0 failures.) - CHATAPP SECURITY LEAK (MAJOR): the ChatApp security pin only asserted the floor stays unanswered — it never checked the paste is DROPPED from the buffer, so a regression in ChatApp's OWN `approvalPending` gate (independent of the usePaste channel) went uncaught, unlike the hardened Home sibling. Added facet 2: a distinctive token pasted during the approval must not render. (Verified: regressing chat-ink.tsx:882 `approvalPending` → false renders `> yzLEAKz` and the new assertion fails.) - RESIZE SMOKE (MINOR): RootApp's subscribeResize → setSize re-measure wiring existed in production but was stubbed inert everywhere. Added a Home resize test that captures the registered callback, changes the size, fires it, and asserts a re-render. (Verified: no-op'ing the useEffect fails "expected 1 to be greater than 1".) - ADR-0068(f) FRAME-TIME (MINOR): amended part (f) in place (dated note) — the harness pins render-COUNT + both frozen-clock paths + the security/CRLF/ backspace discriminators; a wall-clock FRAME-TIME threshold is deferred to the render-heavy Step-4/5 frame loop (per-frame budgets are CI-flaky). - testing.md (MINOR): documented `.test.tsx` as the sanctioned convention for ink-mounted component tests (one-canonical-home, CLAUDE.md #8). - cleanup() quirk (NIT): noted in harness-util.ts that ink-testing-library's cleanup() never clears its instances array (harmless today; idempotent unmount) so a future double-unmount-throws release is diagnosable. 1658 tests pass; lint/typecheck/build/format green. Refs: ADR-0068 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rphaned JSDoc, doc trims Four of the six findings were false positives; verified against the code rather than assumed. The two valid nits are applied, plus one real defect they led me to. Verified FALSE (no change) - "chat.test.ts:657 defines `openSessionStore` twice". It does not: the literal is a SPREAD (`...a.d`) followed by one explicit `openSessionStore` — a legal, intentional override, not a duplicate key. Lines 657/674/688 are three DIFFERENT literals. A real duplicate key is a hard TS error; typecheck is green. - "chat-alt-hoist.test.ts:657 / :477 duplicate `openSessionStore` / `BudgetWarn`". That file is 203 lines and contains NEITHER symbol; both live in chat.test.ts, `BudgetWarn` exactly once. The line numbers were carried over from the wrong file. - "`buildInteractiveShellRunner` leaks an `any` from `built.session.runUserCommand`". `BuiltChatSession.session` is `AgentSession`, whose `runUserCommand(command: string, args: readonly string[])` returns `Promise<UserCommandOutcome>`. Proven, not assumed: narrowing the wrapper's return annotation to `Promise<number>` makes tsc reject it — an `any` would have been assignable. `recommendedTypeChecked` (no-unsafe-return / -member-access) is active and green. - Docstring coverage 72.58% (advisory): no repo standard sets this gate, and the new helpers all carry JSDoc. Skipped as a metric — though see below for the one real docstring bug, found by reading rather than by the percentage. Fixed - harness-util.ts: hoist `settleFrames(frames = 4)` beside flush/waitFor/bracketed. The 4x flush loop was copy-pasted TWELVE times (7 in chat-app.test.tsx, 4 + 1 inline in home-app.test.tsx) — in a file whose own header claims the timing contract has "ONE home ... a drift would otherwise have to be fixed in three places". Break-verified it is load-bearing: `frames = 0` fails the three scroll/wheel tests. - chat.ts: REUNITE `driveOneSession`'s JSDoc with `driveOneSession`. Adding `buildInteractiveShellRunner` two commits ago wedged the new helper between that doc and its subject, so the doc described the wrong function — the same defect the review flagged in chat-projection.ts, which I had introduced here at the same time. - config-spec.md: the `alt_screen` entry restated viewport/scroll/mouse-wheel/flicker mechanics that belong to ADR-0068. Replaced with a pointer (CLAUDE.md #8, one canonical home per artifact). - accessibility.md: document the mouse-selection tradeoff. alt-screen.ts:33 already told readers to "see accessibility.md" for it and config-spec.md now leans on that citation too, but the file never covered it — a dangling canonical-home reference. Adds the table row + the bypass-modifier note (Shift; Option on iTerm2). Refs: ADR-0068 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… canonical homes commands.md gains the two entries in the curated-registry list; chat-session.md and home.md replace the "still pending at Step 5" sentence with what they actually do; accessibility.md names them as the in-app answer to the copy regression mouse reporting introduces. One canonical home per artifact (CLAUDE.md #8): each file states its own concern and links out — the mechanism stays in ADR-0068 §e. Refs: ADR-0068 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>



The seam goes from frozen contract to proven against a real provider: the first adapter over
@anthropic-ai/sdk, the conformance harness that proves it (and every future adapter), theLlmErrorclassification the fallback chain narrows on, and capability gating + theproviderOptionsescape hatch. This opens 1.G/1.H → M1. Builds on PR #7 (the frozen seam + ToolNormalizer + CostTracker).
Commits
cccc46ddocs(roadmap): mark 1.A / 1.B / 1.E done (PR Phase 1 Wave 1 — LLMProvider seam types (1.A) + ToolNormalizer (1.E) + CostTracker (1.B) #7 merged).7fa3ba7feat(llm): 1.I — LlmError classification + LlmProviderError; add@anthropic-ai/sdk.b17616bfeat(llm): 1.C — AnthropicAdapter (generate + stream, full normalization).d8fb708feat(llm): 1.F conformance harness + 1.D capability gating & providerOptions.5d5bd6cfix(llm): address the adversarial-review findings (below).What's here
1.I —
RETRYABLE_KINDS/isRetryable/kindFromHttpStatus/makeLlmError(retryable derivedfrom kind) +
LlmProviderError(the throw channel forgenerate).1.C —
adapters/anthropic.ts:generate+streamagainst the seam; system→top-level, ToolDef→input_schema (1.E), tool_use/tool_result round-trip, the typed event stream folded into
StreamChunks(tool-arg deltas concatenated, id tracked by content-block index), stop reasons→the 5-value enum,
Usagenet of cache,max_tokensdefaulted,AbortSignalthreaded, SDK errors classified. Exposedvia a separate
@relavium/llm/adaptersentry point so the engine consumes only the platform-free seam.1.F — one shared conformance spec asserting concrete canonical values (exact stop reason,
token counts, tool name) driven by recorded fixtures: a replay
fetchinjected into the SDK runs theadapter offline; live-nightly is wired but skipped until
ANTHROPIC_API_KEYis provisioned.1.D — capability gating (
assertSupported/supportsRequest— fail fast, never silently drop afeature) + the
providerOptionsescape hatch (merged so mapped fields always win).Toolchain
@types/node(the SDK's Node globals).tsconfig.json(types: ["node"])covers the whole package for ESLint + the main typecheck;
tsconfig.seam.json(types: [],adapters/conformance/tests excluded) is the platform-free GUARD that fails on a stray Node/DOM
global in seam code.
Adversarial review — 17 confirmed findings fixed
@types/node— now excluded(verified: planting
processin a seam file failstsc -p tsconfig.seam.json).LlmError.cause(vendor-shape leak +secret-exposure surface);
providerOptionscan't override mapped fields; the conformance suite nowasserts concrete values + a streamed-error path so it actually catches a regression.
LlmError.codefrom the SDK errortype, noterr.name.Conformance
any/ unsafeasoutside justified vendor-boundary test fixtures@anthropic-ai/sdkimported only undersrc/adapters/*; fence airtight; conformance imports no SDK (adapter takes afetchoverride)@anthropic-ai/sdkadded under ADR-0011 + tech-stack (no new ADR)pnpm turbo run lint typecheck test buildgreen · 89 tests (1 live-skipped) · coverage: engine files 100%, anthropic.ts 96% line / 90% branch (≥90% bar) · seam fence airtight · no DB driftRefs: ADR-0011
🤖 Generated with Claude Code
Summary by Sourcery
Introduce the first Anthropic LLM adapter, shared error classification, and a conformance harness that validates adapters against recorded fixtures while enforcing capability gating and seam purity.
New Features:
Enhancements:
Build:
Tests:
Summary by CodeRabbit
New Features
Tests
Documentation
Chores