diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/LICENSE-MIT b/plugins/abridge/tests/fixtures/anthropic_openai/LICENSE-MIT new file mode 100644 index 0000000..c34ef5c --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 cc_convert maintainers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/README.md b/plugins/abridge/tests/fixtures/anthropic_openai/README.md new file mode 100644 index 0000000..fbe703b --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/README.md @@ -0,0 +1,50 @@ +# Anthropic ↔ OpenAI conversion fixtures + +Golden parity fixtures for bidirectional Anthropic Messages ↔ OpenAI +Chat Completions translation, exercised by +`tests/test_anthropic_transform_fixtures.py` against +`agentix.bridge.clients._anthropic_transforms`. + +## Origin and license + +These files are copied verbatim from the test corpus of +[cc_convert](https://github.com/yitianlian/cc_convert) (a Rust +Anthropic ↔ OpenAI protocol converter), specifically from +`tests/fixtures/` as vendored on this repo's `abridge/gateway-tito` +ref (`sidecars/cc_convert/tests/fixtures/`). Only the fixture data was +copied — no Rust code, no scripts. + +cc_convert is dual-licensed **MIT OR Apache-2.0**; we redistribute the +fixtures under the MIT license. The upstream MIT license text is +included alongside this file as [`LICENSE-MIT`](LICENSE-MIT) +(Copyright (c) 2026 cc_convert maintainers). + +## Layout + +Each subdirectory holds one direction of translation. Files pair up by +the shared `_` suffix: + +- `requests/` — Anthropic → OpenAI request translation. + Input `anthropic_.json`, expected output `openai_.json`, + plus `tool_map_.json` recording any sanitized-tool-name + mapping (empty for all cases except `13_long_tool_name`). +- `responses/` — OpenAI → Anthropic response translation. + Input `openai_.json`, expected output `anthropic_.json`, + plus `meta_.json` carrying the `original_model` and the + `tool_map` the reference translator was invoked with. +- `streams/` — OpenAI SSE → Anthropic event-stream translation. + Input `openai_.sse` (Chat Completions chunks), expected output + `anthropic_.jsonl` (one Anthropic stream event per line, in + order). + +## Provenance of the goldens + +Upstream generated the request goldens with LiteLLM's +`AnthropicAdapter` as the primary parity oracle (cross-referenced +against 1rgs/claude-code-proxy and maxnowack/anthropic-proxy); the +response and stream goldens are hand-curated from the OpenAI Chat +Completions API reference. Some goldens therefore carry LiteLLM +serialization artifacts (explicit `"thinking_blocks": null`, +`"provider_specific_fields": null`, an unconditional empty leading +text block in streams) — the test suite's comparison helpers document +how those are normalized. diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_01_plain_user_text.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_01_plain_user_text.json new file mode 100644 index 0000000..67e735c --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_01_plain_user_text.json @@ -0,0 +1,10 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hello", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_02_system_string.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_02_system_string.json new file mode 100644 index 0000000..0c4adca --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_02_system_string.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "system": "Be concise." +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_03_system_blocks_with_cache_control.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_03_system_blocks_with_cache_control.json new file mode 100644 index 0000000..d39d02d --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_03_system_blocks_with_cache_control.json @@ -0,0 +1,19 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "system": [ + { + "cache_control": { + "type": "ephemeral" + }, + "text": "rule 1", + "type": "text" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_04_multi_turn.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_04_multi_turn.json new file mode 100644 index 0000000..9143904 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_04_multi_turn.json @@ -0,0 +1,18 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + }, + { + "content": "hello!", + "role": "assistant" + }, + { + "content": "ok", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_05_user_image_base64.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_05_user_image_base64.json new file mode 100644 index 0000000..bec6c4a --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_05_user_image_base64.json @@ -0,0 +1,23 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "source": { + "data": "AAAA", + "media_type": "image/png", + "type": "base64" + }, + "type": "image" + }, + { + "text": "what is this?", + "type": "text" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_06_user_image_url.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_06_user_image_url.json new file mode 100644 index 0000000..7feb155 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_06_user_image_url.json @@ -0,0 +1,18 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "source": { + "type": "url", + "url": "https://example.com/x.png" + }, + "type": "image" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_07_assistant_single_tool_use.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_07_assistant_single_tool_use.json new file mode 100644 index 0000000..61d6e84 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_07_assistant_single_tool_use.json @@ -0,0 +1,19 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "id": "toolu_1", + "input": { + "city": "Paris" + }, + "name": "get_weather", + "type": "tool_use" + } + ], + "role": "assistant" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_08_assistant_two_parallel_tool_uses.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_08_assistant_two_parallel_tool_uses.json new file mode 100644 index 0000000..127b844 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_08_assistant_two_parallel_tool_uses.json @@ -0,0 +1,27 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "id": "tu_a", + "input": { + "a": 1 + }, + "name": "f1", + "type": "tool_use" + }, + { + "id": "tu_b", + "input": { + "b": 2 + }, + "name": "f2", + "type": "tool_use" + } + ], + "role": "assistant" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_09_user_single_tool_result.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_09_user_single_tool_result.json new file mode 100644 index 0000000..b004e41 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_09_user_single_tool_result.json @@ -0,0 +1,16 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "content": "21C", + "tool_use_id": "toolu_1", + "type": "tool_result" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_10_user_three_tool_results.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_10_user_three_tool_results.json new file mode 100644 index 0000000..35d3aae --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_10_user_three_tool_results.json @@ -0,0 +1,26 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "content": "a", + "tool_use_id": "t1", + "type": "tool_result" + }, + { + "content": "b", + "tool_use_id": "t2", + "type": "tool_result" + }, + { + "content": "c", + "tool_use_id": "t3", + "type": "tool_result" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_11_user_tool_result_multipart.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_11_user_tool_result_multipart.json new file mode 100644 index 0000000..b158869 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_11_user_tool_result_multipart.json @@ -0,0 +1,28 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "content": [ + { + "text": "see image:", + "type": "text" + }, + { + "source": { + "type": "url", + "url": "https://x/y.png" + }, + "type": "image" + } + ], + "tool_use_id": "t1", + "type": "tool_result" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_12_tools_input_schema.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_12_tools_input_schema.json new file mode 100644 index 0000000..4c7d4cb --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_12_tools_input_schema.json @@ -0,0 +1,27 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tools": [ + { + "description": "weather lookup", + "input_schema": { + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + }, + "name": "get_weather" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_13_long_tool_name.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_13_long_tool_name.json new file mode 100644 index 0000000..544c6ac --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_13_long_tool_name.json @@ -0,0 +1,18 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tools": [ + { + "input_schema": { + "type": "object" + }, + "name": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_14_tool_choice_any.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_14_tool_choice_any.json new file mode 100644 index 0000000..50b8a1b --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_14_tool_choice_any.json @@ -0,0 +1,13 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tool_choice": { + "type": "any" + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_15_tool_choice_named.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_15_tool_choice_named.json new file mode 100644 index 0000000..92ca508 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_15_tool_choice_named.json @@ -0,0 +1,22 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tool_choice": { + "name": "f", + "type": "tool" + }, + "tools": [ + { + "input_schema": { + "type": "object" + }, + "name": "f" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_16_metadata_user_id.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_16_metadata_user_id.json new file mode 100644 index 0000000..ea84c36 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_16_metadata_user_id.json @@ -0,0 +1,13 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "metadata": { + "user_id": "u-123" + }, + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_17_thinking_medium.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_17_thinking_medium.json new file mode 100644 index 0000000..f242b6e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_17_thinking_medium.json @@ -0,0 +1,14 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "thinking": { + "budget_tokens": 5000, + "type": "enabled" + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_18_top_k_dropped.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_18_top_k_dropped.json new file mode 100644 index 0000000..6d021f1 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_18_top_k_dropped.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "top_k": 20 +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_19_stream_include_usage.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_19_stream_include_usage.json new file mode 100644 index 0000000..4fa1dc6 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_19_stream_include_usage.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "stream": true +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_20_o3_mini_uses_max_completion_tokens.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_20_o3_mini_uses_max_completion_tokens.json new file mode 100644 index 0000000..725c70a --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_20_o3_mini_uses_max_completion_tokens.json @@ -0,0 +1,10 @@ +{ + "max_tokens": 200, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "o3-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_32_agent_tool_loop.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_32_agent_tool_loop.json new file mode 100644 index 0000000..c2568f4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_32_agent_tool_loop.json @@ -0,0 +1,41 @@ +{ + "max_tokens": 200, + "messages": [ + { + "content": "What's the weather in Tokyo?", + "role": "user" + }, + { + "content": [ + { + "text": "Let me check.", + "type": "text" + }, + { + "id": "toolu_w1", + "input": { + "city": "Tokyo" + }, + "name": "get_weather", + "type": "tool_use" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "content": "Sunny, 25C", + "tool_use_id": "toolu_w1", + "type": "tool_result" + } + ], + "role": "user" + }, + { + "content": "It's sunny and 25\u00b0C in Tokyo.", + "role": "assistant" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_33_user_content_cache_control.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_33_user_content_cache_control.json new file mode 100644 index 0000000..1f64473 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_33_user_content_cache_control.json @@ -0,0 +1,22 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "cache_control": { + "type": "ephemeral" + }, + "text": "Very long cached context", + "type": "text" + }, + { + "text": "Question: summarize.", + "type": "text" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_34_assistant_content_cache_control.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_34_assistant_content_cache_control.json new file mode 100644 index 0000000..2d3cae6 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_34_assistant_content_cache_control.json @@ -0,0 +1,26 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "ok", + "role": "user" + }, + { + "content": [ + { + "cache_control": { + "type": "ephemeral" + }, + "text": "remembered answer", + "type": "text" + } + ], + "role": "assistant" + }, + { + "content": "again", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_35_assistant_thinking_history.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_35_assistant_thinking_history.json new file mode 100644 index 0000000..a515b03 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_35_assistant_thinking_history.json @@ -0,0 +1,28 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "Hard math problem", + "role": "user" + }, + { + "content": [ + { + "signature": "sig_xyz", + "thinking": "Let me work this out step by step...", + "type": "thinking" + }, + { + "text": "The answer is 42.", + "type": "text" + } + ], + "role": "assistant" + }, + { + "content": "Why?", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_36_user_mixed_content.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_36_user_mixed_content.json new file mode 100644 index 0000000..90cb106 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_36_user_mixed_content.json @@ -0,0 +1,27 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "content": "previous tool output", + "tool_use_id": "tu_x", + "type": "tool_result" + }, + { + "text": "Now look at this image:", + "type": "text" + }, + { + "source": { + "type": "url", + "url": "https://example.com/a.png" + }, + "type": "image" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_37_empty_string_content.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_37_empty_string_content.json new file mode 100644 index 0000000..e17d95c --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_37_empty_string_content.json @@ -0,0 +1,10 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_38_complex_tool_schema.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_38_complex_tool_schema.json new file mode 100644 index 0000000..6a6f44e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_38_complex_tool_schema.json @@ -0,0 +1,60 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "ok", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tools": [ + { + "description": "Find flights", + "input_schema": { + "properties": { + "departure_date": { + "format": "date", + "type": "string" + }, + "destination": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "passengers": { + "items": { + "properties": { + "age": { + "minimum": 0, + "type": "integer" + }, + "class": { + "enum": [ + "economy", + "business", + "first" + ], + "type": "string" + } + }, + "required": [ + "age", + "class" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "origin", + "destination", + "departure_date" + ], + "type": "object" + }, + "name": "search_flights" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_39_tool_choice_auto_no_parallel.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_39_tool_choice_auto_no_parallel.json new file mode 100644 index 0000000..3570773 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_39_tool_choice_auto_no_parallel.json @@ -0,0 +1,22 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "ok", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tool_choice": { + "disable_parallel_tool_use": true, + "type": "auto" + }, + "tools": [ + { + "input_schema": { + "type": "object" + }, + "name": "f" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_40_tool_choice_none.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_40_tool_choice_none.json new file mode 100644 index 0000000..5b12609 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_40_tool_choice_none.json @@ -0,0 +1,21 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "ok", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tool_choice": { + "type": "none" + }, + "tools": [ + { + "input_schema": { + "type": "object" + }, + "name": "f" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_41_thinking_high.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_41_thinking_high.json new file mode 100644 index 0000000..012bd39 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_41_thinking_high.json @@ -0,0 +1,14 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "thinking": { + "budget_tokens": 12000, + "type": "enabled" + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_42_thinking_low.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_42_thinking_low.json new file mode 100644 index 0000000..6bb5ec9 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_42_thinking_low.json @@ -0,0 +1,14 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "thinking": { + "budget_tokens": 2000, + "type": "enabled" + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_43_stop_sequences.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_43_stop_sequences.json new file mode 100644 index 0000000..528a047 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/anthropic_43_stop_sequences.json @@ -0,0 +1,14 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "tell a joke", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "stop_sequences": [ + "END", + "STOP" + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_01_plain_user_text.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_01_plain_user_text.json new file mode 100644 index 0000000..67e735c --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_01_plain_user_text.json @@ -0,0 +1,10 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hello", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_02_system_string.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_02_system_string.json new file mode 100644 index 0000000..8d45e0e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_02_system_string.json @@ -0,0 +1,14 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "Be concise.", + "role": "system" + }, + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_03_system_blocks_with_cache_control.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_03_system_blocks_with_cache_control.json new file mode 100644 index 0000000..21f553f --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_03_system_blocks_with_cache_control.json @@ -0,0 +1,19 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "text": "rule 1", + "type": "text" + } + ], + "role": "system" + }, + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_04_multi_turn.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_04_multi_turn.json new file mode 100644 index 0000000..35ffcc0 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_04_multi_turn.json @@ -0,0 +1,19 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + }, + { + "content": "hello!", + "role": "assistant", + "thinking_blocks": null + }, + { + "content": "ok", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_05_user_image_base64.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_05_user_image_base64.json new file mode 100644 index 0000000..f6bf7b2 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_05_user_image_base64.json @@ -0,0 +1,21 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "image_url": { + "url": "data:image/png;base64,AAAA" + }, + "type": "image_url" + }, + { + "text": "what is this?", + "type": "text" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_06_user_image_url.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_06_user_image_url.json new file mode 100644 index 0000000..12aebdf --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_06_user_image_url.json @@ -0,0 +1,17 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "image_url": { + "url": "https://example.com/x.png" + }, + "type": "image_url" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_07_assistant_single_tool_use.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_07_assistant_single_tool_use.json new file mode 100644 index 0000000..64feb7a --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_07_assistant_single_tool_use.json @@ -0,0 +1,21 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": null, + "role": "assistant", + "thinking_blocks": null, + "tool_calls": [ + { + "function": { + "arguments": "{\"city\": \"Paris\"}", + "name": "get_weather" + }, + "id": "toolu_1", + "type": "function" + } + ] + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_08_assistant_two_parallel_tool_uses.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_08_assistant_two_parallel_tool_uses.json new file mode 100644 index 0000000..5655ab5 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_08_assistant_two_parallel_tool_uses.json @@ -0,0 +1,29 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": null, + "role": "assistant", + "thinking_blocks": null, + "tool_calls": [ + { + "function": { + "arguments": "{\"a\": 1}", + "name": "f1" + }, + "id": "tu_a", + "type": "function" + }, + { + "function": { + "arguments": "{\"b\": 2}", + "name": "f2" + }, + "id": "tu_b", + "type": "function" + } + ] + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_09_user_single_tool_result.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_09_user_single_tool_result.json new file mode 100644 index 0000000..c694e6e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_09_user_single_tool_result.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "21C", + "role": "tool", + "tool_call_id": "toolu_1" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_10_user_three_tool_results.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_10_user_three_tool_results.json new file mode 100644 index 0000000..25a36ea --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_10_user_three_tool_results.json @@ -0,0 +1,21 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "a", + "role": "tool", + "tool_call_id": "t1" + }, + { + "content": "b", + "role": "tool", + "tool_call_id": "t2" + }, + { + "content": "c", + "role": "tool", + "tool_call_id": "t3" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_11_user_tool_result_multipart.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_11_user_tool_result_multipart.json new file mode 100644 index 0000000..b1a1d5d --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_11_user_tool_result_multipart.json @@ -0,0 +1,22 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "text": "see image:", + "type": "text" + }, + { + "image_url": { + "url": "https://x/y.png" + }, + "type": "image_url" + } + ], + "role": "tool", + "tool_call_id": "t1" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_12_tools_input_schema.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_12_tools_input_schema.json new file mode 100644 index 0000000..52f6fa2 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_12_tools_input_schema.json @@ -0,0 +1,30 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tools": [ + { + "function": { + "description": "weather lookup", + "name": "get_weather", + "parameters": { + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + } + }, + "type": "function" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_13_long_tool_name.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_13_long_tool_name.json new file mode 100644 index 0000000..1cc5ea6 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_13_long_tool_name.json @@ -0,0 +1,21 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tools": [ + { + "function": { + "name": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_d929cdee", + "parameters": { + "type": "object" + } + }, + "type": "function" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_14_tool_choice_any.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_14_tool_choice_any.json new file mode 100644 index 0000000..219c784 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_14_tool_choice_any.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tool_choice": "required" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_15_tool_choice_named.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_15_tool_choice_named.json new file mode 100644 index 0000000..ded0a20 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_15_tool_choice_named.json @@ -0,0 +1,27 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tool_choice": { + "function": { + "name": "f" + }, + "type": "function" + }, + "tools": [ + { + "function": { + "name": "f", + "parameters": { + "type": "object" + } + }, + "type": "function" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_16_metadata_user_id.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_16_metadata_user_id.json new file mode 100644 index 0000000..575d027 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_16_metadata_user_id.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "user": "u-123" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_17_thinking_medium.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_17_thinking_medium.json new file mode 100644 index 0000000..a7bd87f --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_17_thinking_medium.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "reasoning_effort": "medium" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_18_top_k_dropped.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_18_top_k_dropped.json new file mode 100644 index 0000000..6d021f1 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_18_top_k_dropped.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "top_k": 20 +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_19_stream_include_usage.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_19_stream_include_usage.json new file mode 100644 index 0000000..4fa1dc6 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_19_stream_include_usage.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "stream": true +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_20_o3_mini_uses_max_completion_tokens.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_20_o3_mini_uses_max_completion_tokens.json new file mode 100644 index 0000000..725c70a --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_20_o3_mini_uses_max_completion_tokens.json @@ -0,0 +1,10 @@ +{ + "max_tokens": 200, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "o3-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_32_agent_tool_loop.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_32_agent_tool_loop.json new file mode 100644 index 0000000..262bdb2 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_32_agent_tool_loop.json @@ -0,0 +1,35 @@ +{ + "max_tokens": 200, + "messages": [ + { + "content": "What's the weather in Tokyo?", + "role": "user" + }, + { + "content": "Let me check.", + "role": "assistant", + "thinking_blocks": null, + "tool_calls": [ + { + "function": { + "arguments": "{\"city\": \"Tokyo\"}", + "name": "get_weather" + }, + "id": "toolu_w1", + "type": "function" + } + ] + }, + { + "content": "Sunny, 25C", + "role": "tool", + "tool_call_id": "toolu_w1" + }, + { + "content": "It's sunny and 25\u00b0C in Tokyo.", + "role": "assistant", + "thinking_blocks": null + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_33_user_content_cache_control.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_33_user_content_cache_control.json new file mode 100644 index 0000000..534e0ba --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_33_user_content_cache_control.json @@ -0,0 +1,19 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": [ + { + "text": "Very long cached context", + "type": "text" + }, + { + "text": "Question: summarize.", + "type": "text" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_34_assistant_content_cache_control.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_34_assistant_content_cache_control.json new file mode 100644 index 0000000..0a9de8e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_34_assistant_content_cache_control.json @@ -0,0 +1,19 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "ok", + "role": "user" + }, + { + "content": "remembered answer", + "role": "assistant", + "thinking_blocks": null + }, + { + "content": "again", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_35_assistant_thinking_history.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_35_assistant_thinking_history.json new file mode 100644 index 0000000..d5c0c99 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_35_assistant_thinking_history.json @@ -0,0 +1,26 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "Hard math problem", + "role": "user" + }, + { + "content": "The answer is 42.", + "role": "assistant", + "thinking_blocks": [ + { + "cache_control": {}, + "signature": "sig_xyz", + "thinking": "Let me work this out step by step...", + "type": "thinking" + } + ] + }, + { + "content": "Why?", + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_36_user_mixed_content.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_36_user_mixed_content.json new file mode 100644 index 0000000..4f89ebf --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_36_user_mixed_content.json @@ -0,0 +1,26 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "previous tool output", + "role": "tool", + "tool_call_id": "tu_x" + }, + { + "content": [ + { + "text": "Now look at this image:", + "type": "text" + }, + { + "image_url": { + "url": "https://example.com/a.png" + }, + "type": "image_url" + } + ], + "role": "user" + } + ], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_37_empty_string_content.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_37_empty_string_content.json new file mode 100644 index 0000000..7c9cc54 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_37_empty_string_content.json @@ -0,0 +1,5 @@ +{ + "max_tokens": 100, + "messages": [], + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_38_complex_tool_schema.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_38_complex_tool_schema.json new file mode 100644 index 0000000..e589698 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_38_complex_tool_schema.json @@ -0,0 +1,63 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "ok", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tools": [ + { + "function": { + "description": "Find flights", + "name": "search_flights", + "parameters": { + "properties": { + "departure_date": { + "format": "date", + "type": "string" + }, + "destination": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "passengers": { + "items": { + "properties": { + "age": { + "minimum": 0, + "type": "integer" + }, + "class": { + "enum": [ + "economy", + "business", + "first" + ], + "type": "string" + } + }, + "required": [ + "age", + "class" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "origin", + "destination", + "departure_date" + ], + "type": "object" + } + }, + "type": "function" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_39_tool_choice_auto_no_parallel.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_39_tool_choice_auto_no_parallel.json new file mode 100644 index 0000000..4928a6c --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_39_tool_choice_auto_no_parallel.json @@ -0,0 +1,22 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "ok", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tool_choice": "auto", + "tools": [ + { + "function": { + "name": "f", + "parameters": { + "type": "object" + } + }, + "type": "function" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_40_tool_choice_none.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_40_tool_choice_none.json new file mode 100644 index 0000000..3e013b1 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_40_tool_choice_none.json @@ -0,0 +1,22 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "ok", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "tool_choice": "none", + "tools": [ + { + "function": { + "name": "f", + "parameters": { + "type": "object" + } + }, + "type": "function" + } + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_41_thinking_high.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_41_thinking_high.json new file mode 100644 index 0000000..428c6ae --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_41_thinking_high.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "reasoning_effort": "high" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_42_thinking_low.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_42_thinking_low.json new file mode 100644 index 0000000..c8ad4fb --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_42_thinking_low.json @@ -0,0 +1,11 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "hi", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "reasoning_effort": "low" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_43_stop_sequences.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_43_stop_sequences.json new file mode 100644 index 0000000..528a047 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/openai_43_stop_sequences.json @@ -0,0 +1,14 @@ +{ + "max_tokens": 100, + "messages": [ + { + "content": "tell a joke", + "role": "user" + } + ], + "model": "gpt-4o-mini", + "stop_sequences": [ + "END", + "STOP" + ] +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_01_plain_user_text.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_01_plain_user_text.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_01_plain_user_text.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_02_system_string.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_02_system_string.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_02_system_string.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_03_system_blocks_with_cache_control.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_03_system_blocks_with_cache_control.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_03_system_blocks_with_cache_control.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_04_multi_turn.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_04_multi_turn.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_04_multi_turn.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_05_user_image_base64.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_05_user_image_base64.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_05_user_image_base64.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_06_user_image_url.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_06_user_image_url.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_06_user_image_url.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_07_assistant_single_tool_use.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_07_assistant_single_tool_use.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_07_assistant_single_tool_use.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_08_assistant_two_parallel_tool_uses.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_08_assistant_two_parallel_tool_uses.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_08_assistant_two_parallel_tool_uses.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_09_user_single_tool_result.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_09_user_single_tool_result.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_09_user_single_tool_result.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_10_user_three_tool_results.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_10_user_three_tool_results.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_10_user_three_tool_results.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_11_user_tool_result_multipart.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_11_user_tool_result_multipart.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_11_user_tool_result_multipart.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_12_tools_input_schema.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_12_tools_input_schema.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_12_tools_input_schema.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_13_long_tool_name.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_13_long_tool_name.json new file mode 100644 index 0000000..8633d4a --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_13_long_tool_name.json @@ -0,0 +1,3 @@ +{ + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_d929cdee": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_14_tool_choice_any.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_14_tool_choice_any.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_14_tool_choice_any.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_15_tool_choice_named.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_15_tool_choice_named.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_15_tool_choice_named.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_16_metadata_user_id.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_16_metadata_user_id.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_16_metadata_user_id.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_17_thinking_medium.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_17_thinking_medium.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_17_thinking_medium.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_18_top_k_dropped.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_18_top_k_dropped.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_18_top_k_dropped.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_19_stream_include_usage.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_19_stream_include_usage.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_19_stream_include_usage.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_20_o3_mini_uses_max_completion_tokens.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_20_o3_mini_uses_max_completion_tokens.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_20_o3_mini_uses_max_completion_tokens.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_32_agent_tool_loop.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_32_agent_tool_loop.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_32_agent_tool_loop.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_33_user_content_cache_control.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_33_user_content_cache_control.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_33_user_content_cache_control.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_34_assistant_content_cache_control.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_34_assistant_content_cache_control.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_34_assistant_content_cache_control.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_35_assistant_thinking_history.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_35_assistant_thinking_history.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_35_assistant_thinking_history.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_36_user_mixed_content.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_36_user_mixed_content.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_36_user_mixed_content.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_37_empty_string_content.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_37_empty_string_content.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_37_empty_string_content.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_38_complex_tool_schema.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_38_complex_tool_schema.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_38_complex_tool_schema.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_39_tool_choice_auto_no_parallel.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_39_tool_choice_auto_no_parallel.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_39_tool_choice_auto_no_parallel.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_40_tool_choice_none.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_40_tool_choice_none.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_40_tool_choice_none.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_41_thinking_high.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_41_thinking_high.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_41_thinking_high.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_42_thinking_low.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_42_thinking_low.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_42_thinking_low.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_43_stop_sequences.json b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_43_stop_sequences.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/requests/tool_map_43_stop_sequences.json @@ -0,0 +1 @@ +{} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_21_plain_text.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_21_plain_text.json new file mode 100644 index 0000000..8b6cf3f --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_21_plain_text.json @@ -0,0 +1,18 @@ +{ + "content": [ + { + "text": "hello world", + "type": "text" + } + ], + "id": "chatcmpl-abc", + "model": "gpt-4o-mini", + "role": "assistant", + "stop_reason": "end_turn", + "stop_sequence": null, + "type": "message", + "usage": { + "input_tokens": 10, + "output_tokens": 4 + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_22_empty_content.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_22_empty_content.json new file mode 100644 index 0000000..d716967 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_22_empty_content.json @@ -0,0 +1,13 @@ +{ + "content": [], + "id": "chatcmpl-1", + "model": "gpt-4o-mini", + "role": "assistant", + "stop_reason": "end_turn", + "stop_sequence": null, + "type": "message", + "usage": { + "input_tokens": 0, + "output_tokens": 0 + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_23_single_tool_call_no_text.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_23_single_tool_call_no_text.json new file mode 100644 index 0000000..988ffb3 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_23_single_tool_call_no_text.json @@ -0,0 +1,23 @@ +{ + "content": [ + { + "id": "call_1", + "input": { + "city": "Paris" + }, + "name": "get_weather", + "provider_specific_fields": null, + "type": "tool_use" + } + ], + "id": "chatcmpl-x", + "model": "gpt-4o-mini", + "role": "assistant", + "stop_reason": "tool_use", + "stop_sequence": null, + "type": "message", + "usage": { + "input_tokens": 0, + "output_tokens": 0 + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_24_multiple_tool_calls.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_24_multiple_tool_calls.json new file mode 100644 index 0000000..5ae947d --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_24_multiple_tool_calls.json @@ -0,0 +1,28 @@ +{ + "content": [ + { + "id": "c1", + "input": {}, + "name": "f", + "provider_specific_fields": null, + "type": "tool_use" + }, + { + "id": "c2", + "input": {}, + "name": "g", + "provider_specific_fields": null, + "type": "tool_use" + } + ], + "id": "chatcmpl-y", + "model": "gpt-4o-mini", + "role": "assistant", + "stop_reason": "tool_use", + "stop_sequence": null, + "type": "message", + "usage": { + "input_tokens": 0, + "output_tokens": 0 + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_25_length_max_tokens.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_25_length_max_tokens.json new file mode 100644 index 0000000..6eca1f2 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_25_length_max_tokens.json @@ -0,0 +1,18 @@ +{ + "content": [ + { + "text": "trunc", + "type": "text" + } + ], + "id": "chatcmpl-1", + "model": "gpt-4o-mini", + "role": "assistant", + "stop_reason": "max_tokens", + "stop_sequence": null, + "type": "message", + "usage": { + "input_tokens": 0, + "output_tokens": 0 + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_26_cached_tokens.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_26_cached_tokens.json new file mode 100644 index 0000000..bd3ec3f --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/anthropic_26_cached_tokens.json @@ -0,0 +1,19 @@ +{ + "content": [ + { + "text": "hi", + "type": "text" + } + ], + "id": "chatcmpl-1", + "model": "gpt-4o-mini", + "role": "assistant", + "stop_reason": "end_turn", + "stop_sequence": null, + "type": "message", + "usage": { + "cache_read_input_tokens": 32, + "input_tokens": 68, + "output_tokens": 2 + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_21_plain_text.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_21_plain_text.json new file mode 100644 index 0000000..d29db7e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_21_plain_text.json @@ -0,0 +1,4 @@ +{ + "original_model": "claude-opus-4-7", + "tool_map": {} +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_22_empty_content.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_22_empty_content.json new file mode 100644 index 0000000..d29db7e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_22_empty_content.json @@ -0,0 +1,4 @@ +{ + "original_model": "claude-opus-4-7", + "tool_map": {} +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_23_single_tool_call_no_text.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_23_single_tool_call_no_text.json new file mode 100644 index 0000000..d29db7e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_23_single_tool_call_no_text.json @@ -0,0 +1,4 @@ +{ + "original_model": "claude-opus-4-7", + "tool_map": {} +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_24_multiple_tool_calls.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_24_multiple_tool_calls.json new file mode 100644 index 0000000..d29db7e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_24_multiple_tool_calls.json @@ -0,0 +1,4 @@ +{ + "original_model": "claude-opus-4-7", + "tool_map": {} +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_25_length_max_tokens.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_25_length_max_tokens.json new file mode 100644 index 0000000..d29db7e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_25_length_max_tokens.json @@ -0,0 +1,4 @@ +{ + "original_model": "claude-opus-4-7", + "tool_map": {} +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_26_cached_tokens.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_26_cached_tokens.json new file mode 100644 index 0000000..d29db7e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/meta_26_cached_tokens.json @@ -0,0 +1,4 @@ +{ + "original_model": "claude-opus-4-7", + "tool_map": {} +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_21_plain_text.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_21_plain_text.json new file mode 100644 index 0000000..4957179 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_21_plain_text.json @@ -0,0 +1,18 @@ +{ + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "hello world", + "role": "assistant" + } + } + ], + "id": "chatcmpl-abc", + "model": "gpt-4o-mini", + "usage": { + "completion_tokens": 4, + "prompt_tokens": 10 + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_22_empty_content.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_22_empty_content.json new file mode 100644 index 0000000..2284095 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_22_empty_content.json @@ -0,0 +1,14 @@ +{ + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": null, + "role": "assistant" + } + } + ], + "id": "chatcmpl-1", + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_23_single_tool_call_no_text.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_23_single_tool_call_no_text.json new file mode 100644 index 0000000..7e58d7e --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_23_single_tool_call_no_text.json @@ -0,0 +1,24 @@ +{ + "choices": [ + { + "finish_reason": "tool_calls", + "index": 0, + "message": { + "content": null, + "role": "assistant", + "tool_calls": [ + { + "function": { + "arguments": "{\"city\":\"Paris\"}", + "name": "get_weather" + }, + "id": "call_1", + "type": "function" + } + ] + } + } + ], + "id": "chatcmpl-x", + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_24_multiple_tool_calls.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_24_multiple_tool_calls.json new file mode 100644 index 0000000..67c8575 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_24_multiple_tool_calls.json @@ -0,0 +1,32 @@ +{ + "choices": [ + { + "finish_reason": "tool_calls", + "index": 0, + "message": { + "content": null, + "role": "assistant", + "tool_calls": [ + { + "function": { + "arguments": "{}", + "name": "f" + }, + "id": "c1", + "type": "function" + }, + { + "function": { + "arguments": "{}", + "name": "g" + }, + "id": "c2", + "type": "function" + } + ] + } + } + ], + "id": "chatcmpl-y", + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_25_length_max_tokens.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_25_length_max_tokens.json new file mode 100644 index 0000000..e777bb1 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_25_length_max_tokens.json @@ -0,0 +1,14 @@ +{ + "choices": [ + { + "finish_reason": "length", + "index": 0, + "message": { + "content": "trunc", + "role": "assistant" + } + } + ], + "id": "chatcmpl-1", + "model": "gpt-4o-mini" +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_26_cached_tokens.json b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_26_cached_tokens.json new file mode 100644 index 0000000..e1c710a --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/responses/openai_26_cached_tokens.json @@ -0,0 +1,21 @@ +{ + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "hi", + "role": "assistant" + } + } + ], + "id": "chatcmpl-1", + "model": "gpt-4o-mini", + "usage": { + "completion_tokens": 2, + "prompt_tokens": 100, + "prompt_tokens_details": { + "cached_tokens": 32 + } + } +} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_27_text_only.jsonl b/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_27_text_only.jsonl new file mode 100644 index 0000000..fc69873 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_27_text_only.jsonl @@ -0,0 +1,7 @@ +{"message": {"content": [], "id": "msg_168c6410-02b2-495c-ab0a-d9e294f19e40", "model": "claude-opus-4-7", "role": "assistant", "stop_reason": null, "stop_sequence": null, "type": "message", "usage": {"cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "input_tokens": 0, "output_tokens": 0}}, "type": "message_start"} +{"content_block": {"text": "", "type": "text"}, "index": 0, "type": "content_block_start"} +{"delta": {"text": "hel", "type": "text_delta"}, "index": 0, "type": "content_block_delta"} +{"delta": {"text": "lo", "type": "text_delta"}, "index": 0, "type": "content_block_delta"} +{"index": 0, "type": "content_block_stop"} +{"delta": {"stop_reason": "end_turn"}, "type": "message_delta", "usage": {"input_tokens": 0, "output_tokens": 0}} +{"type": "message_stop"} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_28_single_tool_call_fragments.jsonl b/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_28_single_tool_call_fragments.jsonl new file mode 100644 index 0000000..389c1af --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_28_single_tool_call_fragments.jsonl @@ -0,0 +1,9 @@ +{"message": {"content": [], "id": "msg_444b0173-71c5-4703-b1e2-970510339fe2", "model": "claude-opus-4-7", "role": "assistant", "stop_reason": null, "stop_sequence": null, "type": "message", "usage": {"cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "input_tokens": 0, "output_tokens": 0}}, "type": "message_start"} +{"content_block": {"text": "", "type": "text"}, "index": 0, "type": "content_block_start"} +{"index": 0, "type": "content_block_stop"} +{"content_block": {"id": "call_1", "input": {}, "name": "get_weather", "type": "tool_use"}, "index": 1, "type": "content_block_start"} +{"delta": {"partial_json": "{\"city\":", "type": "input_json_delta"}, "index": 1, "type": "content_block_delta"} +{"delta": {"partial_json": "\"Paris\"}", "type": "input_json_delta"}, "index": 1, "type": "content_block_delta"} +{"index": 1, "type": "content_block_stop"} +{"delta": {"stop_reason": "tool_use"}, "type": "message_delta", "usage": {"input_tokens": 0, "output_tokens": 0}} +{"type": "message_stop"} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_29_two_parallel_tool_calls.jsonl b/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_29_two_parallel_tool_calls.jsonl new file mode 100644 index 0000000..d820c14 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_29_two_parallel_tool_calls.jsonl @@ -0,0 +1,8 @@ +{"message": {"content": [], "id": "msg_03550e46-97fc-4419-a073-81b6caf142fa", "model": "claude-opus-4-7", "role": "assistant", "stop_reason": null, "stop_sequence": null, "type": "message", "usage": {"cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "input_tokens": 0, "output_tokens": 0}}, "type": "message_start"} +{"content_block": {"text": "", "type": "text"}, "index": 0, "type": "content_block_start"} +{"index": 0, "type": "content_block_stop"} +{"content_block": {"id": "a", "input": {}, "name": "f", "type": "tool_use"}, "index": 1, "type": "content_block_start"} +{"delta": {"partial_json": "{}{}", "type": "input_json_delta"}, "index": 1, "type": "content_block_delta"} +{"index": 1, "type": "content_block_stop"} +{"delta": {"stop_reason": "tool_use"}, "type": "message_delta", "usage": {"input_tokens": 0, "output_tokens": 0}} +{"type": "message_stop"} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_30_stream_ends_without_finish_reason.jsonl b/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_30_stream_ends_without_finish_reason.jsonl new file mode 100644 index 0000000..c154931 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_30_stream_ends_without_finish_reason.jsonl @@ -0,0 +1,4 @@ +{"message": {"content": [], "id": "msg_73c3bbe2-d1f6-49f0-a0dc-43f0a829acec", "model": "claude-opus-4-7", "role": "assistant", "stop_reason": null, "stop_sequence": null, "type": "message", "usage": {"cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "input_tokens": 0, "output_tokens": 0}}, "type": "message_start"} +{"content_block": {"text": "", "type": "text"}, "index": 0, "type": "content_block_start"} +{"delta": {"text": "partial", "type": "text_delta"}, "index": 0, "type": "content_block_delta"} +{"type": "message_stop"} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_31_reasoning_then_text.jsonl b/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_31_reasoning_then_text.jsonl new file mode 100644 index 0000000..7ff620c --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/streams/anthropic_31_reasoning_then_text.jsonl @@ -0,0 +1,7 @@ +{"message": {"content": [], "id": "msg_ea5061d1-e15f-4204-864f-1d0cddc75b9c", "model": "claude-opus-4-7", "role": "assistant", "stop_reason": null, "stop_sequence": null, "type": "message", "usage": {"cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "input_tokens": 0, "output_tokens": 0}}, "type": "message_start"} +{"content_block": {"text": "", "type": "text"}, "index": 0, "type": "content_block_start"} +{"delta": {"thinking": "let me think...", "type": "thinking_delta"}, "index": 0, "type": "content_block_delta"} +{"delta": {"text": "Done.", "type": "text_delta"}, "index": 0, "type": "content_block_delta"} +{"index": 0, "type": "content_block_stop"} +{"delta": {"stop_reason": "end_turn"}, "type": "message_delta", "usage": {"input_tokens": 0, "output_tokens": 0}} +{"type": "message_stop"} diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_27_text_only.sse b/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_27_text_only.sse new file mode 100644 index 0000000..091c1f6 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_27_text_only.sse @@ -0,0 +1,8 @@ +data: {"id":"chatcmpl-1","choices":[{"index":0,"delta":{"role":"assistant","content":"hel"}}]} + +data: {"id":"chatcmpl-1","choices":[{"index":0,"delta":{"content":"lo"}}]} + +data: {"id":"chatcmpl-1","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} + +data: [DONE] + diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_28_single_tool_call_fragments.sse b/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_28_single_tool_call_fragments.sse new file mode 100644 index 0000000..9d9d1e6 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_28_single_tool_call_fragments.sse @@ -0,0 +1,8 @@ +data: {"id":"chatcmpl-x","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"get_weather","arguments":""}}]}}]} + +data: {"id":"chatcmpl-x","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"arguments":"{\"city\":"}}]}}]} + +data: {"id":"chatcmpl-x","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"arguments":"\"Paris\"}"}}]}}]} + +data: {"id":"chatcmpl-x","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]} + diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_29_two_parallel_tool_calls.sse b/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_29_two_parallel_tool_calls.sse new file mode 100644 index 0000000..9a8e867 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_29_two_parallel_tool_calls.sse @@ -0,0 +1,4 @@ +data: {"id":"chatcmpl-y","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"a","type":"function","function":{"name":"f","arguments":"{}"}},{"index":1,"id":"b","type":"function","function":{"name":"g","arguments":"{}"}}]}}]} + +data: {"id":"chatcmpl-y","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]} + diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_30_stream_ends_without_finish_reason.sse b/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_30_stream_ends_without_finish_reason.sse new file mode 100644 index 0000000..5bb3cf5 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_30_stream_ends_without_finish_reason.sse @@ -0,0 +1,2 @@ +data: {"id":"chatcmpl-z","choices":[{"index":0,"delta":{"role":"assistant","content":"partial"}}]} + diff --git a/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_31_reasoning_then_text.sse b/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_31_reasoning_then_text.sse new file mode 100644 index 0000000..5581fe5 --- /dev/null +++ b/plugins/abridge/tests/fixtures/anthropic_openai/streams/openai_31_reasoning_then_text.sse @@ -0,0 +1,6 @@ +data: {"id":"chatcmpl-r","choices":[{"index":0,"delta":{"reasoning_content":"let me think..."}}]} + +data: {"id":"chatcmpl-r","choices":[{"index":0,"delta":{"content":"Done."}}]} + +data: {"id":"chatcmpl-r","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} + diff --git a/plugins/abridge/tests/test_anthropic_transform_fixtures.py b/plugins/abridge/tests/test_anthropic_transform_fixtures.py new file mode 100644 index 0000000..f576375 --- /dev/null +++ b/plugins/abridge/tests/test_anthropic_transform_fixtures.py @@ -0,0 +1,392 @@ +"""Golden-fixture parity tests for the Anthropic <-> OpenAI transforms. + +Runs the pure converters in `agentix.bridge.clients._anthropic_transforms` +against the cc_convert conversion corpus vendored under +`fixtures/anthropic_openai/` (see the README there for origin, license, +and how the goldens were produced). + +Three directions: + +- requests: `anthropic_.json` -> `anthropic_messages_to_openai` + -> compare to `openai_.json`. +- responses: `openai_.json` -> `openai_to_anthropic_messages` + -> compare to `anthropic_.json` (the generated message id is + normalized away — see `_normalized_anthropic_response`). +- streams: the Python path has no incremental OpenAI-SSE translator — + the abridge client makes a *non-streaming* upstream call and replays + the finished response as Anthropic SSE via `anthropic_sse`. Chunk-by- + chunk event fidelity is therefore untestable; what IS testable is + final-assembled-response equivalence: assemble the OpenAI chunks into + a completed response (test helper), convert it, render it back out + through `anthropic_sse`, re-assemble our own event stream, and compare + that against the message assembled from the golden event stream. + +Every known Python-path gap is marked `xfail(strict=True)` with a +precise reason, so the xfail table below doubles as the prioritized +gap list for the transforms. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +import pytest +from agentix.bridge.clients._anthropic_transforms import ( + anthropic_messages_to_openai, + anthropic_sse, + openai_to_anthropic_messages, +) + +FIXTURES = Path(__file__).parent / "fixtures" / "anthropic_openai" + +# ── known Python-path gaps (case -> xfail reason) ────────────────────────── + +REQUEST_GAPS: dict[str, str] = { + "05_user_image_base64": "base64 image blocks in user content are dropped (no image_url part is emitted)", + "06_user_image_url": "a user message whose content is only an image block is dropped entirely", + "11_user_tool_result_multipart": "image parts inside a multipart tool_result are dropped (only text is forwarded)", + "13_long_tool_name": ">64-char tool names are not sanitized (no truncate+hash rename, no tool_map)", + "14_tool_choice_any": "tool_choice {type: any} is dropped (should map to OpenAI tool_choice 'required')", + "15_tool_choice_named": "tool_choice {type: tool, name} is dropped (should map to a named function tool_choice)", + "16_metadata_user_id": "metadata.user_id is dropped (should map to the OpenAI 'user' field)", + "17_thinking_medium": "thinking {budget_tokens: 5000} is dropped (no reasoning_effort='medium' mapping)", + "18_top_k_dropped": "top_k is dropped; the oracle passes it through for OpenAI-compatible upstreams", + "19_stream_include_usage": "stream=true is dropped (the adapter always makes a non-streaming upstream call)", + "35_assistant_thinking_history": "assistant thinking blocks are dropped instead of forwarded as thinking_blocks", + "36_user_mixed_content": "the image part of mixed text+image user content is dropped", + "37_empty_string_content": "a message with empty-string content is forwarded; the oracle drops it", + "39_tool_choice_auto_no_parallel": "tool_choice {type: auto} is dropped (should map to OpenAI tool_choice 'auto')", + "40_tool_choice_none": "tool_choice {type: none} is dropped (should map to OpenAI tool_choice 'none')", + "41_thinking_high": "thinking with a large budget is dropped (no reasoning_effort='high' mapping)", + "42_thinking_low": "thinking {budget_tokens: 2000} is dropped (no reasoning_effort='low' mapping)", + "43_stop_sequences": "stop_sequences is dropped (never mapped to an OpenAI stop parameter)", +} + +RESPONSE_GAPS: dict[str, str] = { + "22_empty_content": "an empty assistant message becomes [{type: text, text: ''}] instead of content: []", + "26_cached_tokens": ( + "usage.prompt_tokens_details.cached_tokens is ignored: no cache_read_input_tokens, " + "and input_tokens is not reduced by the cached share" + ), +} + +STREAM_GAPS: dict[str, str] = { + "30_stream_ends_without_finish_reason": ( + "a stream aborted without finish_reason is reported as stop_reason 'end_turn'; " + "the oracle keeps stop_reason null and emits no message_delta" + ), + "31_reasoning_then_text": "reasoning_content deltas are dropped; no thinking block is produced", +} + +# Cases where the golden itself is not a valid oracle (upstream artifact, +# not a Python gap) — skipped, not xfailed, so the xfail list stays a pure +# gap list. +STREAM_SKIPS: dict[str, str] = { + "29_two_parallel_tool_calls": ( + "the golden merges both parallel tool calls into ONE content block with concatenated " + "partial_json '{}{}' (LiteLLM stream-translator artifact); the Python one-shot path emits " + "two separate tool_use blocks, so there is no valid oracle to compare against" + ), +} + + +def _cases(directory: str, pattern: str, gaps: dict[str, str], skips: dict[str, str] | None = None) -> list[Any]: + params: list[Any] = [] + for path in sorted((FIXTURES / directory).glob(pattern)): + case = path.stem.split("_", 1)[1] + marks: list[pytest.MarkDecorator] = [] + if case in gaps: + marks.append(pytest.mark.xfail(strict=True, reason=gaps[case])) + if skips and case in skips: + marks.append(pytest.mark.skip(reason=skips[case])) + params.append(pytest.param(case, marks=marks, id=case)) + return params + + +def _load(directory: str, name: str) -> dict[str, Any]: + return json.loads((FIXTURES / directory / name).read_text()) + + +# ── comparison normalization ──────────────────────────────────────────────── + + +def _drop_none(value: Any) -> Any: + """Recursively drop dict entries whose value is None. + + JSON `null` and an absent key are interchangeable on both wire + protocols, and the goldens carry LiteLLM serialization artifacts + that spell absence as an explicit null (`"thinking_blocks": null`, + `"provider_specific_fields": null`, `"content": null`). This only + equates null with absence — a null where a real value is expected + (or vice versa) still fails. + """ + if isinstance(value, dict): + return {k: _drop_none(v) for k, v in value.items() if v is not None} + if isinstance(value, list): + return [_drop_none(item) for item in value] + return value + + +def _canonical_message_content(content: Any) -> Any: + """Fold a text-only content part list into a plain string. + + OpenAI treats `content: "s"` and `content: [{"type": "text", ...}]` + as equivalent. The goldens keep Anthropic's block boundaries (those + only carry cache_control, which has no OpenAI equivalent) while the + Python transform joins text blocks with a newline — so both sides + are folded with the same newline join. The fold applies ONLY when + every part is a text part: any non-text part (e.g. image_url) keeps + the list shape, so dropped images still fail loudly. + """ + if isinstance(content, list) and all(isinstance(part, dict) and part.get("type") == "text" for part in content): + return "\n".join(str(part.get("text", "")) for part in content) + return content + + +def _normalized_openai_request(body: dict[str, Any]) -> dict[str, Any]: + out: dict[str, Any] = _drop_none(body) + for message in out.get("messages", []): + if "content" in message: + message["content"] = _canonical_message_content(message["content"]) + for tool in out.get("tools", []): + function = tool.get("function", {}) + # The transform always emits `description` ("" when the Anthropic + # tool declares none); the golden omits the key entirely. An empty + # description carries no information, so "" == absent here. + if function.get("description") == "": + del function["description"] + return out + + +def _normalized_anthropic_response(body: dict[str, Any]) -> dict[str, Any]: + out: dict[str, Any] = _drop_none(body) + # The transform mints a fresh `msg_` message id while the golden + # echoes the upstream OpenAI id — both are generated values, so the id + # (and only the id) is excluded from the comparison. + out.pop("id", None) + return out + + +# ── requests: Anthropic -> OpenAI ─────────────────────────────────────────── + + +@pytest.mark.parametrize("case", _cases("requests", "anthropic_*.json", REQUEST_GAPS)) +def test_request_transform_matches_golden(case: str) -> None: + anthropic_body = _load("requests", f"anthropic_{case}.json") + expected = _load("requests", f"openai_{case}.json") + + actual = anthropic_messages_to_openai(anthropic_body) + + assert _normalized_openai_request(actual) == _normalized_openai_request(expected) + + +# ── responses: OpenAI -> Anthropic ────────────────────────────────────────── + + +@pytest.mark.parametrize("case", _cases("responses", "openai_*.json", RESPONSE_GAPS)) +def test_response_transform_matches_golden(case: str) -> None: + openai_body = _load("responses", f"openai_{case}.json") + expected = _load("responses", f"anthropic_{case}.json") + meta = _load("responses", f"meta_{case}.json") + # The Python path has no tool_map (reverse name-mapping) support; every + # golden in the corpus was produced with an empty map, so nothing is + # being papered over. A future non-empty map must not pass silently. + assert meta["tool_map"] == {} + + # `response_model` is caller-supplied; the goldens echo the upstream + # OpenAI model (meta.original_model is not used by the reference + # translator's output), so pass the upstream model through. + actual = openai_to_anthropic_messages(openai_body, response_model=openai_body["model"]) + + assert _normalized_anthropic_response(actual) == _normalized_anthropic_response(expected) + + +# ── streams: final assembled response equivalence ─────────────────────────── + + +def _parse_openai_sse(text: str) -> list[dict[str, Any]]: + chunks: list[dict[str, Any]] = [] + for line in text.splitlines(): + if not line.startswith("data: "): + continue + payload = line[len("data: ") :].strip() + if payload == "[DONE]": + continue + chunks.append(json.loads(payload)) + return chunks + + +def _assemble_openai_stream(chunks: list[dict[str, Any]]) -> dict[str, Any]: + """Merge Chat Completions chunks into one completed response. + + Test-side helper standing in for the non-streaming upstream call the + adapter actually makes: content and per-tool-call argument fragments + are concatenated, tool calls are keyed by their delta `index`, and the + last non-null finish_reason wins. `reasoning_content` fragments are + preserved so the reasoning gap fails on the transform, not on the + assembly. + """ + response_id: str | None = None + model: str | None = None + usage: dict[str, Any] | None = None + finish_reason: str | None = None + text_parts: list[str] = [] + reasoning_parts: list[str] = [] + tool_calls: dict[int, dict[str, Any]] = {} + + for chunk in chunks: + response_id = chunk.get("id") or response_id + model = chunk.get("model") or model + usage = chunk.get("usage") or usage + for choice in chunk.get("choices") or []: + delta = choice.get("delta") or {} + if delta.get("content"): + text_parts.append(delta["content"]) + if delta.get("reasoning_content"): + reasoning_parts.append(delta["reasoning_content"]) + for fragment in delta.get("tool_calls") or []: + slot = tool_calls.setdefault( + fragment.get("index", 0), + {"id": None, "type": "function", "function": {"name": "", "arguments": ""}}, + ) + if fragment.get("id"): + slot["id"] = fragment["id"] + function = fragment.get("function") or {} + if function.get("name"): + slot["function"]["name"] = function["name"] + if function.get("arguments"): + slot["function"]["arguments"] += function["arguments"] + if choice.get("finish_reason"): + finish_reason = choice["finish_reason"] + + message: dict[str, Any] = {"role": "assistant", "content": "".join(text_parts) or None} + if reasoning_parts: + message["reasoning_content"] = "".join(reasoning_parts) + if tool_calls: + message["tool_calls"] = [tool_calls[index] for index in sorted(tool_calls)] + + completed: dict[str, Any] = { + "id": response_id, + "model": model, + "choices": [{"index": 0, "message": message, "finish_reason": finish_reason}], + } + if usage: + completed["usage"] = usage + return completed + + +def _parse_anthropic_sse(blob: bytes) -> list[dict[str, Any]]: + events: list[dict[str, Any]] = [] + for line in blob.decode().splitlines(): + if line.startswith("data: "): + events.append(json.loads(line[len("data: ") :])) + return events + + +def _assemble_anthropic_stream(events: list[dict[str, Any]]) -> dict[str, Any]: + """Assemble an Anthropic event stream into its final message shape. + + Returns `{"content": [...], "stop_reason": ...}` — the semantic core + of the stream. Ids, model, and usage are deliberately excluded: the + goldens use a freshly generated `msg_` id, the caller-level + `original_model`, and all-zero usage, none of which the body-level + transforms control. Two normalizations: + + - empty text blocks are dropped (the golden stream translator opens + an unconditional empty text block before every message — a LiteLLM + artifact carrying no content); + - `thinking_delta` fragments become a separate thinking part even + when the golden interleaves them inside a text block, so thinking + content is compared by value, not by block layout. + """ + blocks: dict[int, dict[str, Any]] = {} + stop_reason: str | None = None + for event in events: + event_type = event.get("type") + if event_type == "content_block_start": + start = dict(event.get("content_block") or {}) + blocks[event["index"]] = { + "type": start.get("type", "text"), + "id": start.get("id"), + "name": start.get("name"), + "text": start.get("text", ""), + "thinking": start.get("thinking", ""), + "partial_json": "", + } + elif event_type == "content_block_delta": + block = blocks.setdefault( + event["index"], + {"type": "text", "id": None, "name": None, "text": "", "thinking": "", "partial_json": ""}, + ) + delta = event.get("delta") or {} + delta_type = delta.get("type") + if delta_type == "text_delta": + block["text"] += delta.get("text", "") + elif delta_type == "thinking_delta": + block["thinking"] += delta.get("thinking", "") + elif delta_type == "input_json_delta": + block["partial_json"] += delta.get("partial_json", "") + elif event_type == "message_delta": + delta = event.get("delta") or {} + if delta.get("stop_reason"): + stop_reason = delta["stop_reason"] + + content: list[dict[str, Any]] = [] + for index in sorted(blocks): + block = blocks[index] + if block["thinking"]: + content.append({"type": "thinking", "thinking": block["thinking"]}) + if block["type"] == "tool_use": + raw = block["partial_json"] or "{}" + try: + tool_input: Any = json.loads(raw) + except json.JSONDecodeError: + tool_input = raw + content.append({"type": "tool_use", "id": block["id"], "name": block["name"], "input": tool_input}) + elif block["text"]: + content.append({"type": "text", "text": block["text"]}) + return {"content": content, "stop_reason": stop_reason} + + +@pytest.mark.parametrize("case", _cases("streams", "openai_*.sse", STREAM_GAPS, STREAM_SKIPS)) +def test_stream_final_assembly_matches_golden(case: str) -> None: + chunks = _parse_openai_sse((FIXTURES / "streams" / f"openai_{case}.sse").read_text()) + golden_events = [ + json.loads(line) + for line in (FIXTURES / "streams" / f"anthropic_{case}.jsonl").read_text().splitlines() + if line.strip() + ] + + completed = _assemble_openai_stream(chunks) + # The stream goldens carry the original (Anthropic-side) model name at + # message_start; it is caller-supplied, exactly like `response_model`. + body = openai_to_anthropic_messages(completed, response_model="claude-opus-4-7") + actual_events = _parse_anthropic_sse(anthropic_sse(body)) + + assert _assemble_anthropic_stream(actual_events) == _assemble_anthropic_stream(golden_events) + + +# ── corpus integrity ──────────────────────────────────────────────────────── + + +def test_fixture_corpus_is_complete() -> None: + """Guard against a botched fixture copy: every case must be a full + triple/pair, and the corpus must have the expected size.""" + request_cases = {p.stem.split("_", 1)[1] for p in (FIXTURES / "requests").glob("anthropic_*.json")} + assert len(request_cases) == 32 + for case in request_cases: + assert (FIXTURES / "requests" / f"openai_{case}.json").is_file() + assert (FIXTURES / "requests" / f"tool_map_{case}.json").is_file() + + response_cases = {p.stem.split("_", 1)[1] for p in (FIXTURES / "responses").glob("openai_*.json")} + assert len(response_cases) == 6 + for case in response_cases: + assert (FIXTURES / "responses" / f"anthropic_{case}.json").is_file() + assert (FIXTURES / "responses" / f"meta_{case}.json").is_file() + + stream_cases = {p.stem.split("_", 1)[1] for p in (FIXTURES / "streams").glob("openai_*.sse")} + assert len(stream_cases) == 5 + for case in stream_cases: + assert (FIXTURES / "streams" / f"anthropic_{case}.jsonl").is_file()