Skip to content

Phase 1 adapter lane — AnthropicAdapter (1.C) + LlmError (1.I) + conformance harness (1.F) + capabilities (1.D)#8

Merged
cemililik merged 7 commits into
mainfrom
development
Jun 6, 2026
Merged

Phase 1 adapter lane — AnthropicAdapter (1.C) + LlmError (1.I) + conformance harness (1.F) + capabilities (1.D)#8
cemililik merged 7 commits into
mainfrom
development

Conversation

@cemililik

@cemililik cemililik commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

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), the
LlmError classification the fallback chain narrows on, and capability gating + the providerOptions
escape hatch. This opens 1.G/1.H → M1. Builds on PR #7 (the frozen seam + ToolNormalizer + CostTracker).

Commits

  • cccc46d docs(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).
  • 7fa3ba7 feat(llm): 1.I — LlmError classification + LlmProviderError; add @anthropic-ai/sdk.
  • b17616b feat(llm): 1.C — AnthropicAdapter (generate + stream, full normalization).
  • d8fb708 feat(llm): 1.F conformance harness + 1.D capability gating & providerOptions.
  • 5d5bd6c fix(llm): address the adversarial-review findings (below).

What's here

1.IRETRYABLE_KINDS / isRetryable / kindFromHttpStatus / makeLlmError (retryable derived
from kind) + LlmProviderError (the throw channel for generate).

1.Cadapters/anthropic.ts: generate + stream against 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,
Usage net of cache, max_tokens defaulted, AbortSignal threaded, SDK errors classified. Exposed
via a separate @relavium/llm/adapters entry 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 fetch injected into the SDK runs the
adapter offline; live-nightly is wired but skipped until ANTHROPIC_API_KEY is provisioned.

1.D — capability gating (assertSupported / supportsRequest — fail fast, never silently drop a
feature) + the providerOptions escape hatch (merged so mapped fields always win).

Toolchain

  • The package now needs @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

  • [HIGH] the seam purity guard was defeated by test files pulling in @types/node — now excluded
    (verified: planting process in a seam file fails tsc -p tsconfig.seam.json).
  • [MED] the raw SDK error no longer crosses the seam as LlmError.cause (vendor-shape leak +
    secret-exposure surface); providerOptions can't override mapped fields; the conformance suite now
    asserts concrete values + a streamed-error path so it actually catches a regression.
  • [LOW] LlmError.code from the SDK error type, not err.name.

Conformance

  • TS strict; no any / unsafe as outside justified vendor-boundary test fixtures
  • No vendor SDK type crosses the seam@anthropic-ai/sdk imported only under src/adapters/*; fence airtight; conformance imports no SDK (adapter takes a fetch override)
  • API key never in an error message / cause / log — asserted by a test
  • @anthropic-ai/sdk added under ADR-0011 + tech-stack (no new ADR)
  • pnpm turbo run lint typecheck test build green · 89 tests (1 live-skipped) · coverage: engine files 100%, anthropic.ts 96% line / 90% branch (≥90% bar) · seam fence airtight · no DB drift

Refs: 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:

  • Add an Anthropic-backed LlmProvider adapter with generate and stream support behind a dedicated @relavium/llm/adapters entrypoint.
  • Introduce a shared LlmError classification contract and provider error wrapper for fallback-aware error handling across adapters.
  • Add a generic adapter conformance harness with recorded/replayed fixtures, including Anthropic-specific fixtures and tests.
  • Expose capability gating utilities and an UnsupportedCapabilityError to enforce provider feature support at request time.

Enhancements:

  • Tighten the seam purity guard with a dedicated tsconfig.seam.json run and clarify AbortSignalLike documentation.
  • Extend roadmap docs to mark earlier seam work as done and describe the new adapter and engine lanes.
  • Document and plan future test coverage enforcement and provider SDK catalog entries.

Build:

  • Adjust the llm package typecheck pipeline to run both the full project and a seam-only config, and add a new adapters subpath export for platform-coupled providers.
  • Add @anthropic-ai/sdk and @types/node to the workspace catalog and llm package dependencies to support the new adapter.

Tests:

  • Add unit tests for LlmError classification and capability gating, plus a replay-based Anthropic conformance suite with fixtures and live (key-gated) smoke test.

Summary by CodeRabbit

  • New Features

    • Anthropic LLM adapter with streaming, tool-call handling, normalized errors and usage reporting.
    • Capability-gating API to check/require provider features and streaming support.
    • Standardized LLM error model and provider error wrapper with clear retry semantics.
  • Tests

    • Conformance and replay suites with streaming/tool/error fixtures and secret-detection checks.
    • Extensive adapter unit tests covering mapping, streaming, usage, and error classification.
  • Documentation

    • Updated Phase 1 roadmap and deferred testing notes.
  • Chores

    • Exposed adapter entrypoints, typecheck profile additions, workspace SDK pin, ignored temp coverage.

cemililik and others added 5 commits June 6, 2026 14:12
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>
@sourcery-ai

sourcery-ai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements 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 classification

sequenceDiagram
  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
Loading

Sequence diagram for AnthropicAdapter.stream with conformance replay and error chunking

sequenceDiagram
  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
Loading

File-Level Changes

Change Details Files
Add Anthropic adapter as first concrete LlmProvider implementation behind a dedicated adapters entry point.
  • Implement createAnthropicAdapter/anthropicAdapter using @anthropic-ai/sdk for generate and stream paths, fully normalizing Anthropic messages, content blocks, usage, stop reasons, and errors into canonical seam types
  • Introduce Anthropic-specific normalization helpers for stop reasons, usage, content parts, tool definitions, tool choices, and request body construction including providerOptions merging and AbortSignal threading
  • Expose adapters via a new @relavium/llm/adapters barrel and update package exports, build/typecheck scripts, and workspace catalog to include @anthropic-ai/sdk and @types/node while keeping seam types platform-free
packages/llm/src/adapters/anthropic.ts
packages/llm/src/adapters/index.ts
packages/llm/package.json
pnpm-workspace.yaml
packages/llm/tsconfig.json
packages/llm/tsconfig.build.json
packages/llm/tsconfig.seam.json
Introduce centralized LlmError classification and provider error wrapper used by adapters and future fallback chain.
  • Define retryability policy via RETRYABLE_KINDS, isRetryable, and kindFromHttpStatus and ensure makeLlmError derives retryable from kind while keeping optional fields absent when undefined
  • Implement LlmProviderError to wrap normalized LlmError for generate rejections and wire Anthropic adapter error handling through anthropicErrorToLlmError using SDK error types and provider error codes
  • Add tests verifying classification behavior, HTTP mapping, schema validity, and optional field handling
packages/llm/src/llm-error.ts
packages/llm/src/llm-error.test.ts
packages/llm/src/index.ts
Add capability gating utilities and UnsupportedCapabilityError to fail fast when a provider lacks required features.
  • Introduce Capability type, requiredCapabilities, supportsRequest, assertSupported, and assertStreamable to derive needed capabilities from LlmRequest and enforce provider CapabilityFlags
  • Add UnsupportedCapabilityError with structured metadata and extend LlmConfigErrorCode to include unsupported_capability
  • Update public llm index exports and test capability gating behavior including interaction with plain text vs tools requests and streaming
packages/llm/src/capabilities.ts
packages/llm/src/capabilities.test.ts
packages/llm/src/errors.ts
packages/llm/src/index.ts
Add generic conformance harness with replay/record substrate and Anthropic fixtures to validate adapter normalization offline and live.
  • Define ConformanceFixtures/ConformanceExpectations plus defineConformanceSuite to assert concrete canonical behaviors (stop reasons, token counts, tool names, error kinds) across generate/stream and error scenarios
  • Implement replayFetch/recordFetch and RecordedResponse types to drive adapters via injected fetch using recorded JSON/SSE payloads for deterministic tests
  • Create Anthropic-specific conformance fixtures and tests wiring createAnthropicAdapter with replayFetch and a live, key-gated nightly smoke test using anthropicAdapter
packages/llm/src/conformance/spec.ts
packages/llm/src/conformance/replay.ts
packages/llm/src/conformance/fixtures/anthropic.ts
packages/llm/src/conformance/anthropic.conformance.test.ts
packages/llm/src/adapters/anthropic.ts
Tighten seam/platform boundary and update docs/roadmap and deferred tasks to reflect new milestones and guards.
  • Update roadmap docs to mark 1.A/1.B/1.E as done, describe adapter lane work (1.C/1.D/1.F/1.I), and clarify status of phase-1 engine and seam milestones
  • Document AbortSignalLike and seam/platform separation in shared content types, and introduce tsconfig.seam.json as a platform-free guard run alongside the main typecheck
  • Extend deferred tasks with coverage/threshold improvements and ensure @types/node and SDK imports are restricted to adapters and conformance code
docs/roadmap/current.md
docs/roadmap/phases/phase-1-engine-and-llm.md
docs/roadmap/deferred-tasks.md
packages/shared/src/content.ts
packages/llm/tsconfig.json
packages/llm/tsconfig.seam.json
pnpm-lock.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Anthropic Adapter & Testing Infrastructure

Layer / File(s) Summary
LLM error classification & provider wrapper
packages/llm/src/llm-error.ts, packages/llm/src/llm-error.test.ts
Defines RETRYABLE_KINDS and isRetryable; maps HTTP status → LlmErrorKind; adds LlmProviderError and makeLlmError; tests for mappings and optional fields.
Capability gating & UnsupportedCapabilityError
packages/llm/src/capabilities.ts, packages/llm/src/capabilities.test.ts, packages/llm/src/errors.ts
Adds Capability type, requiredCapabilities, supportsRequest, assertSupported, assertStreamable, and UnsupportedCapabilityError class; tests validate thrown error shape and gating behavior.
Anthropic adapter implementation & tests
packages/llm/src/adapters/anthropic.ts, packages/llm/src/adapters/index.ts, packages/llm/src/adapters/anthropic.test.ts
Implements Anthropic→canonical mappers (mapStopReason, mapUsage, mapContent), anthropicErrorToLlmError, request bridging, streaming SSE folding, DI factory and production singleton; comprehensive tests for mapping, request construction, streaming edge cases, error classification, and secret-safety.
Conformance fixtures, replay & spec
packages/llm/src/conformance/replay.ts, packages/llm/src/conformance/spec.ts, packages/llm/src/conformance/fixtures/anthropic.ts, packages/llm/src/conformance/anthropic.conformance.test.ts, packages/llm/src/conformance/replay.test.ts
Adds RecordedResponse/FetchLike types, replayFetch/recordFetch with JSON/body validation and secret detection (looksLikeSecret), conformance spec and fixtures with SSE transcripts, defineConformanceSuite, and replay/live tests.
Public API re-exports
packages/llm/src/index.ts
Re-exports UnsupportedCapabilityError, capability helpers (requiredCapabilities, supportsRequest, assertSupported, assertStreamable, Capability), and LLM error utilities (RETRYABLE_KINDS, isRetryable, kindFromHttpStatus, makeLlmError, LlmProviderError).
Package configuration & TypeScript
packages/llm/package.json, packages/llm/tsconfig.json, packages/llm/tsconfig.seam.json, packages/llm/tsconfig.build.json, pnpm-workspace.yaml
Adds ./adapters export and typecheck script; includes Node types for package typecheck; adds tsconfig.seam.json for seam purity; excludes conformance from build output; adds @anthropic-ai/sdk to workspace catalog and dev deps.
Roadmap & documentation
docs/roadmap/current.md, docs/roadmap/deferred-tasks.md, docs/roadmap/phases/phase-1-engine-and-llm.md, packages/shared/src/content.ts, .gitignore
Updates Phase 1 Immediate next steps and workstream statuses to mark PR #7 as landed; adds deferred test-depth task; clarifies AbortSignalLike docs; ignores coverage-tmp/.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • HodeTech/Relavium#7: Wave-1 seam types (LLMProvider, CostTracker, ToolNormalizer) that this PR builds upon and extends with capability gating and adapter integrations.

"🐰 I hop through code and stitch the seam,
Anthropic whispers turn to canonical dream,
Streams fold to chunks and errors earn a name,
Fixtures replay stories, tests chase every claim,
Docs and exports set the paths to aim."

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: introducing the Anthropic adapter (1.C) along with supporting infrastructure (LlmError 1.I, conformance harness 1.F, capabilities 1.D) for Phase 1's adapter lane.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread packages/llm/src/conformance/spec.ts

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +203 to +214
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 };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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}

Comment on lines +245 to +250
if (req.tools !== undefined) {
body.tools = req.tools.map(toAnthropicTool);
}
if (req.toolChoice !== undefined) {
body.tool_choice = toAnthropicToolChoice(req.toolChoice);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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  }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
packages/llm/src/adapters/anthropic.test.ts (1)

76-76: ⚡ Quick win

Replace unsafe test assertions with satisfies and runtime narrowing helpers.

These as assertions bypass strict typing in multiple places. Prefer small parse guards for JSON bodies and satisfies for 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 using as.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4d244a1 and 5d5bd6c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • docs/roadmap/current.md
  • docs/roadmap/deferred-tasks.md
  • docs/roadmap/phases/phase-1-engine-and-llm.md
  • packages/llm/package.json
  • packages/llm/src/adapters/anthropic.test.ts
  • packages/llm/src/adapters/anthropic.ts
  • packages/llm/src/adapters/index.ts
  • packages/llm/src/capabilities.test.ts
  • packages/llm/src/capabilities.ts
  • packages/llm/src/conformance/anthropic.conformance.test.ts
  • packages/llm/src/conformance/fixtures/anthropic.ts
  • packages/llm/src/conformance/replay.ts
  • packages/llm/src/conformance/spec.ts
  • packages/llm/src/errors.ts
  • packages/llm/src/index.ts
  • packages/llm/src/llm-error.test.ts
  • packages/llm/src/llm-error.ts
  • packages/llm/tsconfig.build.json
  • packages/llm/tsconfig.json
  • packages/llm/tsconfig.seam.json
  • packages/shared/src/content.ts
  • pnpm-workspace.yaml

Comment thread packages/llm/src/adapters/anthropic.ts
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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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" || true

Repository: 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" || true

Repository: 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" || true

Repository: HodeTech/Relavium

Length of output: 5750


Remove unsafe as casts in the Anthropic adapter boundary

  • Line 225: replace wire.input_schema as Anthropic.Tool.InputSchema with runtime narrowing/type guard, then assign without as.
  • Line 269: replace req.signal as AbortSignal with 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

Comment thread packages/llm/src/adapters/anthropic.ts
Comment thread packages/llm/src/adapters/anthropic.ts Outdated
Comment thread packages/llm/src/conformance/spec.ts
…+ 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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/llm/src/conformance/replay.ts (1)

24-24: ⚡ Quick win

Tighten 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5bd6c and 6e46889.

📒 Files selected for processing (8)
  • packages/llm/src/adapters/anthropic.test.ts
  • packages/llm/src/adapters/anthropic.ts
  • packages/llm/src/conformance/fixtures/anthropic.ts
  • packages/llm/src/conformance/replay.test.ts
  • packages/llm/src/conformance/replay.ts
  • packages/llm/src/conformance/spec.ts
  • packages/llm/src/llm-error.test.ts
  • packages/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>
@sonarqubecloud

sonarqubecloud Bot commented Jun 6, 2026

Copy link
Copy Markdown

@cemililik

Copy link
Copy Markdown
Contributor Author

@coderabbitai review all

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

@cemililik Sure! I'll kick off a full review of all the changes in this PR.


🧠 Learnings used
Learnt from: CR
Repo: HodeTech/Relavium PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-05T10:10:45.443Z
Learning: Use trunk-based development on `main` with short-lived feature branches via PR; write Conventional Commits with per-package scope (e.g., `feat(core):`, `fix(llm):`, `docs(decisions):`) and reference the ADR or task the change advances
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/llm/src/adapters/anthropic.test.ts (1)

58-58: ⚡ Quick win

Replace remaining unsafe test casts with typed narrowing helpers.

Lines 58, 227, and 452 rely on unsafe as assertions. Please switch these to runtime narrowing (isRecord/array guards) or satisfies-based fixture typing so tests stay strict-mode safe without assertion bypass.

As per coding guidelines, **/*.{ts,tsx} must use strict mode with no unsafe as assertions 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4d244a1 and 048129c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • .gitignore
  • docs/roadmap/current.md
  • docs/roadmap/deferred-tasks.md
  • docs/roadmap/phases/phase-1-engine-and-llm.md
  • packages/llm/package.json
  • packages/llm/src/adapters/anthropic.test.ts
  • packages/llm/src/adapters/anthropic.ts
  • packages/llm/src/adapters/index.ts
  • packages/llm/src/capabilities.test.ts
  • packages/llm/src/capabilities.ts
  • packages/llm/src/conformance/anthropic.conformance.test.ts
  • packages/llm/src/conformance/fixtures/anthropic.ts
  • packages/llm/src/conformance/replay.test.ts
  • packages/llm/src/conformance/replay.ts
  • packages/llm/src/conformance/spec.ts
  • packages/llm/src/errors.ts
  • packages/llm/src/index.ts
  • packages/llm/src/llm-error.test.ts
  • packages/llm/src/llm-error.ts
  • packages/llm/tsconfig.build.json
  • packages/llm/tsconfig.json
  • packages/llm/tsconfig.seam.json
  • packages/shared/src/content.ts
  • pnpm-workspace.yaml

@cemililik
cemililik merged commit a3162a5 into main Jun 6, 2026
7 checks passed
cemililik added a commit that referenced this pull request Jun 6, 2026
…#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>
cemililik added a commit that referenced this pull request Jul 4, 2026
…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>
cemililik added a commit that referenced this pull request Jul 4, 2026
…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>
cemililik added a commit that referenced this pull request Jul 8, 2026
…+ 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>
cemililik added a commit that referenced this pull request Jul 9, 2026
…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>
cemililik added a commit that referenced this pull request Jul 9, 2026
…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>
cemililik added a commit that referenced this pull request Jul 9, 2026
… 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant