llmshim 0.3.6 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f45388109bb2742c9d038e99a6bba32789f3a9f9bc39ff3d78cc0c13019c85f
4
- data.tar.gz: 723da5a41f2606cc52915c25e425f8eb34231b9cfe1a7f0ed7ee54a00760657b
3
+ metadata.gz: 7daaabbea4814eb113a27e7cf8965d87f8bc29459f4a1b6caad395984388e2da
4
+ data.tar.gz: f629a9202933185c03908033c48d2ef8a6d30330acd5cabb7048309680eb4ca0
5
5
  SHA512:
6
- metadata.gz: 3662b15aa3c188ac5ee0242781d72000adf6c7c003f0da636019eb09e8da1779248c9b31538cca67fa0e0cf280ef7ba48afa6c0445a6467a73e3cd25897d8659
7
- data.tar.gz: e6857361151dea40ac1dad35bcfedd941a719f6f5827569668891478212e6d808c95f64f5f6fa6317fb359511660ea64389c1ed1bf38b0a250b247e69cd97cce
6
+ metadata.gz: 40f1298cb6bed88b1353236cb09b8085bd21e9dc6bb8d968c66ad85cddb9e711b7ce7ae3c2dc6ac9236aca12e776ee28dc43910c437a80995822065961b5c14b
7
+ data.tar.gz: 76a5d19612b5ae27ad8e602c5b37848e08998909e43b41ef2067ade819fffe8b6dc4efcf34ffa494f2b7594ac7aa30a56b6ee62a76c8dcf3d165dc43bf22253a
data/README.md CHANGED
@@ -46,9 +46,9 @@ environment variables. The Ruby client never sees your keys — the proxy holds
46
46
  | ---------- | --------------------------------------- | ------------------------------------- |
47
47
  | OpenAI | `openai/gpt-5.6-sol` | `OPENAI_API_KEY` |
48
48
  | Anthropic | `anthropic/claude-sonnet-5` | `ANTHROPIC_API_KEY` |
49
- | Gemini | `gemini/gemini-3.5-flash` | `GEMINI_API_KEY` |
50
- | xAI | `xai/grok-4.5` | `XAI_API_KEY` |
51
- | OpenRouter | `openrouter/anthropic/claude-sonnet-4.5` | `OPENROUTER_API_KEY` |
49
+ | Gemini | `gemini/gemini-3.8-flash` | `GEMINI_API_KEY` |
50
+ | xAI | `xai/grok-4.6` | `XAI_API_KEY` |
51
+ | OpenRouter | `openrouter/anthropic/claude-sonnet-5` | `OPENROUTER_API_KEY` |
52
52
  | vLLM | `vllm/<served-model>` | `VLLM_BASE_URL` (+ optional `VLLM_API_KEY`) |
53
53
  | SGLang | `sglang/<served-model>` | `SGLANG_BASE_URL` (+ optional `SGLANG_API_KEY`) |
54
54
 
@@ -75,7 +75,7 @@ of message hashes:
75
75
 
76
76
  ```ruby
77
77
  resp = client.chat(
78
- model: "openai/gpt-5.5",
78
+ model: "openai/gpt-5.6-sol",
79
79
  messages: [
80
80
  { role: "system", content: "You are a pirate." },
81
81
  { role: "user", content: "Hello!" }
@@ -99,7 +99,7 @@ A shared default client (base URL from `LLMSHIM_BASE_URL`, else `http://localhos
99
99
  ```ruby
100
100
  require "llmshim"
101
101
 
102
- resp = Llmshim.chat(model: "gpt-5.5", messages: "Explain quicksort")
102
+ resp = Llmshim.chat(model: "gpt-5.6-sol", messages: "Explain quicksort")
103
103
  puts resp.content
104
104
  ```
105
105
 
@@ -127,7 +127,7 @@ Predicate helpers are available too: `event.content?`, `event.reasoning?`,
127
127
  Called without a block, `stream` returns the collected array of events:
128
128
 
129
129
  ```ruby
130
- events = client.stream(model: "gpt-5.5", messages: "Hi")
130
+ events = client.stream(model: "gpt-5.6-sol", messages: "Hi")
131
131
  text = events.select(&:content?).map(&:text).join
132
132
  ```
133
133
 
@@ -145,9 +145,9 @@ resp = client.chat(
145
145
  ],
146
146
  tool_choice: "auto",
147
147
  # Provider-specific controls, namespaced under x-<provider> (see below):
148
- provider_config: { "x-anthropic" => { thinking: { type: "enabled", budget_tokens: 4000 } } },
148
+ provider_config: { "x-anthropic" => { thinking: { type: "adaptive" }, output_config: { effort: "high" } } },
149
149
  # Try these models if the primary fails with a retryable error:
150
- fallback: ["openai/gpt-5.6-sol", "gemini/gemini-3.5-flash"]
150
+ fallback: ["openai/gpt-5.6-sol", "gemini/gemini-3.8-flash"]
151
151
  )
152
152
 
153
153
  resp.message.tool_calls.each do |tc|
@@ -177,7 +177,7 @@ level is ignored. Use the namespace matching the target provider:
177
177
 
178
178
  ```ruby
179
179
  # Anthropic extended thinking:
180
- provider_config: { "x-anthropic" => { thinking: { type: "enabled", budget_tokens: 4000 } } }
180
+ provider_config: { "x-anthropic" => { thinking: { type: "adaptive" }, output_config: { effort: "high" } } }
181
181
 
182
182
  # OpenRouter provider routing (also accepts `models`, `transforms`):
183
183
  provider_config: { "x-openrouter" => { provider: { sort: "throughput" } } }
@@ -120,6 +120,9 @@ module Llmshim
120
120
  provider_config["tool_choice"] = opts[:tool_choice] unless opts[:tool_choice].nil?
121
121
  body["provider_config"] = provider_config unless provider_config.empty?
122
122
 
123
+ body["x-cache"] = stringify(opts[:cache]) unless opts[:cache].nil?
124
+ body["x-shim"] = stringify(opts[:shim]) unless opts[:shim].nil?
125
+ body["response_format"] = stringify(opts[:response_format]) unless opts[:response_format].nil?
123
126
  body["fallback"] = opts[:fallback] unless opts[:fallback].nil?
124
127
  body["stream"] = opts[:stream] unless opts[:stream].nil?
125
128
 
data/lib/llmshim/types.rb CHANGED
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module Llmshim
4
6
  # Token accounting returned with a chat response or a usage stream event.
5
7
  #
6
8
  # Mirrors the +Usage+ schema in api/openapi.yaml.
7
9
  Usage = Struct.new(
8
10
  :input_tokens, :output_tokens, :reasoning_tokens, :total_tokens,
11
+ :cache_read_tokens, :cache_write_tokens,
9
12
  keyword_init: true
10
13
  ) do
11
14
  def self.from_hash(hash)
@@ -15,7 +18,9 @@ module Llmshim
15
18
  input_tokens: hash["input_tokens"],
16
19
  output_tokens: hash["output_tokens"],
17
20
  reasoning_tokens: hash["reasoning_tokens"],
18
- total_tokens: hash["total_tokens"]
21
+ total_tokens: hash["total_tokens"],
22
+ cache_read_tokens: hash.fetch("cache_read_tokens", 0),
23
+ cache_write_tokens: hash.fetch("cache_write_tokens", 0)
19
24
  )
20
25
  end
21
26
  end
@@ -24,9 +29,13 @@ module Llmshim
24
29
  #
25
30
  # +function+ is a plain Hash with "name" and "arguments" (JSON-encoded string)
26
31
  # to match the wire format exactly.
27
- ToolCall = Struct.new(:id, :type, :function, keyword_init: true) do
32
+ ToolCall = Struct.new(:id, :type, :function, :thought_signature, :wire_ids, keyword_init: true) do
28
33
  def self.from_hash(hash)
29
- new(id: hash["id"], type: hash["type"], function: hash["function"])
34
+ new(id: hash["id"], type: hash["type"], function: hash["function"], thought_signature: hash["thought_signature"], wire_ids: hash["wire_ids"])
35
+ end
36
+
37
+ def to_json(*args)
38
+ to_h.reject { |_key, value| value.nil? }.to_json(*args)
30
39
  end
31
40
 
32
41
  # Convenience accessor for the tool name.
@@ -41,10 +50,14 @@ module Llmshim
41
50
  end
42
51
 
43
52
  # The assistant message inside a ChatResponse.
44
- ResponseMessage = Struct.new(:role, :content, :tool_calls, keyword_init: true) do
53
+ ResponseMessage = Struct.new(:role, :content, :tool_calls, :reasoning, :refusal, keyword_init: true) do
45
54
  def self.from_hash(hash)
46
55
  calls = (hash["tool_calls"] || []).map { |c| ToolCall.from_hash(c) }
47
- new(role: hash["role"], content: hash["content"], tool_calls: calls)
56
+ new(role: hash["role"], content: hash["content"], tool_calls: calls, reasoning: hash["reasoning"], refusal: hash["refusal"])
57
+ end
58
+
59
+ def to_json(*args)
60
+ to_h.reject { |key, value| value.nil? && !%i[role content].include?(key) }.to_json(*args)
48
61
  end
49
62
  end
50
63
 
@@ -52,7 +65,7 @@ module Llmshim
52
65
  #
53
66
  # +raw+ retains the original parsed Hash for forward compatibility.
54
67
  ChatResponse = Struct.new(
55
- :id, :model, :provider, :message, :reasoning, :usage, :latency_ms, :raw,
68
+ :id, :model, :provider, :message, :reasoning, :usage, :latency_ms, :raw, :served_model, :finish_reason,
56
69
  keyword_init: true
57
70
  ) do
58
71
  def self.from_hash(hash)
@@ -64,7 +77,9 @@ module Llmshim
64
77
  reasoning: hash["reasoning"],
65
78
  usage: Usage.from_hash(hash["usage"]),
66
79
  latency_ms: hash["latency_ms"],
67
- raw: hash
80
+ raw: hash,
81
+ served_model: hash["x-llmshim-served-model"],
82
+ finish_reason: hash["finish_reason"]
68
83
  )
69
84
  end
70
85
 
@@ -120,10 +135,22 @@ module Llmshim
120
135
  type == "usage"
121
136
  end
122
137
 
138
+ def finish_reason
139
+ raw["finish_reason"]
140
+ end
141
+
142
+ def served_model
143
+ raw["x-llmshim-served-model"]
144
+ end
145
+
123
146
  def done?
124
147
  type == "done"
125
148
  end
126
149
 
150
+ def error_details
151
+ raw["error"]
152
+ end
153
+
127
154
  def error?
128
155
  type == "error"
129
156
  end
@@ -133,6 +160,19 @@ module Llmshim
133
160
  raw["text"]
134
161
  end
135
162
 
163
+ # Reasoning delta blocks retain signatures, opaque data, and provenance.
164
+ def blocks
165
+ raw["blocks"]
166
+ end
167
+
168
+ def thought_signature
169
+ raw["thought_signature"]
170
+ end
171
+
172
+ def wire_ids
173
+ raw["wire_ids"]
174
+ end
175
+
136
176
  # tool_call events
137
177
  def id
138
178
  raw["id"]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Llmshim
4
- VERSION = "0.3.6"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llmshim
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sanjay Nadhavajhala
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-12 00:00:00.000000000 Z
11
+ date: 2026-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest