turnkit 0.2.2 → 0.2.3
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +73 -135
- data/lib/turnkit/adapters/ruby_llm.rb +40 -4
- data/lib/turnkit/turn.rb +4 -1
- data/lib/turnkit/usage.rb +5 -3
- data/lib/turnkit/version.rb +1 -1
- data/lib/turnkit.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c02ad5eef683595c702a33806438f414ed2da9e18c607a8b314bba4ae442404
|
|
4
|
+
data.tar.gz: 4da3877b7c20aecae1dd77e6df4497bb64a3909d28419fb1413feb37fa5fa298
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5de4c365826d8a4154d2ee013fe0f7289796b91b63eb34ad81693993eb55b8f8d0282f8415e7798f9eb698d2f6f4aa52b79949e1c89c0c64effe506cf26ef0b
|
|
7
|
+
data.tar.gz: b168324cf4f97485ce7854006565441fd0fe67e1f84835805d98d67f27a2a793fe2ce8bd27a6939c6ccbf3cc92023bc93c8aff5e8049fb0b2991a50548d211d6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.3 - 2026-06-06
|
|
4
|
+
|
|
5
|
+
- Add Anthropic prompt cache support for stable system prompt sections.
|
|
6
|
+
- Track cache write tokens and aggregate model costs on turns.
|
|
7
|
+
- Refresh README usage examples for prompt caching and usage tracking.
|
|
8
|
+
|
|
3
9
|
## 0.2.0 - 2026-06-04
|
|
4
10
|
|
|
5
11
|
- Add configurable system prompt sections and custom system prompt builders.
|
data/README.md
CHANGED
|
@@ -26,33 +26,9 @@ Set a provider key:
|
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
28
|
export ANTHROPIC_API_KEY=...
|
|
29
|
-
# or OPENAI_API_KEY=..., GEMINI_API_KEY=..., OPENROUTER_API_KEY=...
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
```ruby
|
|
35
|
-
TurnKit.default_model = "claude-sonnet-4-5" # Anthropic
|
|
36
|
-
# TurnKit.default_model = "gpt-4.1-mini" # OpenAI
|
|
37
|
-
# TurnKit.default_model = "gemini-2.5-flash" # Gemini
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
You can also override the model per agent or per run.
|
|
41
|
-
|
|
42
|
-
To use a different model SDK, provide a client object that responds to `chat`:
|
|
43
|
-
|
|
44
|
-
```ruby
|
|
45
|
-
class MyClient < TurnKit::Client
|
|
46
|
-
def chat(model:, messages:, tools:, instructions:, temperature: nil, metadata: nil)
|
|
47
|
-
# Call your provider here.
|
|
48
|
-
TurnKit::Result.new(text: "provider response", model: model)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
TurnKit.client = MyClient.new
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Ask an agent:
|
|
31
|
+
Create an agent:
|
|
56
32
|
|
|
57
33
|
```ruby
|
|
58
34
|
require "turnkit"
|
|
@@ -68,6 +44,22 @@ puts turn.output_text
|
|
|
68
44
|
|
|
69
45
|
## Usage
|
|
70
46
|
|
|
47
|
+
Choose a model:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
TurnKit.default_model = "claude-sonnet-4-5"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Use OpenAI:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
export OPENAI_API_KEY=...
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
TurnKit.default_model = "gpt-4.1-mini"
|
|
61
|
+
```
|
|
62
|
+
|
|
71
63
|
Create a conversation:
|
|
72
64
|
|
|
73
65
|
```ruby
|
|
@@ -101,7 +93,7 @@ class SaveReport < TurnKit::Tool
|
|
|
101
93
|
end
|
|
102
94
|
```
|
|
103
95
|
|
|
104
|
-
Use
|
|
96
|
+
Use a tool:
|
|
105
97
|
|
|
106
98
|
```ruby
|
|
107
99
|
agent = TurnKit::Agent.new(
|
|
@@ -125,142 +117,99 @@ agent = TurnKit::Agent.new(
|
|
|
125
117
|
)
|
|
126
118
|
```
|
|
127
119
|
|
|
128
|
-
|
|
120
|
+
Delegate to sub-agents:
|
|
129
121
|
|
|
130
122
|
```ruby
|
|
131
|
-
|
|
132
|
-
"
|
|
133
|
-
description: "
|
|
123
|
+
writer = TurnKit::Agent.new(
|
|
124
|
+
name: "writer",
|
|
125
|
+
description: "Draft concise copy."
|
|
134
126
|
)
|
|
135
127
|
|
|
136
|
-
|
|
137
|
-
name: "
|
|
138
|
-
|
|
139
|
-
tools: [WebSearch, ReadWebPage],
|
|
140
|
-
available_skills: [research]
|
|
128
|
+
editor = TurnKit::Agent.new(
|
|
129
|
+
name: "editor",
|
|
130
|
+
sub_agents: [writer]
|
|
141
131
|
)
|
|
142
|
-
```
|
|
143
132
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
```ruby
|
|
147
|
-
article = Article.find(1)
|
|
148
|
-
conversation = agent.conversation(subject: article)
|
|
133
|
+
turn = editor.conversation.ask("Ask the writer for three headlines.")
|
|
134
|
+
puts turn.output_text
|
|
149
135
|
```
|
|
150
136
|
|
|
151
|
-
|
|
137
|
+
Use prompt caching:
|
|
152
138
|
|
|
153
139
|
```ruby
|
|
154
|
-
|
|
155
|
-
name: "writer",
|
|
156
|
-
instructions: "Write plainly.",
|
|
157
|
-
prompt_sections: %i[agent instructions tools environment]
|
|
158
|
-
)
|
|
140
|
+
TurnKit.prompt_cache = :auto
|
|
159
141
|
```
|
|
160
142
|
|
|
161
|
-
|
|
143
|
+
Disable prompt caching:
|
|
162
144
|
|
|
163
145
|
```ruby
|
|
164
|
-
|
|
165
|
-
name: "custom",
|
|
166
|
-
instructions: "Answer in JSON.",
|
|
167
|
-
system_prompt: ->(prompt) {
|
|
168
|
-
[
|
|
169
|
-
prompt.agent_section,
|
|
170
|
-
prompt.instructions_section,
|
|
171
|
-
"Return only valid JSON."
|
|
172
|
-
].compact.join("\n\n")
|
|
173
|
-
}
|
|
174
|
-
)
|
|
146
|
+
TurnKit.prompt_cache = :off
|
|
175
147
|
```
|
|
176
148
|
|
|
177
|
-
|
|
149
|
+
Split custom prompts:
|
|
178
150
|
|
|
179
151
|
```ruby
|
|
180
152
|
agent = TurnKit::Agent.new(
|
|
181
|
-
name: "
|
|
182
|
-
system_prompt:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
:retrieval_context,
|
|
188
|
-
ExternalSearch.results_for("turnkit"),
|
|
189
|
-
label: "Retrieved external evidence."
|
|
190
|
-
),
|
|
191
|
-
prompt.section(:tools),
|
|
192
|
-
prompt.section(:environment)
|
|
193
|
-
].compact.join("\n\n")
|
|
194
|
-
}
|
|
153
|
+
name: "cached",
|
|
154
|
+
system_prompt: [
|
|
155
|
+
"Stable instructions and tool guidance.",
|
|
156
|
+
TurnKit::SystemPrompt::CACHE_BOUNDARY,
|
|
157
|
+
"Dynamic subject and live context."
|
|
158
|
+
].join("\n")
|
|
195
159
|
)
|
|
196
160
|
```
|
|
197
161
|
|
|
198
|
-
|
|
162
|
+
Inspect usage:
|
|
199
163
|
|
|
200
164
|
```ruby
|
|
201
|
-
TurnKit
|
|
202
|
-
|
|
203
|
-
TurnKit::Agent.new(name: "raw", prompt_mode: :none) # tiny TurnKit identity prompt
|
|
165
|
+
record = TurnKit.store.load_turn(turn.id)
|
|
166
|
+
record.fetch("usage")
|
|
204
167
|
```
|
|
205
168
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
Inject live context on each turn:
|
|
169
|
+
Return usage from custom clients:
|
|
209
170
|
|
|
210
171
|
```ruby
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
172
|
+
class MyClient < TurnKit::Client
|
|
173
|
+
def chat(model:, messages:, tools:, instructions:, temperature: nil, metadata: nil)
|
|
174
|
+
TurnKit::Result.new(
|
|
175
|
+
text: "provider response",
|
|
176
|
+
model: model,
|
|
177
|
+
usage: TurnKit::Usage.new(
|
|
178
|
+
input_tokens: 100,
|
|
179
|
+
output_tokens: 20,
|
|
180
|
+
cached_tokens: 80,
|
|
181
|
+
cache_write_tokens: 100
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
218
186
|
```
|
|
219
187
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
Add model-specific prompt guidance:
|
|
188
|
+
Split instructions inside custom clients:
|
|
223
189
|
|
|
224
190
|
```ruby
|
|
225
|
-
|
|
226
|
-
TurnKit::PromptContribution.new(
|
|
227
|
-
stable_prefix: "Provider guidance for #{context.model}.",
|
|
228
|
-
section_overrides: {
|
|
229
|
-
behavior: "Be concise, tool-aware, and explicit about uncertainty."
|
|
230
|
-
}
|
|
231
|
-
)
|
|
232
|
-
}
|
|
191
|
+
stable, dynamic = TurnKit::SystemPrompt.split_cache_boundary(instructions)
|
|
233
192
|
```
|
|
234
193
|
|
|
235
|
-
|
|
194
|
+
Send `stable` with provider cache controls.
|
|
236
195
|
|
|
237
|
-
|
|
238
|
-
prompt = TurnKit::SystemPrompt.new(agent: agent, turn: turn, conversation: conversation)
|
|
239
|
-
prompt.report
|
|
240
|
-
# => { "chars" => ..., "hash" => ..., "stable_chars" => ..., "dynamic_chars" => ... }
|
|
241
|
-
```
|
|
196
|
+
Send `dynamic` as normal prompt content.
|
|
242
197
|
|
|
243
|
-
|
|
198
|
+
Use a custom client:
|
|
244
199
|
|
|
245
200
|
```ruby
|
|
246
|
-
|
|
247
|
-
name: "writer",
|
|
248
|
-
description: "Draft concise copy."
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
editor = TurnKit::Agent.new(
|
|
252
|
-
name: "editor",
|
|
253
|
-
sub_agents: [writer]
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
turn = editor.conversation.ask("Ask the writer for three headlines.")
|
|
257
|
-
puts turn.output_text
|
|
201
|
+
TurnKit.client = MyClient.new
|
|
258
202
|
```
|
|
259
203
|
|
|
260
204
|
Install Rails persistence:
|
|
261
205
|
|
|
262
206
|
```sh
|
|
263
207
|
bin/rails generate turnkit:install
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Run migrations:
|
|
211
|
+
|
|
212
|
+
```sh
|
|
264
213
|
bin/rails db:migrate
|
|
265
214
|
```
|
|
266
215
|
|
|
@@ -269,7 +218,6 @@ Configure Rails:
|
|
|
269
218
|
```ruby
|
|
270
219
|
TurnKit.store = TurnKit::ActiveRecordStore.new
|
|
271
220
|
TurnKit.default_model = "claude-sonnet-4-5"
|
|
272
|
-
TurnKit.timeout = 300
|
|
273
221
|
```
|
|
274
222
|
|
|
275
223
|
Reconcile stale turns:
|
|
@@ -289,9 +237,10 @@ TurnKit.timeout = 300
|
|
|
289
237
|
TurnKit.max_depth = 3
|
|
290
238
|
TurnKit.max_tool_executions = 100
|
|
291
239
|
TurnKit.cost_limit = nil
|
|
240
|
+
TurnKit.prompt_cache = :auto
|
|
292
241
|
```
|
|
293
242
|
|
|
294
|
-
Override
|
|
243
|
+
Override an agent:
|
|
295
244
|
|
|
296
245
|
```ruby
|
|
297
246
|
agent = TurnKit::Agent.new(
|
|
@@ -303,29 +252,18 @@ agent = TurnKit::Agent.new(
|
|
|
303
252
|
)
|
|
304
253
|
```
|
|
305
254
|
|
|
306
|
-
Override the model for a single conversation or turn:
|
|
307
|
-
|
|
308
|
-
```ruby
|
|
309
|
-
conversation = agent.conversation(model: "claude-opus-4-1")
|
|
310
|
-
turn = conversation.run!(model: "gpt-4.1-mini")
|
|
311
|
-
```
|
|
312
|
-
|
|
313
255
|
| Option | Description |
|
|
314
256
|
| --- | --- |
|
|
315
|
-
| `default_model` | Set the default RubyLLM model.
|
|
316
|
-
| `client` | Set the model client.
|
|
257
|
+
| `default_model` | Set the default RubyLLM model. |
|
|
258
|
+
| `client` | Set the model client. |
|
|
317
259
|
| `store` | Set the conversation store. |
|
|
318
260
|
| `max_iterations` | Limit model calls per turn. |
|
|
319
261
|
| `timeout` | Limit seconds per root turn. |
|
|
320
262
|
| `max_depth` | Limit sub-agent nesting. |
|
|
321
263
|
| `max_tool_executions` | Limit tool calls per root turn. |
|
|
322
264
|
| `cost_limit` | Limit cost per root turn. |
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
325
|
-
| `prompt_data_max_chars` | Limit data-block content rendered into prompts. |
|
|
326
|
-
| `context_contributors` | Add live per-turn prompt context blocks. |
|
|
327
|
-
| `system_prompt_contributors` | Add global prompt prefix/suffix/section overrides. |
|
|
328
|
-
| `model_prompt_contributors` | Add model-matched prompt contributions. |
|
|
265
|
+
| `prompt_cache` | Use provider prompt caching. |
|
|
266
|
+
| `prompt_sections` | Set default prompt sections. |
|
|
329
267
|
|
|
330
268
|
## Contributing
|
|
331
269
|
|
|
@@ -9,7 +9,7 @@ module TurnKit
|
|
|
9
9
|
configure_from_environment
|
|
10
10
|
|
|
11
11
|
chat = ::RubyLLM.chat(model: model)
|
|
12
|
-
chat
|
|
12
|
+
add_instructions(chat, instructions, model: model)
|
|
13
13
|
chat.with_temperature(temperature) if temperature
|
|
14
14
|
Array(tools).each { |tool| chat.with_tool(ruby_llm_tool(tool)) }
|
|
15
15
|
Array(messages).each { |message| add_message(chat, message) }
|
|
@@ -55,6 +55,37 @@ module TurnKit
|
|
|
55
55
|
)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
def add_instructions(chat, instructions, model:)
|
|
59
|
+
return if instructions.nil? || instructions.empty?
|
|
60
|
+
|
|
61
|
+
if prompt_cache_enabled? && anthropic_model?(model) && instructions.include?(SystemPrompt::CACHE_BOUNDARY)
|
|
62
|
+
stable, dynamic = SystemPrompt.split_cache_boundary(instructions)
|
|
63
|
+
add_system_message(chat, stable, cache: true)
|
|
64
|
+
add_system_message(chat, dynamic, cache: false)
|
|
65
|
+
else
|
|
66
|
+
chat.with_instructions(instructions)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def add_system_message(chat, content, cache: false)
|
|
71
|
+
content = content.to_s.strip
|
|
72
|
+
return if content.empty?
|
|
73
|
+
|
|
74
|
+
if cache
|
|
75
|
+
content = ::RubyLLM::Providers::Anthropic::Content.new(content, cache: true)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
chat.add_message(role: :system, content: content)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def prompt_cache_enabled?
|
|
82
|
+
TurnKit.prompt_cache != :off
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def anthropic_model?(model)
|
|
86
|
+
model.to_s.start_with?("claude")
|
|
87
|
+
end
|
|
88
|
+
|
|
58
89
|
def ruby_llm_tool_calls(tool_calls)
|
|
59
90
|
return nil if tool_calls.nil? || tool_calls.empty?
|
|
60
91
|
|
|
@@ -88,9 +119,10 @@ module TurnKit
|
|
|
88
119
|
ToolCall.new(id: call.id, name: call.name, arguments: call.arguments)
|
|
89
120
|
end
|
|
90
121
|
usage = Usage.new(
|
|
91
|
-
input_tokens: response
|
|
92
|
-
output_tokens: response
|
|
93
|
-
cached_tokens: response
|
|
122
|
+
input_tokens: token_value(response, :input_tokens),
|
|
123
|
+
output_tokens: token_value(response, :output_tokens),
|
|
124
|
+
cached_tokens: token_value(response, :cached_tokens),
|
|
125
|
+
cache_write_tokens: token_value(response, :cache_creation_tokens)
|
|
94
126
|
)
|
|
95
127
|
Result.new(
|
|
96
128
|
text: response.respond_to?(:content) ? response.content.to_s : response.to_s,
|
|
@@ -99,6 +131,10 @@ module TurnKit
|
|
|
99
131
|
model: response.respond_to?(:model_id) ? response.model_id : model
|
|
100
132
|
)
|
|
101
133
|
end
|
|
134
|
+
|
|
135
|
+
def token_value(response, method)
|
|
136
|
+
response.respond_to?(method) ? response.public_send(method).to_i : 0
|
|
137
|
+
end
|
|
102
138
|
end
|
|
103
139
|
end
|
|
104
140
|
end
|
data/lib/turnkit/turn.rb
CHANGED
|
@@ -123,9 +123,12 @@ module TurnKit
|
|
|
123
123
|
"input_tokens" => current["input_tokens"].to_i + usage.input_tokens,
|
|
124
124
|
"output_tokens" => current["output_tokens"].to_i + usage.output_tokens,
|
|
125
125
|
"cached_tokens" => current["cached_tokens"].to_i + usage.cached_tokens,
|
|
126
|
+
"cache_write_tokens" => current["cache_write_tokens"].to_i + usage.cache_write_tokens,
|
|
126
127
|
"total_tokens" => current["total_tokens"].to_i + usage.total_tokens
|
|
127
128
|
}
|
|
128
|
-
|
|
129
|
+
attributes = { usage: totals, heartbeat_at: Clock.now }
|
|
130
|
+
attributes[:cost] = @record["cost"].to_f + usage.cost.to_f if usage.cost
|
|
131
|
+
update!(attributes)
|
|
129
132
|
end
|
|
130
133
|
|
|
131
134
|
def update!(attributes)
|
data/lib/turnkit/usage.rb
CHANGED
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module TurnKit
|
|
4
4
|
class Usage
|
|
5
|
-
attr_reader :input_tokens, :output_tokens, :cached_tokens, :cost
|
|
5
|
+
attr_reader :input_tokens, :output_tokens, :cached_tokens, :cache_write_tokens, :cost
|
|
6
6
|
|
|
7
|
-
def initialize(input_tokens: 0, output_tokens: 0, cached_tokens: 0, cost: nil)
|
|
7
|
+
def initialize(input_tokens: 0, output_tokens: 0, cached_tokens: 0, cache_write_tokens: 0, cost: nil)
|
|
8
8
|
@input_tokens = input_tokens.to_i
|
|
9
9
|
@output_tokens = output_tokens.to_i
|
|
10
10
|
@cached_tokens = cached_tokens.to_i
|
|
11
|
+
@cache_write_tokens = cache_write_tokens.to_i
|
|
11
12
|
@cost = cost
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def total_tokens
|
|
15
|
-
input_tokens + output_tokens + cached_tokens
|
|
16
|
+
input_tokens + output_tokens + cached_tokens + cache_write_tokens
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def to_h
|
|
@@ -20,6 +21,7 @@ module TurnKit
|
|
|
20
21
|
"input_tokens" => input_tokens,
|
|
21
22
|
"output_tokens" => output_tokens,
|
|
22
23
|
"cached_tokens" => cached_tokens,
|
|
24
|
+
"cache_write_tokens" => cache_write_tokens,
|
|
23
25
|
"total_tokens" => total_tokens,
|
|
24
26
|
"cost" => cost
|
|
25
27
|
}.compact
|
data/lib/turnkit/version.rb
CHANGED
data/lib/turnkit.rb
CHANGED
|
@@ -41,7 +41,7 @@ module TurnKit
|
|
|
41
41
|
class << self
|
|
42
42
|
attr_accessor :default_model, :client, :store, :logger
|
|
43
43
|
attr_accessor :max_iterations, :timeout, :max_depth, :max_tool_executions
|
|
44
|
-
attr_accessor :cost_limit
|
|
44
|
+
attr_accessor :cost_limit, :prompt_cache
|
|
45
45
|
attr_accessor :prompt_sections, :prompt_behavior, :available_skills
|
|
46
46
|
attr_accessor :prompt_data_max_chars, :context_contributors
|
|
47
47
|
attr_accessor :system_prompt_contributors, :model_prompt_contributors
|
|
@@ -56,6 +56,7 @@ module TurnKit
|
|
|
56
56
|
self.timeout = 300
|
|
57
57
|
self.max_depth = 3
|
|
58
58
|
self.max_tool_executions = 100
|
|
59
|
+
self.prompt_cache = :auto
|
|
59
60
|
self.prompt_sections = SystemPrompt::DEFAULT_SECTIONS.dup
|
|
60
61
|
self.prompt_data_max_chars = 20_000
|
|
61
62
|
self.available_skills = []
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: turnkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Couch
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby_llm
|