llm_gateway 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.pi/skills/live-provider-testing/SKILL.md +183 -0
  3. data/.pi/skills/options-development/SKILL.md +131 -0
  4. data/CHANGELOG.md +43 -0
  5. data/README.md +559 -185
  6. data/Rakefile +2 -2
  7. data/docs/migration-guide.md +135 -0
  8. data/lib/llm_gateway/adapters/adapter.rb +140 -0
  9. data/lib/llm_gateway/adapters/anthropic/acts_like_messages.rb +21 -0
  10. data/lib/llm_gateway/adapters/anthropic/input_mapper.rb +137 -0
  11. data/lib/llm_gateway/adapters/anthropic/messages_adapter.rb +19 -0
  12. data/lib/llm_gateway/adapters/anthropic/output_mapper.rb +17 -0
  13. data/lib/llm_gateway/adapters/anthropic/stream_mapper.rb +95 -0
  14. data/lib/llm_gateway/adapters/anthropic_option_mapper.rb +95 -0
  15. data/lib/llm_gateway/adapters/groq/chat_completions_adapter.rb +48 -0
  16. data/lib/llm_gateway/adapters/groq/input_mapper.rb +32 -6
  17. data/lib/llm_gateway/adapters/groq/option_mapper.rb +112 -0
  18. data/lib/llm_gateway/adapters/input_message_sanitizer.rb +93 -0
  19. data/lib/llm_gateway/adapters/normalized_stream_accumulator.rb +275 -0
  20. data/lib/llm_gateway/adapters/openai/acts_like_chat_completions.rb +20 -0
  21. data/lib/llm_gateway/adapters/openai/acts_like_responses.rb +25 -0
  22. data/lib/llm_gateway/adapters/openai/chat_completions/input_mapper.rb +168 -0
  23. data/lib/llm_gateway/adapters/openai/chat_completions/input_message_sanitizer.rb +65 -0
  24. data/lib/llm_gateway/adapters/openai/chat_completions/option_mapper.rb +129 -0
  25. data/lib/llm_gateway/adapters/openai/chat_completions/stream_mapper.rb +241 -0
  26. data/lib/llm_gateway/adapters/openai/chat_completions_adapter.rb +19 -0
  27. data/lib/llm_gateway/adapters/{open_ai → openai}/file_output_mapper.rb +1 -1
  28. data/lib/llm_gateway/adapters/openai/prompt_cache_option_mapper.rb +39 -0
  29. data/lib/llm_gateway/adapters/openai/responses/input_mapper.rb +166 -0
  30. data/lib/llm_gateway/adapters/openai/responses/option_mapper.rb +130 -0
  31. data/lib/llm_gateway/adapters/openai/responses/stream_mapper.rb +150 -0
  32. data/lib/llm_gateway/adapters/openai/responses_adapter.rb +19 -0
  33. data/lib/llm_gateway/adapters/openai_codex/input_mapper.rb +206 -0
  34. data/lib/llm_gateway/adapters/openai_codex/option_mapper.rb +28 -0
  35. data/lib/llm_gateway/adapters/openai_codex/responses_adapter.rb +33 -0
  36. data/lib/llm_gateway/adapters/option_mapper.rb +13 -0
  37. data/lib/llm_gateway/adapters/stream_mapper.rb +50 -0
  38. data/lib/llm_gateway/adapters/structs.rb +145 -0
  39. data/lib/llm_gateway/base_client.rb +62 -1
  40. data/lib/llm_gateway/client.rb +18 -158
  41. data/lib/llm_gateway/clients/anthropic.rb +167 -0
  42. data/lib/llm_gateway/clients/claude_code/oauth_flow.rb +162 -0
  43. data/lib/llm_gateway/clients/claude_code/token_manager.rb +112 -0
  44. data/lib/llm_gateway/clients/groq.rb +66 -0
  45. data/lib/llm_gateway/clients/openai.rb +208 -0
  46. data/lib/llm_gateway/clients/openai_codex/oauth_flow.rb +258 -0
  47. data/lib/llm_gateway/clients/openai_codex/token_manager.rb +71 -0
  48. data/lib/llm_gateway/errors.rb +21 -0
  49. data/lib/llm_gateway/prompt.rb +12 -1
  50. data/lib/llm_gateway/provider_registry.rb +37 -0
  51. data/lib/llm_gateway/version.rb +1 -1
  52. data/lib/llm_gateway.rb +162 -17
  53. data/scripts/create_anthropic_credentials.rb +106 -0
  54. data/scripts/create_openai_codex_credentials.rb +116 -0
  55. metadata +60 -27
  56. data/lib/llm_gateway/adapters/claude/bidirectional_message_mapper.rb +0 -83
  57. data/lib/llm_gateway/adapters/claude/client.rb +0 -60
  58. data/lib/llm_gateway/adapters/claude/input_mapper.rb +0 -57
  59. data/lib/llm_gateway/adapters/claude/output_mapper.rb +0 -50
  60. data/lib/llm_gateway/adapters/groq/bidirectional_message_mapper.rb +0 -18
  61. data/lib/llm_gateway/adapters/groq/client.rb +0 -58
  62. data/lib/llm_gateway/adapters/groq/output_mapper.rb +0 -10
  63. data/lib/llm_gateway/adapters/open_ai/chat_completions/bidirectional_message_mapper.rb +0 -103
  64. data/lib/llm_gateway/adapters/open_ai/chat_completions/input_mapper.rb +0 -110
  65. data/lib/llm_gateway/adapters/open_ai/chat_completions/output_mapper.rb +0 -40
  66. data/lib/llm_gateway/adapters/open_ai/client.rb +0 -80
  67. data/lib/llm_gateway/adapters/open_ai/responses/bidirectional_message_mapper.rb +0 -72
  68. data/lib/llm_gateway/adapters/open_ai/responses/input_mapper.rb +0 -62
  69. data/lib/llm_gateway/adapters/open_ai/responses/output_mapper.rb +0 -47
  70. data/sample/claude_code_clone/agent.rb +0 -65
  71. data/sample/claude_code_clone/claude_code_clone.rb +0 -40
  72. data/sample/claude_code_clone/prompt.rb +0 -79
  73. data/sample/claude_code_clone/run.rb +0 -47
  74. data/sample/claude_code_clone/tools/bash_tool.rb +0 -54
  75. data/sample/claude_code_clone/tools/edit_tool.rb +0 -61
  76. data/sample/claude_code_clone/tools/grep_tool.rb +0 -113
  77. data/sample/claude_code_clone/tools/read_tool.rb +0 -61
  78. data/sample/claude_code_clone/tools/todowrite_tool.rb +0 -98
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 972dac306c8d8f59b41c462f0133d0ce415e689515fbd84d5503541a9fee6f93
4
- data.tar.gz: 6014254f858af1b83906b29950811e2ce84940f0ff49301a9b36dd19f592b7a8
3
+ metadata.gz: ce9b9e4f2137a73474b1ed5f0876d8b1bf6185666ab8c756c3a3f67e99e9d86e
4
+ data.tar.gz: 5735832e4bd57946ffc0a251c5a3a0861af0fc12a989456e1f877675e08846ba
5
5
  SHA512:
6
- metadata.gz: 388573aa9afe19a075c561f17d5e4a6b8e550de25b73c3e6d9583403096f1c26f13667055d5920408c2c06f9add888af81a0bf34434762b8ece4a58de32ee3c3
7
- data.tar.gz: 42d0b37c296ee058109cb38daaeefc269ce128a39bfbb3b89745eb734653a2828ec8c54f8acd1a12242aa03d1706304507c627074a53e4e6cddc7eb27324d69d
6
+ metadata.gz: afd52f4ead29acf7a612a06456e203e295534d2cb2275a7ea99be5840da39a821f4727402687bd9c3696bc0081c12f09861aa1b7ad135f986054625c68341422
7
+ data.tar.gz: 9c033b13f91e9315aadedca98cb61e32c01584a4e6cbe4f05b3782eb84287d24e50dbbc5e1bc127d14bc362d2aac262a589b810614a01d432d02f72acb3013a7
@@ -0,0 +1,183 @@
1
+ ---
2
+ name: live-provider-testing
3
+ description: Use when adding or updating llm_gateway live provider integration tests, stream tests, recorded handoff fixtures, or handoff tests that replay outputs across provider/model pairs.
4
+ ---
5
+
6
+ # Live Provider Testing
7
+
8
+ Use this skill when working on live integration tests under `test/integration/live/`, especially tests that validate multiple provider/model pairs, record final stream outputs, or create handoff tests from those recordings.
9
+
10
+ ## Core pattern
11
+
12
+ Live provider tests use a shared `PAIRS` constant and generate one test per provider/model pair.
13
+
14
+ ```ruby
15
+ PAIRS = [
16
+ { provider: "openai_apikey_completions", model: "gpt-5.1" },
17
+ { provider: "anthropic_apikey_messages", model: "claude-sonnet-4-20250514" },
18
+ { provider: "openai_apikey_responses", model: "gpt-5.4" },
19
+ { provider: "anthropic_oauth_messages", model: "claude-sonnet-4-20250514" },
20
+ { provider: "openai_oauth_codex", model: "gpt-5.4" }
21
+ ].freeze
22
+ ```
23
+
24
+ Define tests by iterating over `PAIRS`, not by manually repeating calls:
25
+
26
+ ```ruby
27
+ def self.define_stream_tests_for(provider:, model:)
28
+ test "live_text_streaming_#{provider}_#{model}" do
29
+ with_vcr_adapter(provider:, model:) do |adapter|
30
+ response = basic_streaming_text_test(adapter)
31
+ record_live_handoff_result(test_file: __FILE__, provider:, model:, result: response)
32
+ end
33
+ end
34
+ end
35
+
36
+ PAIRS.each do |pair|
37
+ define_stream_tests_for(provider: pair[:provider], model: pair[:model])
38
+ end
39
+ ```
40
+
41
+ Always include `LiveTestHelper` and reset configuration in teardown:
42
+
43
+ ```ruby
44
+ include LiveTestHelper
45
+
46
+ def teardown
47
+ LlmGateway.reset_configuration!
48
+ end
49
+ ```
50
+
51
+ ## Running adapters
52
+
53
+ Use `with_vcr_adapter(provider:, model:)` for live/VCR-backed provider tests. It handles:
54
+
55
+ - provider configuration
56
+ - API key and OAuth credential lookup
57
+ - VCR cassette naming
58
+ - replay tokens for OAuth providers
59
+ - authentication skips
60
+
61
+ Do not construct provider clients directly inside live tests unless the test specifically targets client construction.
62
+
63
+ ## Stream tests that record outputs
64
+
65
+ The stream tests currently record final results for later handoff tests:
66
+
67
+ - `test/integration/live/stream_image_test.rb`
68
+ - `test/integration/live/stream_reasoning_test.rb`
69
+ - `test/integration/live/stream_test.rb`
70
+
71
+ Their helper methods should return the final `AssistantMessage` response after assertions pass. Generated tests then call:
72
+
73
+ ```ruby
74
+ record_live_handoff_result(test_file: __FILE__, provider:, model:, result: response)
75
+ ```
76
+
77
+ This writes JSON under:
78
+
79
+ ```text
80
+ test/fixtures/handoff/{source_test_name_without_rb}/{provider_model}.json
81
+ ```
82
+
83
+ Example:
84
+
85
+ ```text
86
+ test/fixtures/handoff/stream_test/openai_apikey_completions_gpt-5.1.json
87
+ ```
88
+
89
+ The JSON file is keyed by the current Minitest test name with the `test_` prefix removed, so multiple generated tests for the same pair can share one pair file.
90
+
91
+ ## Handoff tests from recorded stream outputs
92
+
93
+ Handoff stream tests are separate files and must not modify the existing handoff tests:
94
+
95
+ - Existing handoff tests to leave alone:
96
+ - `test/integration/live/handoff_test.rb`
97
+ - `test/integration/live/handoff_media_test.rb`
98
+ - Stream handoff tests:
99
+ - `test/integration/live/handoff_stream_image_test.rb`
100
+ - `test/integration/live/handoff_stream_reasoning_test.rb`
101
+ - `test/integration/live/handoff_stream_test.rb`
102
+
103
+ Each stream handoff test should:
104
+
105
+ 1. Read `PAIRS` from the source stream test file.
106
+ 2. Load all recorded output JSON files from the matching fixture directory.
107
+ 3. Send those recorded outputs to each provider/model pair.
108
+ 4. Assert that the receiving model understood the previous outputs.
109
+
110
+ Important: the prompt must not interpolate `records.length` or otherwise tell the model the count. The model should infer the count from the supplied JSON/transcript. It is fine for the assertion to compare against `records.length.to_s`.
111
+
112
+ Example prompt style:
113
+
114
+ ```ruby
115
+ prompt = <<~PROMPT
116
+ You are receiving recorded final outputs from previous image streaming tests.
117
+ Each output describes the same image. Read the JSON, infer what all previous assistants saw,
118
+ and answer in one short sentence. Include the shape, color, and the number of recorded outputs
119
+ as an Arabic numeral.
120
+
121
+ #{JSON.pretty_generate(records)}
122
+ PROMPT
123
+ ```
124
+
125
+ Example assertions:
126
+
127
+ ```ruby
128
+ text = response.content.select { |block| block.type == "text" }.map(&:text).join(" ").downcase
129
+ assert_includes text, "red"
130
+ assert_includes text, "circle"
131
+ assert_includes text, records.length.to_s
132
+ ```
133
+
134
+ ## Reading pairs from source tests
135
+
136
+ When creating a handoff test from a source stream test, keep the provider matrix coupled to the source test by reading its `PAIRS` definition:
137
+
138
+ ```ruby
139
+ SOURCE_TEST_PATH = File.expand_path("stream_image_test.rb", __dir__)
140
+ PAIRS = eval(File.read(SOURCE_TEST_PATH).match(/PAIRS = (\[.*?\])\s*\.freeze/m)[1]).freeze
141
+ ```
142
+
143
+ Only use this pattern for test code where the source file is trusted repository code.
144
+
145
+ ## Fixture loading pattern
146
+
147
+ Handoff tests should skip cleanly if recordings do not exist:
148
+
149
+ ```ruby
150
+ def load_recorded_outputs
151
+ skip "Missing fixture directory at #{FIXTURE_DIR}. Run stream_image_test live tests first." unless Dir.exist?(FIXTURE_DIR)
152
+
153
+ Dir.glob(File.join(FIXTURE_DIR, "*.json")).sort.map do |path|
154
+ {
155
+ pair: File.basename(path, ".json"),
156
+ result: JSON.parse(File.read(path))
157
+ }
158
+ end.tap do |records|
159
+ skip "No recorded outputs in #{FIXTURE_DIR}. Run stream_image_test live tests first." if records.empty?
160
+ end
161
+ end
162
+ ```
163
+
164
+ ## Validation expectations
165
+
166
+ Use assertions that prove semantic handoff, not exact wording:
167
+
168
+ - Image handoff: assert the response mentions `red`, `circle`, and the inferred fixture count.
169
+ - Reasoning handoff: assert the response mentions `69` and the inferred fixture count.
170
+ - General stream handoff: assert the response mentions the inferred fixture count and expected tool/math results such as `42`, `714`, and `887`.
171
+
172
+ Avoid brittle assertions against full response text.
173
+
174
+ ## Syntax and test checks
175
+
176
+ After editing Ruby tests, at minimum run syntax checks:
177
+
178
+ ```bash
179
+ ruby -c test/integration/live/<file>.rb
180
+ ruby -c test/utils/live_test_helper.rb
181
+ ```
182
+
183
+ Run the actual live tests only when requested or when credentials/VCR setup is available, since they may require API keys, OAuth credentials, and network access.
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: options-development
3
+ description: Use when developing or updating provider option mappers in llm_gateway from an API reference URL and optional API hint. Guides managed option mapping, valid option whitelists, source comments, tests, and client behavior boundaries.
4
+ ---
5
+
6
+ # Options Development
7
+
8
+ Use this skill when the user asks to build, update, or audit an LLM provider/API option mapper. The user should provide:
9
+
10
+ - an API reference URL, e.g. `https://platform.claude.com/docs/en/api/messages/create`
11
+ - a hint identifying the API when a provider has multiple APIs, e.g. `Anthropic Messages`, `OpenAI Responses`, `OpenAI Chat Completions`, `Groq Chat Completions`, `OpenAI Codex`
12
+
13
+ ## Goal
14
+
15
+ Keep option handling split into clear layers:
16
+
17
+ 1. **Managed options**: library-owned options that should work consistently across clients.
18
+ 2. **Option mapper**: translates managed options to provider/API option names and shapes. If an option is not managed but is a valid provider option, pass it through. Every mapper must validate the final transformed hash against a whitelist of valid provider options and reject unknown keys.
19
+ 3. **Client behavior**: clients receive already-mapped options and must not do additional option mapping. A client should behave exactly like the provider API docs for its endpoint.
20
+ 4. **Tools and transcript**: option mappers must not map tools, transcript, messages, or system content.
21
+ 5. **Current limitation**: mapped options are currently pushed only into the stream path; do not broaden this unless the task explicitly asks for architecture work.
22
+
23
+ ## Current managed options
24
+
25
+ - `reasoning`
26
+ - supported values: `"none"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`
27
+ - mapped differently depending on the provider/API
28
+ - `max_completion_tokens`
29
+ - default is usually `20_480`
30
+ - Anthropic maps this to `max_tokens`
31
+ - OpenAI Responses maps this to `max_output_tokens`
32
+ - `response_format`
33
+ - examples: `"text"`, `{ type: "json_object" }`, `{ type: "json_schema" }`
34
+ - `cache_key`
35
+ - OpenAI maps this to `prompt_cache_key`
36
+ - `cache_retention`
37
+ - OpenAI values: `"short"`, `"long"`, `"none"`
38
+ - Anthropic passes this through as `cache_retention`
39
+ - `temperature`
40
+ - Groq defaults this to `0`
41
+
42
+ These are **not** options and must not be handled by option mappers:
43
+
44
+ - `message` / `messages` / transcript
45
+ - `tools`
46
+ - `system`
47
+
48
+ ## Repository locations
49
+
50
+ Option mappers live under `lib/llm_gateway/adapters/`, including:
51
+
52
+ - `lib/llm_gateway/adapters/anthropic_option_mapper.rb`
53
+ - `lib/llm_gateway/adapters/groq/option_mapper.rb`
54
+ - `lib/llm_gateway/adapters/openai/chat_completions/option_mapper.rb`
55
+ - `lib/llm_gateway/adapters/openai/responses/option_mapper.rb`
56
+ - `lib/llm_gateway/adapters/openai_codex/option_mapper.rb`
57
+
58
+ Tests live under `test/unit/options/`.
59
+
60
+ ## Required workflow
61
+
62
+ 1. **Identify mapper and tests**
63
+ - Use the API hint to find the corresponding option mapper and test file.
64
+ - Inspect the related adapter/client only to verify mapping boundaries; do not move mapping into clients.
65
+
66
+ 2. **Read the provider API reference**
67
+ - Fetch/read the provided URL if network access is available, for example with `curl -L <url>`.
68
+ - Extract every request-body option accepted by the endpoint.
69
+ - Exclude non-option structural fields such as messages/input/transcript, tools, and system/developer instructions.
70
+ - If docs are not fetchable, say so and ask the user for the relevant request parameter list before coding.
71
+
72
+ 3. **Add/maintain a source comment at the option mapper**
73
+ - Near the valid-option whitelist in the mapper, add a comment containing:
74
+ - source URL
75
+ - API name/hint
76
+ - date accessed
77
+ - the full list of valid option keys copied from the API reference
78
+ - Keep this comment updated whenever the whitelist changes.
79
+
80
+ 4. **Implement managed option mapping in the mapper only**
81
+ - Match the coding style and structure of the closest existing mapper before changing behavior. For example, Anthropic and OpenAI Chat Completions use named default constants, `VALID_OPTIONS`, `MANAGED_OPTIONS`, a `map` method that builds `mapped_options`, explicit normalizer helpers, and `validate_options!` near the mapper.
82
+ - Prefer `mapped_options = options.reject { |key, _| MANAGED_OPTIONS.include?(key) }` when a mapper has multiple managed aliases; this makes alias removal explicit and keeps pass-through provider-native options obvious.
83
+ - Remove managed aliases after mapping so the final hash contains only provider-native option keys.
84
+ - Preserve valid provider-native options that are not managed.
85
+ - Apply provider-specific defaults only in the mapper.
86
+ - Unless the user explicitly asks for default behavior changes, do not modify existing defaults.
87
+ - Do not map tools, transcript/messages, or system.
88
+
89
+ 5. **Whitelist after transformation**
90
+ - Define a `VALID_OPTIONS`/equivalent whitelist from the API reference.
91
+ - After all transformations, reject any final key not in the whitelist.
92
+ - Prefer raising `ArgumentError` with a useful message listing unknown option keys and/or valid keys.
93
+ - Validate the returned hash, not merely the input hash, so bad mapped output is caught.
94
+
95
+ 6. **Tests**
96
+ - Match the structure and naming style of the closest existing provider/API option test before adding cases. For Anthropic and OpenAI Chat Completions, prefer a compact set of broad tests:
97
+ - one adapter-boundary test named like `passes mapped managed options and provider-native options through adapter to client`
98
+ - one unknown provider option rejection test
99
+ - one structural field rejection test
100
+ - one superset/final output mapper test
101
+ - Prefer testing option behavior at the adapter boundary by stubbing the provider client and asserting the exact options passed to the client's request/stream method. This verifies that managed options are mapped before the client and that valid provider-native options pass through the adapter layer unchanged.
102
+ - Keep pure mapper tests for validation/error behavior, final-output superset assertions, and small normalization helpers when useful, but avoid relying only on direct `OptionMapper.map(...)` assertions for passthrough behavior.
103
+ - Fake clients in adapter-boundary stream tests should yield enough realistic stream chunks for the adapter's stream mapper and accumulator to complete without errors; do not yield only terminal/usage chunks if the mapper expects started content/tool state.
104
+ - Add/update tests for:
105
+ - each managed option mapping relevant to the provider/API
106
+ - pass-through of valid provider-native options together with representative managed options in the same adapter-level test
107
+ - rejection of unknown options after transformation
108
+ - no handling of tools/messages/system in the mapper
109
+ - provider-specific defaults, if any
110
+ - For adapter-level option tests:
111
+ - instantiate the real adapter with a fake/stub client for the target provider/API
112
+ - call the public adapter method (`stream` today) with managed and provider-native options
113
+ - capture the keyword args received by the client method
114
+ - assert the final provider-native option hash, including mapped managed options and unchanged provider-native options
115
+ - ensure fake clients provide whatever minimal stream/result events are needed so the adapter can complete without network or VCR
116
+ - After making option-mapper changes, run the targeted option tests, then the broader test suite if practical.
117
+ - If VCR/cassette-backed tests fail and option changes are the only code changes, do not immediately re-record or mutate cassettes. First explain why the VCR is failing (for example: request body changed because a new/default option is now sent, unknown option rejection changed control flow, or provider-native option passthrough altered the recorded request). Ask for confirmation before taking further VCR steps.
118
+
119
+ ## Implementation checklist
120
+
121
+ Before finishing, verify:
122
+
123
+ - [ ] mapper has source/API/date/full-valid-option-list comment
124
+ - [ ] mapper maps all relevant managed options and deletes aliases
125
+ - [ ] valid provider-native options pass through unchanged
126
+ - [ ] final returned options are whitelist-validated
127
+ - [ ] clients do not perform additional option mapping
128
+ - [ ] tools/transcript/messages/system are not mapped as options
129
+ - [ ] defaults were not modified unless explicitly requested
130
+ - [ ] tests cover mapping, pass-through, rejection, and defaults
131
+ - [ ] tests were run after option changes; any VCR failure was explained before further cassette work
data/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.5.0](https://github.com/Hyper-Unearthing/llm_gateway/tree/v0.5.0) (2026-05-20)
4
+
5
+ [Full Changelog](https://github.com/Hyper-Unearthing/llm_gateway/compare/v0.4.0...v0.5.0)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - Refactor stream mapper accumulation [\#61](https://github.com/Hyper-Unearthing/llm_gateway/pull/61) ([billybonks](https://github.com/billybonks))
10
+ - feat\(groq\): add stream support for groq [\#60](https://github.com/Hyper-Unearthing/llm_gateway/pull/60) ([billybonks](https://github.com/billybonks))
11
+ - test\(feat\): allow options to be passed in model pairs [\#59](https://github.com/Hyper-Unearthing/llm_gateway/pull/59) ([billybonks](https://github.com/billybonks))
12
+ - Focus streaming and Claude client tests [\#58](https://github.com/Hyper-Unearthing/llm_gateway/pull/58) ([billybonks](https://github.com/billybonks))
13
+ - feat\(test\): automatically delete unused vcrs [\#57](https://github.com/Hyper-Unearthing/llm_gateway/pull/57) ([billybonks](https://github.com/billybonks))
14
+ - refactor: handoff test [\#56](https://github.com/Hyper-Unearthing/llm_gateway/pull/56) ([billybonks](https://github.com/billybonks))
15
+ - Refactor/options clients [\#55](https://github.com/Hyper-Unearthing/llm_gateway/pull/55) ([billybonks](https://github.com/billybonks))
16
+ - burn: all the old code [\#54](https://github.com/Hyper-Unearthing/llm_gateway/pull/54) ([billybonks](https://github.com/billybonks))
17
+ - test: only skip actual auth errors [\#53](https://github.com/Hyper-Unearthing/llm_gateway/pull/53) ([billybonks](https://github.com/billybonks))
18
+ - test: dont try refresh token when using vcr only when regenerating [\#52](https://github.com/Hyper-Unearthing/llm_gateway/pull/52) ([billybonks](https://github.com/billybonks))
19
+
20
+ ## [v0.4.0](https://github.com/Hyper-Unearthing/llm_gateway/tree/v0.4.0) (2026-05-17)
21
+
22
+ [Full Changelog](https://github.com/Hyper-Unearthing/llm_gateway/compare/v0.3.0...v0.4.0)
23
+
24
+ **Merged pull requests:**
25
+
26
+ - docs: update docs new code [\#51](https://github.com/Hyper-Unearthing/llm_gateway/pull/51) ([billybonks](https://github.com/billybonks))
27
+ - test: rework live tests to use vcr [\#50](https://github.com/Hyper-Unearthing/llm_gateway/pull/50) ([billybonks](https://github.com/billybonks))
28
+ - refactor: update provider keys [\#49](https://github.com/Hyper-Unearthing/llm_gateway/pull/49) ([billybonks](https://github.com/billybonks))
29
+ - Refactor/major internal organisation [\#48](https://github.com/Hyper-Unearthing/llm_gateway/pull/48) ([billybonks](https://github.com/billybonks))
30
+ - cross provider handoff support [\#47](https://github.com/Hyper-Unearthing/llm_gateway/pull/47) ([billybonks](https://github.com/billybonks))
31
+ - Refactor provider usage and especially oauth [\#46](https://github.com/Hyper-Unearthing/llm_gateway/pull/46) ([billybonks](https://github.com/billybonks))
32
+ - Refactor/options [\#45](https://github.com/Hyper-Unearthing/llm_gateway/pull/45) ([billybonks](https://github.com/billybonks))
33
+ - fix: map content blocks in tools as well [\#44](https://github.com/Hyper-Unearthing/llm_gateway/pull/44) ([billybonks](https://github.com/billybonks))
34
+ - Streaming support [\#43](https://github.com/Hyper-Unearthing/llm_gateway/pull/43) ([billybonks](https://github.com/billybonks))
35
+ - feat: try to simplify api and provider combination [\#42](https://github.com/Hyper-Unearthing/llm_gateway/pull/42) ([billybonks](https://github.com/billybonks))
36
+ - feat: add reasoning effort parameter [\#41](https://github.com/Hyper-Unearthing/llm_gateway/pull/41) ([billybonks](https://github.com/billybonks))
37
+ - Feat/backport streaming [\#40](https://github.com/Hyper-Unearthing/llm_gateway/pull/40) ([billybonks](https://github.com/billybonks))
38
+ - fix: cache control [\#39](https://github.com/Hyper-Unearthing/llm_gateway/pull/39) ([billybonks](https://github.com/billybonks))
39
+ - feat: configure [\#38](https://github.com/Hyper-Unearthing/llm_gateway/pull/38) ([billybonks](https://github.com/billybonks))
40
+ - fix: alias clients to old classname until 1.0 [\#36](https://github.com/Hyper-Unearthing/llm_gateway/pull/36) ([billybonks](https://github.com/billybonks))
41
+ - refactor: adapter to split clients from adapter [\#35](https://github.com/Hyper-Unearthing/llm_gateway/pull/35) ([billybonks](https://github.com/billybonks))
42
+ - test: make tests less brittle when regenerating vcr [\#34](https://github.com/Hyper-Unearthing/llm_gateway/pull/34) ([billybonks](https://github.com/billybonks))
43
+ - Clean up gems and delete sample code [\#33](https://github.com/Hyper-Unearthing/llm_gateway/pull/33) ([billybonks](https://github.com/billybonks))
44
+ - feat: add clade subscription as a seperate provider [\#32](https://github.com/Hyper-Unearthing/llm_gateway/pull/32) ([billybonks](https://github.com/billybonks))
45
+
3
46
  ## [v0.3.0](https://github.com/Hyper-Unearthing/llm_gateway/tree/v0.3.0) (2025-08-19)
4
47
 
5
48
  [Full Changelog](https://github.com/Hyper-Unearthing/llm_gateway/compare/v0.2.0...v0.3.0)