Skip to content

feat(native-llm): route Anthropic API-key models through native runtime#28271

Open
kitlangton wants to merge 5 commits into
anomalyco:devfrom
kitlangton:native-llm-anthropic
Open

feat(native-llm): route Anthropic API-key models through native runtime#28271
kitlangton wants to merge 5 commits into
anomalyco:devfrom
kitlangton:native-llm-anthropic

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

Summary

  • Widens the native LLM gate beyond OpenAI: Anthropic providers using @ai-sdk/anthropic now stream through @opencode-ai/llm's anthropic-messages protocol. AI SDK remains the default; native is still opt-in behind OPENCODE_EXPERIMENTAL_NATIVE_LLM / OPENCODE_EXPERIMENTAL.
  • The request lowering side (native-request.ts ROUTE / DEFAULT_BASE_URL tables) already mapped @ai-sdk/anthropicanthropic-messages, so the only change to native-runtime.ts:status() is admitting the new providerID + npm pair.
  • OAuth (Claude Pro/Max) is still rejected — token-refresh wiring is the next piece of work.

Test plan

  • bun run typecheck from packages/opencode — clean.
  • bun run test test/session/llm-native.test.ts — 9/9 pass.
  • Recorded cassette session/native-anthropic-tool-call.json against claude-haiku-4-5-20251001; replay run is 3/3 in llm-native-recorded.test.ts.
  • Soak it in real usage for a bit, then start on Gemini / OpenAI Chat / OAuth.

To re-record: RECORD=true OPENCODE_RECORD_ANTHROPIC_API_KEY=... bun run test test/session/llm-native-recorded.test.ts

Widens the native LLM gate beyond OpenAI: Anthropic providers using
@ai-sdk/anthropic now stream through @opencode-ai/llm's anthropic-messages
protocol. Adds a recorded tool-call test against claude-haiku-4-5.
Replaces the single-call native-{provider}-tool-call recorded tests with
tool-loop scenarios modeled on @opencode-ai/llm's golden suite. Each test
drives two turns through LLM.Service.stream — mirroring how the session
processor drives multi-turn in production — and asserts the model's final
text answer after the tool result is fed back.

Anthropic cassette re-recorded against claude-haiku-4-5. OpenAI and Zen
tests skip until their cassettes are re-recorded (OPENCODE_RECORD_OPENAI_API_KEY
/ OPENCODE_RECORD_CONSOLE_TOKEN + OPENCODE_RECORD_ZEN_ORG_ID).
Collapses three near-duplicate provider blocks (config builder, cassette
constant, canRun flag, testEffect call, recorded*Instance) into one
PROVIDERS table and a single for-loop registering tests. Uses
LLMEvent.is.* type guards and LLMResponse.text() from @opencode-ai/llm
instead of hand-rolled Extract<...> + filter().map().join(). Extracts a
small toolRoundtrip helper for the assistant tool-call + tool-result
follow-up messages. Net -64 lines.
Replaces the global OPENCODE_RECORD_ZEN_API_URL env var with a per-test
connection slug (OPENCODE_RECORD_ZEN_CONNECTION). The Zen proxy URL
depends on which upstream connection the test routes through, so the
slug now lives next to the cassette spec instead of being smuggled
through a single URL env var.

Records the native Zen cassette against the production console proxy.
Re-records the Anthropic cassette in the same pass since the test code
shape changed.
- canRecord is now a plain boolean evaluated at module load (consistent
  with shouldRecord). Env vars don't change after import.
- Drop "as const" from the PROVIDERS satisfies — literal keys aren't
  consumed anywhere; the satisfies alone is enough.
- structuredClone instead of JSON.parse(JSON.stringify) for cloneModel.
- Inline the trivial single-use locals in recordedNativeLLMLayer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant