Anthropic Messages adapter (#40)
CI / ci (push) Successful in 1m27s

Co-authored-by: dev_agent <dev_agent@cubecraftlabs.com>
Co-committed-by: dev_agent <dev_agent@cubecraftlabs.com>
This commit was merged in pull request #40.
This commit is contained in:
2026-10-04 08:09:20 -04:00
committed by approver_agent
parent acd29c8221
commit 15886f2c55
18 changed files with 1909 additions and 23 deletions
+41
View File
@@ -0,0 +1,41 @@
# Anthropic adapter fixtures
These are the Messages API response bodies the contract tests in
`anthropic_test.go` replay. They are **not live captures**. Product AI is
tested with fakes, and live vendor calls are opt-in and never use
credentials, so no request was sent to Anthropic to make them. Each fixture
is a sanitized adaptation of the response shapes in Anthropic's public API
documentation:
- Messages: <https://docs.anthropic.com/en/api/messages>
- Tool use, including `tool_choice` `{type: tool}`:
<https://docs.anthropic.com/en/docs/build-with-claude/tool-use>
- List models: <https://docs.anthropic.com/en/api/models-list>
- Errors: <https://docs.anthropic.com/en/api/errors>
Sanitizing:
- IDs, request IDs, timestamps and token counts are illustrative values in
the documented format, not values from a real account.
- Message content and tool input are replaced with MealQuest dinner text.
- No real credential appears anywhere. `{{KEY}}` is a placeholder the tests
replace with a fake key, to prove the adapter redacts a key echoed back in
a vendor body.
Below, *not in the public example* marks fields or values that were added
for a test case and do not come from the documented example.
| Fixture | Shape | Not in the public example |
| --- | --- | --- |
| `messages_tool_use.json` | Message with one `tool_use` block, `stop_reason: tool_use`, `usage` with cache fields. | The tool name and input. |
| `messages_text.json` | Message with `text` blocks, `stop_reason: end_turn`. | A second text block, to test joining. |
| `messages_max_tokens.json` | Message with a `text` block, `stop_reason: max_tokens`. | — |
| `models_page1.json` | Models list page: `data`, `has_more: true`, `first_id`, `last_id`. | — |
| `models_page2.json` | Last models list page, `has_more: false`. | The second model has no `display_name`, to test the fallback to `id`. |
| `error_authentication.json` | Error, `authentication_error` (401). | The `{{KEY}}` text; added to test redaction. |
| `error_permission.json` | Error, `permission_error` (403). | — |
| `error_rate_limit.json` | Error, `rate_limit_error` (429). | The `{{KEY}}` text; added to test redaction. |
| `error_overloaded.json` | Error, `overloaded_error` (529). | — |
| `error_not_found.json` | Error, `not_found_error` (404). | — |
| `error_invalid_request.json` | Error, `invalid_request_error` (400). | — |
| `error_api.json` | Error, `api_error` (500). | The `x-api-key: {{KEY}}` text; added to test redaction. |
+1
View File
@@ -0,0 +1 @@
{"type": "error", "error": {"type": "api_error", "message": "An unexpected error has occurred internal to Anthropic's systems. x-api-key: {{KEY}}"}, "request_id": "req_011CSHoEeqs5C35K2UUqR7G4"}
@@ -0,0 +1 @@
{"type": "error", "error": {"type": "authentication_error", "message": "invalid x-api-key: {{KEY}}"}, "request_id": "req_011CSHoEeqs5C35K2UUqR7Fy"}
@@ -0,0 +1 @@
{"type": "error", "error": {"type": "invalid_request_error", "message": "max_tokens: Field required"}, "request_id": "req_011CSHoEeqs5C35K2UUqR7G3"}
@@ -0,0 +1 @@
{"type": "error", "error": {"type": "not_found_error", "message": "model: claude-nonexistent"}, "request_id": "req_011CSHoEeqs5C35K2UUqR7G2"}
@@ -0,0 +1 @@
{"type": "error", "error": {"type": "overloaded_error", "message": "Overloaded"}, "request_id": "req_011CSHoEeqs5C35K2UUqR7G1"}
@@ -0,0 +1 @@
{"type": "error", "error": {"type": "permission_error", "message": "Your API key does not have permission to use the specified resource."}, "request_id": "req_011CSHoEeqs5C35K2UUqR7Fz"}
@@ -0,0 +1 @@
{"type": "error", "error": {"type": "rate_limit_error", "message": "Number of request tokens has exceeded your per-minute rate limit for key {{KEY}}"}, "request_id": "req_011CSHoEeqs5C35K2UUqR7G0"}
@@ -0,0 +1,12 @@
{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-5",
"content": [
{"type": "text", "text": "Here are some Side quests for Our kitchen"}
],
"stop_reason": "max_tokens",
"stop_sequence": null,
"usage": {"input_tokens": 472, "output_tokens": 400}
}
+18
View File
@@ -0,0 +1,18 @@
{
"id": "msg_013Zva2CMHLNnXjNJJKqJ2EF",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-5",
"content": [
{"type": "text", "text": "Try a crock pot chili: "},
{"type": "text", "text": "it is a Quick dinner."}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 2095,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"output_tokens": 503
}
}
@@ -0,0 +1,22 @@
{
"id": "msg_01Aq9w938a90dw8q",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-5",
"content": [
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "side_quests",
"input": {"dinners": [{"name": "Sheet-pan gnocchi", "why": "Like your Regular tray bake, but quicker."}]}
}
],
"stop_reason": "tool_use",
"stop_sequence": null,
"usage": {
"input_tokens": 472,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"output_tokens": 61
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"data": [
{
"type": "model",
"id": "claude-sonnet-5",
"display_name": "Claude Sonnet 5",
"created_at": "2026-02-24T00:00:00Z"
}
],
"has_more": true,
"first_id": "claude-sonnet-5",
"last_id": "claude-sonnet-5"
}
+18
View File
@@ -0,0 +1,18 @@
{
"data": [
{
"type": "model",
"id": "claude-haiku-4-5-20251001",
"display_name": "Claude Haiku 4.5",
"created_at": "2025-10-01T00:00:00Z"
},
{
"type": "model",
"id": "claude-opus-5-5",
"created_at": "2026-05-01T00:00:00Z"
}
],
"has_more": false,
"first_id": "claude-haiku-4-5-20251001",
"last_id": "claude-opus-5-5"
}