Skip to content

fix(messages): make /v1/messages a drop-in for Anthropic SDK clients - #543

Merged
SantiagoDePolonia merged 3 commits into
mainfrom
fix/anthropic-sdk
Jul 17, 2026
Merged

fix(messages): make /v1/messages a drop-in for Anthropic SDK clients#543
SantiagoDePolonia merged 3 commits into
mainfrom
fix/anthropic-sdk

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Tested GoModel extensively with the official Anthropic Python SDK (anthropic 0.117.0) pointed at /v1/messages, across cheap models on five providers (OpenAI, Gemini, Groq, DeepSeek, Anthropic). The core contract held up (52 checks); this PR fixes every gap between "works" and "drop-in". Full findings + retest results: docs/dev/2026-07-17_anthropic-sdk-compat-findings.md.

User-visible impact

  • Anthropic(api_key=...) now works unchanged. The SDK sends the key as x-api-key; the auth middleware now accepts that header as a fallback when no Authorization header is present (Bearer keeps precedence). Applies to translated and passthrough routes.
  • stop_reason: "stop_sequence" + the matched sequence are reported when the serving provider knows them natively (Anthropic), stream and non-stream. The value rides the canonical dialect as a stop_sequence choice/delta extension field — same pattern as the existing reasoning_content relay. OpenAI-family providers structurally can't distinguish stop-parameter hits from natural stops, so they keep end_turn (documented).
  • Streaming message_start carries a real usage.input_tokens (the chars/4 heuristic estimate) instead of 0; authoritative usage still arrives in message_delta, which SDK accumulators prefer — final totals stay exact.
  • client.models.list() returns proper Anthropic-shaped models (type: "model", display_name, RFC3339 created_at, has_more/first_id/last_id) — negotiated via the anthropic-version header Anthropic SDKs always send; OpenAI clients see the unchanged OpenAI shape.
  • Unknown routes return the canonical error envelope (Anthropic shape for Anthropic-dialect callers) instead of echo's {"message":"Not Found"}, so SDKs raise clean typed errors.

Provider-specific behavior

  • Only the anthropic provider reports matched stop sequences; the extension field is simply absent elsewhere.
  • OpenAI-dialect responses from the anthropic provider gain the optional stop_sequence extension field (omitted unless a sequence matched), consistent with the existing reasoning_content extension.

Docs

  • docs/advanced/anthropic-messages-api.mdx: new Authentication section with an Anthropic-SDK snippet, models-endpoint row, and updated limitations (cache_control dropped, heuristic count_tokens, no Messages batches API — use /v1/batches).
  • ADR-0007 stop-sequence tradeoff note amended.

Testing

  • New table-driven tests: auth (x-api-key accept/reject/precedence), provider response + stream translation, dialect converters (stop_sequence mapping, message_start estimate), Anthropic-shaped models list, both 404 dialect envelopes.
  • go test ./internal/... green; gofmt/vet clean.
  • Live retest with the Anthropic SDK against the rebuilt gateway: 32/32 phase-1 checks pass (was 28 + auth blocker); verified stop_sequence fidelity on the Claude path in both modes.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Anthropic-compatible model listings at /v1/models.
    • Added API-key authentication via x-api-key.
    • Seeded streaming message_start with heuristic input-token estimates.
    • Exposed provider-matched stop sequences in Anthropic responses and streams.
  • Bug Fixes
    • Unknown routes now return dialect-appropriate 404 error formats.
    • Improved stop-sequence vs tool-use reporting precedence in streams.
  • Documentation
    • Expanded Anthropic /v1/messages compatibility, authentication, limitations, and added dated compatibility findings.

Tested GoModel end-to-end with the official Anthropic Python SDK across
five providers and fixed every gap between "works" and "drop-in":

- Auth: accept the Anthropic-native x-api-key header as a fallback when
  no Authorization header is present, so Anthropic(api_key=...) works
  unchanged on translated and passthrough routes.
- stop_sequence: the anthropic provider now parses the natively reported
  matched sequence and carries it as a stop_sequence choice/delta
  extension (same pattern as reasoning_content); the Messages dialect
  maps it back to stop_reason "stop_sequence" plus the matched value,
  stream and non-stream. OpenAI-family providers structurally cannot
  report it (finish_reason conflates) and keep end_turn - documented.
- Streaming usage: message_start now seeds usage.input_tokens with the
  chars/4 heuristic estimate; authoritative usage still arrives in
  message_delta, which SDK accumulators prefer.
- Models: GET /v1/models renders the Anthropic list shape (type,
  display_name, created_at, has_more/first_id/last_id) when the request
  carries the anthropic-version header Anthropic SDKs always send.
- 404s: unknown routes return the canonical error envelope (Anthropic
  shape for Anthropic-dialect callers) instead of echo's default body,
  so SDK clients raise clean typed errors.

Documented remaining translation limitations (cache_control dropped,
heuristic count_tokens, unsigned thinking blocks, no Messages batches
API) in docs/advanced/anthropic-messages-api.mdx and amended ADR-0007.
Full findings and retest results in
docs/dev/2026-07-17_anthropic-sdk-compat-findings.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 61f282bc-e6ee-4894-a8a4-b2b1fc370563

📥 Commits

Reviewing files that changed from the base of the PR and between fea7ef2 and 9360a5f.

📒 Files selected for processing (1)
  • internal/anthropicapi/request_test.go

📝 Walkthrough

Walkthrough

The PR extends Anthropic compatibility for stop sequences, streaming token estimates, authentication, model listing, unknown-route errors, and SDK compatibility documentation.

Changes

Anthropic response and streaming compatibility

Layer / File(s) Summary
Stop-sequence response propagation
internal/core/types.go, internal/providers/anthropic/*, internal/anthropicapi/*, tests/contract/testdata/golden/*
Provider and stream conversions preserve stop_sequence, while Anthropic responses expose stop_reason: "stop_sequence" without changing canonical finish reasons.
Input token estimation
internal/anthropicapi/request.go, internal/anthropicapi/request_test.go, internal/server/messages_handler.go
Canonical chat requests receive heuristic token estimates, which seed streaming message_start usage before final provider usage is emitted.

Server dialect handling

Layer / File(s) Summary
Authentication, models, and route errors
internal/server/auth.go, internal/server/handlers.go, internal/server/error_support.go, internal/server/http.go, internal/anthropicapi/models.go, internal/server/*_test.go
Requests accept bearer or x-api-key credentials, Anthropic requests receive Anthropic model-list and 404 envelopes, and Echo routes unknown paths through dialect-aware handling.

Compatibility documentation

Layer / File(s) Summary
API and compatibility findings
docs/adr/*, docs/advanced/*, docs/dev/*, tests/contract/testdata/golden/*
Documentation and contract data record endpoint support, authentication, stop-sequence and token-counting behavior, test findings, provider quirks, and follow-up items.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AnthropicSDK
  participant MessagesHandler
  participant Provider
  participant StreamConverter
  AnthropicSDK->>MessagesHandler: POST /v1/messages
  MessagesHandler->>Provider: Request streaming completion
  Provider-->>StreamConverter: Stream chunks and usage
  StreamConverter-->>AnthropicSDK: message_start estimate and final message_delta
Loading

Possibly related PRs

Poem

A bunny found a sequence to stop,
And counted each token drop by drop.
Keys now hop through headers bright,
Models list in Anthropic light.
Errors wear the proper coat—
Compatibility afloat!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main change: making /v1/messages compatible with Anthropic SDK clients.
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.
✨ 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 fix/anthropic-sdk

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@mintlify

mintlify Bot commented Jul 17, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
gomodel 🟢 Ready View Preview Jul 17, 2026, 1:28 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@codecov-commenter

codecov-commenter commented Jul 17, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 80.23256% with 17 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/anthropicapi/models.go 0.00% 17 Missing ⚠️

📢 Thoughts on this report? Let us know!

@mintlify

mintlify Bot commented Jul 17, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
gomodel 🟡 Building Jul 17, 2026, 1:26 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

Safe to merge with low risk.

The changes are focused on Anthropic SDK compatibility and preserve existing OpenAI-shaped behavior where needed. Tests cover the new auth, response conversion, stream conversion, models listing, and error-envelope behavior. No blocking correctness or security issues were identified.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Attempted a combined focused anthropic command and captured the complete internal/anthropicapi verbose trace before the command timed out.
  • Ran the anthropic providers test suite and completed with exit code 0.
  • Ran the anthropic server test suite and completed with exit code 0.
  • Executed a broad timeout run across internal packages; the output shows many passes and an unrelated failure in internal/providers.

View all artifacts

T-Rex Ran code and verified through T-Rex

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant SDK as Anthropic SDK client
participant Auth as Auth middleware
participant Handler as /v1 handlers
participant Pipeline as Chat pipeline
participant Provider as Upstream provider
participant Conv as Anthropic response/stream converter

SDK->>Auth: Request with Authorization Bearer or x-api-key
Auth->>Handler: Authenticated request
alt POST /v1/messages
    Handler->>Pipeline: Translate Messages request to ChatRequest
    Pipeline->>Provider: Execute/stream chat completion
    Provider-->>Pipeline: Chat response or SSE chunks
    Pipeline->>Conv: Convert to Anthropic Messages shape
    Conv-->>SDK: Message response/events with usage and stop_sequence when known
else GET /v1/models with anthropic-version
    Handler-->>SDK: Anthropic models list shape
else Unknown route
    Handler-->>SDK: Dialect-aware not_found_error envelope
end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant SDK as Anthropic SDK client
participant Auth as Auth middleware
participant Handler as /v1 handlers
participant Pipeline as Chat pipeline
participant Provider as Upstream provider
participant Conv as Anthropic response/stream converter

SDK->>Auth: Request with Authorization Bearer or x-api-key
Auth->>Handler: Authenticated request
alt POST /v1/messages
    Handler->>Pipeline: Translate Messages request to ChatRequest
    Pipeline->>Provider: Execute/stream chat completion
    Provider-->>Pipeline: Chat response or SSE chunks
    Pipeline->>Conv: Convert to Anthropic Messages shape
    Conv-->>SDK: Message response/events with usage and stop_sequence when known
else GET /v1/models with anthropic-version
    Handler-->>SDK: Anthropic models list shape
else Unknown route
    Handler-->>SDK: Dialect-aware not_found_error envelope
end
Loading

Reviews (1): Last reviewed commit: "fix(messages): make /v1/messages a drop-..." | Re-trigger Greptile

Registering a wildcard RouteNotFound route made echo's router prefer it
over the 405 method-not-allowed fallback during backtracking, so wrong-
method requests on known paths (e.g. GET /v1/chat/completions) returned
404. Move the dialect-aware canonical 404 handler to the router-level
NotFoundHandler config, which fires only when no route matches at all.

Also update the anthropic contract golden for the intentional
stop_sequence choice extension introduced in the previous commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@internal/anthropicapi/request_test.go`:
- Around line 458-473: Refactor TestEstimateChatInputTokens into a table-driven
test covering the existing nil and message-only cases, and add a case containing
both Tools and ToolCalls. Set the expected token estimate for the new case
according to EstimateChatInputTokens behavior, preserving the current assertions
and using descriptive test names.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 724f5e77-d8e9-417a-9c9a-0c8b700dd41f

📥 Commits

Reviewing files that changed from the base of the PR and between 23cdb25 and fea7ef2.

📒 Files selected for processing (24)
  • docs/adr/0007-anthropic-messages-ingress.md
  • docs/advanced/anthropic-messages-api.mdx
  • docs/dev/2026-07-17_anthropic-sdk-compat-findings.md
  • internal/anthropicapi/models.go
  • internal/anthropicapi/request.go
  • internal/anthropicapi/request_test.go
  • internal/anthropicapi/response.go
  • internal/anthropicapi/response_test.go
  • internal/anthropicapi/stream.go
  • internal/anthropicapi/stream_test.go
  • internal/core/types.go
  • internal/providers/anthropic/anthropic_test.go
  • internal/providers/anthropic/chat.go
  • internal/providers/anthropic/chat_stream.go
  • internal/providers/anthropic/types.go
  • internal/server/auth.go
  • internal/server/auth_test.go
  • internal/server/error_support.go
  • internal/server/error_support_test.go
  • internal/server/handlers.go
  • internal/server/handlers_test.go
  • internal/server/http.go
  • internal/server/messages_handler.go
  • tests/contract/testdata/golden/anthropic/messages_with_params.golden.json

Comment thread internal/anthropicapi/request_test.go
Review follow-up: convert the estimate test to the table-driven form and
add a case covering tool calls and tool definitions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SantiagoDePolonia
SantiagoDePolonia merged commit c328c78 into main Jul 17, 2026
19 checks passed
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.

2 participants