Skip to content

fix(THU-899): route managed Opus 5 through the native Anthropic Messages API - #1284

Open
Sancus wants to merge 7 commits into
mainfrom
fix/opus-5-prompt-caching
Open

Sancus wants to merge 7 commits into
mainfrom
fix/opus-5-prompt-caching

Conversation

@Sancus

@Sancus Sancus commented Sep 18, 2026

Copy link
Copy Markdown
Member

Linear

THU-899

Summary

  • Route the managed opus-5 model through Pi's native anthropic-messages adapter and a new backend POST /v1/chat/v1/messages route instead of Anthropic's OpenAI-compatible endpoint, which does not support prompt caching. Pi marks the system prompt, last tool and last user block with cache_control; the backend adds top-level automatic cache_control on every request.
  • The native route reuses the /chat/completions auth, quota admission, usage recording and latency telemetry. It validates the body (400 on malformed input), rejects Anthropic server tools, and classifies Anthropic SDK and mid-stream errors into the existing telemetry kinds with requestId/errorType.
  • Usage ledger prices cache traffic at Anthropic's published multipliers: 5-minute writes 1.25x, 1-hour writes 2x, cache reads 0.1x.
  • Upgrade @earendil-works/pi-ai, pi-agent-core, pi-coding-agent (and pi-tui in cli/) 0.80.7 -> 0.82.1, the first release whose catalog includes claude-opus-5. Provider auth is bound explicitly (boundApiKeyAuth) because 0.82 rejects an unconfigured provider; AgentHarness no longer takes an execution environment, so the app and CLI harness builders drop it.
  • Opus 5 default contextWindow 200K -> 300K; defaultModelsVersion 7 -> 8 and the lineage migration carries the new value to unmodified rows.
  • The Pi harness stream now emits usage metadata at turn_end, so chat_threads.contextSize is persisted and the context-usage indicator renders for Pi-routed models (it previously only worked on the legacy AI SDK path).
  • Adds @anthropic-ai/sdk to the backend.

Test Plan

  • bun run tsc --noEmit, bun run lint, bun run test, bun run test:agent-core
  • backend bun run type-check, bun run lint, bun test
  • cli/: bun run typecheck, bun run test, bun run build, artifact --version/--help smoke
  • Manual: in an Opus 5 thread, the second turn's message_start usage reports cache_read_input_tokens > 0; the context ring appears in the composer after the first reply.

Changes

  • backend/src/inference/routes.ts - POST /v1/messages handler; shared createUsageCallbacks / streamingResponseHeaders helpers used by both routes
  • backend/src/inference/client.ts - getAnthropicMessagesClient
  • backend/src/utils/anthropic-streaming.ts - re-encodes SDK events as SSE, collects final usage, reports stream errors
  • backend/src/inference/usage-ledger.ts, error-kind.ts - cache pricing; Anthropic error classification
  • shared/agent-core/anthropic-model.ts, build-app-harness.ts, bound-api-key-auth.ts, openai-compat-model.ts - managed alias / base URL / context window on the native descriptor; Pi 0.82 auth and harness changes
  • shared/agent-core/pi-to-aisdk-stream.ts - usage message-metadata at turn_end
  • src/acp/built-in-adapter.ts, src/ai/fetch.ts - route thunderbolt + vendor: anthropic models to the native path (Pi and legacy AI SDK); resolveManagedAnthropicConnection derives from the shared thunderbolt auth policy
  • shared/defaults/models.ts, src/lib/data-migrations/upgrade-model-defaults.ts - 300K default
  • cli/package.json, cli/bun.lock, cli/src/agent/harness.ts - Pi 0.82.1 pins and harness construction
  • Tests for each of the above

- the managed model went through Anthropic's OpenAI-compatible endpoint, which
  does not support prompt caching; Pi's anthropic-messages adapter now targets
  a backend /chat/v1/messages route that keeps auth, quota and usage accounting
- the route adds top-level automatic cache_control, prices 5m/1h cache writes
  and cache reads, rejects server tools, and classifies Anthropic errors
- upgrade Pi to 0.82.1, the first release with claude-opus-5; bind provider
  auth explicitly since 0.82 rejects an unconfigured provider
- raise the Opus 5 default context window to 300K (defaultModelsVersion 8)
- the Pi stream never emitted usage metadata, so the thread contextSize stayed
  null and the context-usage ring was hidden for every Pi-routed model
- map Pi usage to the AI SDK shape at turn_end, including cached input tokens
- wire-level assertions for cache_control on the managed Messages route,
  cache-aware pricing, server-tool rejection and Anthropic error telemetry
- Pi adapter and harness tests for the managed alias, context window and the
  turn usage emitted for the context indicator
@github-actions

Copy link
Copy Markdown

Semgrep Security Scan

No security issues found.

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

Preview environment deployed 🚀

Service URL
Marketing / blog / docs https://thunderbolt-pr-1284.preview.thunderbolt.io
App https://app-pr-1284.preview.thunderbolt.io
API https://api-pr-1284.preview.thunderbolt.io
Keycloak https://auth-pr-1284.preview.thunderbolt.io
PowerSync https://powersync-pr-1284.preview.thunderbolt.io

Stack: preview-pr-1284 · Commit: f9088fd5a66f7a8a41c84954e081cfbbe1bbae29

Auto-destroys on PR close/merge. Login via the bundled Keycloak realm — demo@thunderbolt.io / demo by default.

- cli/ pins its own copies of the four @earendil-works/pi-* packages and CI
  requires them to match the root pi-ai version
- AgentHarness no longer takes an execution environment in 0.82; the coding
  tools are bound to cwd directly, so the unused NodeExecutionEnv goes away
@Sancus Sancus changed the title fix: route managed Opus 5 through the native Anthropic Messages API fix: route Thunderbolt managed Opus 5 through the native Anthropic Messages API Sep 18, 2026
@Sancus Sancus changed the title fix: route Thunderbolt managed Opus 5 through the native Anthropic Messages API fix(THU-899): route managed Opus 5 through the native Anthropic Messages API Sep 18, 2026
@Sancus

Sancus commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

There is no small context price discount on Anthropic models anymore at <200K, so I raised the default context to 300K intentionally as part of this patch. 300K aligns well with the small context of other models(OAI) so if you're not doing 1M I think it's the sweet spot.

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

PR Metrics

Metric Value
Lines changed (prod code) +596 / -143
JS bundle size (gzipped) 🟢 644.8 KB → 644.7 KB (-164 B, -0.0%)
Test coverage 🟢 82.25% → 82.28% (+0.0%)
Performance (preview) Preview not ready — Render deploy may have timed out
Accessibility
Best Practices
SEO

Updated Fri, 18 Sep 2026 20:41:02 GMT · run #3054

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: eb893423245b · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread cli/package.json Outdated

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: 16ce8409d3d6 · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread backend/src/inference/routes.ts

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: d2213da3f90c · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread backend/src/inference/routes.ts
Comment thread backend/src/inference/routes.ts Outdated
…ilures

The native route's stream onError now spreads getApiErrorMetadata like
/completions does, so requestId and errorType reach telemetry.

Also share the latency recorder and admission gate between the two
inference routes so timing headers and quota semantics cannot drift.
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