turnkit 0.2.3 → 0.2.4

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: 2c02ad5eef683595c702a33806438f414ed2da9e18c607a8b314bba4ae442404
4
- data.tar.gz: 4da3877b7c20aecae1dd77e6df4497bb64a3909d28419fb1413feb37fa5fa298
3
+ metadata.gz: 75121664c1e081304931fbf125db92a9abc8b9062f920c7e33f7759b52ce51ec
4
+ data.tar.gz: ccabe905d199d955d281c936a019995a3bd9bc29c0fc009160ea924de4605835
5
5
  SHA512:
6
- metadata.gz: b5de4c365826d8a4154d2ee013fe0f7289796b91b63eb34ad81693993eb55b8f8d0282f8415e7798f9eb698d2f6f4aa52b79949e1c89c0c64effe506cf26ef0b
7
- data.tar.gz: b168324cf4f97485ce7854006565441fd0fe67e1f84835805d98d67f27a2a793fe2ce8bd27a6939c6ccbf3cc92023bc93c8aff5e8049fb0b2991a50548d211d6
6
+ metadata.gz: ff0fa50aabb4c4b4fd9ea6f3ae78b62a4b020522a083f96605028dca2f4ca50a4fb6a9b98b36070e070d38a36b205ebf343823b520f5b0e5b4fe7a06b643cdce
7
+ data.tar.gz: beec35d2fc1f51cc6fe674d12d72e0ec1b44722bdcfab28019e9ab2d2ae313c684125989647e6d5d389f80b2df5f98dd33aa3c154e0af7da0885d2b8bec0221c
data/CHANGELOG.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## 0.2.3 - 2026-06-06
3
+ ## 0.2.4 - 2026-06-06
4
4
 
5
5
  - Add Anthropic prompt cache support for stable system prompt sections.
6
- - Track cache write tokens and aggregate model costs on turns.
6
+ - Track cache write tokens and expose model cost totals for turns, conversations, and agents.
7
+ - Calculate costs from RubyLLM model registry pricing with custom rate and calculator overrides.
7
8
  - Refresh README usage examples for prompt caching and usage tracking.
8
9
 
9
10
  ## 0.2.0 - 2026-06-04
data/README.md CHANGED
@@ -37,14 +37,20 @@ agent = TurnKit::Agent.new(
37
37
  name: "helper",
38
38
  instructions: "Answer briefly."
39
39
  )
40
+ ```
41
+
42
+ Ask a question:
40
43
 
44
+ ```ruby
41
45
  turn = agent.conversation.ask("Explain Ruby blocks in one sentence.")
42
46
  puts turn.output_text
43
47
  ```
44
48
 
45
49
  ## Usage
46
50
 
47
- Choose a model:
51
+ ### Models
52
+
53
+ Set the default model:
48
54
 
49
55
  ```ruby
50
56
  TurnKit.default_model = "claude-sonnet-4-5"
@@ -56,10 +62,14 @@ Use OpenAI:
56
62
  export OPENAI_API_KEY=...
57
63
  ```
58
64
 
65
+ Set an OpenAI model:
66
+
59
67
  ```ruby
60
68
  TurnKit.default_model = "gpt-4.1-mini"
61
69
  ```
62
70
 
71
+ ### Conversations
72
+
63
73
  Create a conversation:
64
74
 
65
75
  ```ruby
@@ -67,14 +77,24 @@ agent = TurnKit::Agent.new(
67
77
  name: "writer",
68
78
  instructions: "Write clear release notes."
69
79
  )
80
+ ```
70
81
 
82
+ Add context:
83
+
84
+ ```ruby
71
85
  conversation = agent.conversation(subject: "v1 launch")
72
86
  conversation.say("Mention faster tool execution.")
87
+ ```
88
+
89
+ Run the agent:
73
90
 
91
+ ```ruby
74
92
  turn = conversation.run!
75
93
  puts turn.output_text
76
94
  ```
77
95
 
96
+ ### Tools
97
+
78
98
  Create a tool:
79
99
 
80
100
  ```ruby
@@ -93,7 +113,7 @@ class SaveReport < TurnKit::Tool
93
113
  end
94
114
  ```
95
115
 
96
- Use a tool:
116
+ Use the tool:
97
117
 
98
118
  ```ruby
99
119
  agent = TurnKit::Agent.new(
@@ -101,40 +121,115 @@ agent = TurnKit::Agent.new(
101
121
  instructions: "Save reports when asked.",
102
122
  tools: [SaveReport]
103
123
  )
124
+ ```
125
+
126
+ Ask for tool use:
104
127
 
128
+ ```ruby
105
129
  turn = agent.conversation.ask("Save a short status report.")
106
130
  puts turn.output_text
107
131
  ```
108
132
 
109
- Add skills:
133
+ ### Skills
134
+
135
+ Load a skill:
110
136
 
111
137
  ```ruby
112
138
  skill = TurnKit::Skill.from_file("skills/research.md")
139
+ ```
140
+
141
+ Use the skill:
113
142
 
143
+ ```ruby
114
144
  agent = TurnKit::Agent.new(
115
145
  name: "researcher",
116
146
  skills: [skill]
117
147
  )
118
148
  ```
119
149
 
120
- Delegate to sub-agents:
150
+ ### Sub-agents
151
+
152
+ Create a sub-agent:
121
153
 
122
154
  ```ruby
123
155
  writer = TurnKit::Agent.new(
124
156
  name: "writer",
125
157
  description: "Draft concise copy."
126
158
  )
159
+ ```
160
+
161
+ Delegate to it:
127
162
 
163
+ ```ruby
128
164
  editor = TurnKit::Agent.new(
129
165
  name: "editor",
130
166
  sub_agents: [writer]
131
167
  )
168
+ ```
132
169
 
170
+ Ask the parent agent:
171
+
172
+ ```ruby
133
173
  turn = editor.conversation.ask("Ask the writer for three headlines.")
134
174
  puts turn.output_text
135
175
  ```
136
176
 
137
- Use prompt caching:
177
+ ### Usage and costs
178
+
179
+ Inspect token usage:
180
+
181
+ ```ruby
182
+ turn.usage.total_tokens
183
+ conversation.usage.total_tokens
184
+ agent.usage.total_tokens
185
+ ```
186
+
187
+ Inspect costs:
188
+
189
+ ```ruby
190
+ turn.cost.total
191
+ conversation.cost.total
192
+ agent.cost.total
193
+ ```
194
+
195
+ Use RubyLLM registry prices by default.
196
+
197
+ Override model rates:
198
+
199
+ ```ruby
200
+ TurnKit.cost_rates = {
201
+ "my-model" => {
202
+ input: 0.25,
203
+ output: 1.00,
204
+ cached_input: 0.05,
205
+ cache_creation: 0.25
206
+ }
207
+ }
208
+ ```
209
+
210
+ Override cost calculation:
211
+
212
+ ```ruby
213
+ TurnKit.cost_calculator = ->(usage, model) do
214
+ {
215
+ input: usage.input_tokens * 0.25 / 1_000_000.0,
216
+ output: usage.output_tokens * 1.00 / 1_000_000.0
217
+ }
218
+ end
219
+ ```
220
+
221
+ Limit turn cost:
222
+
223
+ ```ruby
224
+ agent = TurnKit::Agent.new(
225
+ name: "analyst",
226
+ cost_limit: 0.25
227
+ )
228
+ ```
229
+
230
+ ### Prompt caching
231
+
232
+ Enable prompt caching:
138
233
 
139
234
  ```ruby
140
235
  TurnKit.prompt_cache = :auto
@@ -159,14 +254,9 @@ agent = TurnKit::Agent.new(
159
254
  )
160
255
  ```
161
256
 
162
- Inspect usage:
163
-
164
- ```ruby
165
- record = TurnKit.store.load_turn(turn.id)
166
- record.fetch("usage")
167
- ```
257
+ ### Custom clients
168
258
 
169
- Return usage from custom clients:
259
+ Create a client:
170
260
 
171
261
  ```ruby
172
262
  class MyClient < TurnKit::Client
@@ -185,22 +275,20 @@ class MyClient < TurnKit::Client
185
275
  end
186
276
  ```
187
277
 
188
- Split instructions inside custom clients:
278
+ Use the client:
189
279
 
190
280
  ```ruby
191
- stable, dynamic = TurnKit::SystemPrompt.split_cache_boundary(instructions)
281
+ TurnKit.client = MyClient.new
192
282
  ```
193
283
 
194
- Send `stable` with provider cache controls.
195
-
196
- Send `dynamic` as normal prompt content.
197
-
198
- Use a custom client:
284
+ Split cache sections:
199
285
 
200
286
  ```ruby
201
- TurnKit.client = MyClient.new
287
+ stable, dynamic = TurnKit::SystemPrompt.split_cache_boundary(instructions)
202
288
  ```
203
289
 
290
+ ### Rails
291
+
204
292
  Install Rails persistence:
205
293
 
206
294
  ```sh
@@ -217,7 +305,6 @@ Configure Rails:
217
305
 
218
306
  ```ruby
219
307
  TurnKit.store = TurnKit::ActiveRecordStore.new
220
- TurnKit.default_model = "claude-sonnet-4-5"
221
308
  ```
222
309
 
223
310
  Reconcile stale turns:
@@ -237,6 +324,8 @@ TurnKit.timeout = 300
237
324
  TurnKit.max_depth = 3
238
325
  TurnKit.max_tool_executions = 100
239
326
  TurnKit.cost_limit = nil
327
+ TurnKit.cost_rates = {}
328
+ TurnKit.cost_calculator = nil
240
329
  TurnKit.prompt_cache = :auto
241
330
  ```
242
331
 
@@ -259,11 +348,11 @@ agent = TurnKit::Agent.new(
259
348
  | `store` | Set the conversation store. |
260
349
  | `max_iterations` | Limit model calls per turn. |
261
350
  | `timeout` | Limit seconds per root turn. |
262
- | `max_depth` | Limit sub-agent nesting. |
263
351
  | `max_tool_executions` | Limit tool calls per root turn. |
264
352
  | `cost_limit` | Limit cost per root turn. |
353
+ | `cost_rates` | Override prices by model. |
354
+ | `cost_calculator` | Override cost calculation. |
265
355
  | `prompt_cache` | Use provider prompt caching. |
266
- | `prompt_sections` | Set default prompt sections. |
267
356
 
268
357
  ## Contributing
269
358
 
@@ -122,7 +122,8 @@ module TurnKit
122
122
  input_tokens: token_value(response, :input_tokens),
123
123
  output_tokens: token_value(response, :output_tokens),
124
124
  cached_tokens: token_value(response, :cached_tokens),
125
- cache_write_tokens: token_value(response, :cache_creation_tokens)
125
+ cache_write_tokens: token_value(response, :cache_creation_tokens),
126
+ cost: response_cost(response)
126
127
  )
127
128
  Result.new(
128
129
  text: response.respond_to?(:content) ? response.content.to_s : response.to_s,
@@ -135,6 +136,12 @@ module TurnKit
135
136
  def token_value(response, method)
136
137
  response.respond_to?(method) ? response.public_send(method).to_i : 0
137
138
  end
139
+
140
+ def response_cost(response)
141
+ return unless response.respond_to?(:cost)
142
+
143
+ response.cost&.total
144
+ end
138
145
  end
139
146
  end
140
147
  end
data/lib/turnkit/agent.rb CHANGED
@@ -41,6 +41,14 @@ module TurnKit
41
41
  Conversation.new(agent: self, record: record, store: store, model: model || effective_model, subject: subject, metadata: metadata)
42
42
  end
43
43
 
44
+ def cost
45
+ Cost.from_records(effective_store.list_turns(agent_name: name))
46
+ end
47
+
48
+ def usage
49
+ Usage.from_records(effective_store.list_turns(agent_name: name))
50
+ end
51
+
44
52
  def effective_model
45
53
  model || TurnKit.default_model
46
54
  end
@@ -32,10 +32,14 @@ module TurnKit
32
32
  end
33
33
 
34
34
  def add_usage!(usage)
35
- return unless usage&.cost && cost_limit
35
+ add_cost!(usage&.cost)
36
+ end
37
+
38
+ def add_cost!(cost)
39
+ return unless cost && cost_limit
36
40
 
37
41
  @mutex.synchronize do
38
- @cost += usage.cost.to_f
42
+ @cost += cost.to_f
39
43
  raise Error, "cost limit reached" if @cost > cost_limit
40
44
  end
41
45
  end
@@ -48,6 +48,14 @@ module TurnKit
48
48
  store.list_messages(id).map { |attrs| Message.new(attrs) }
49
49
  end
50
50
 
51
+ def usage
52
+ Usage.from_records(store.list_turns(conversation_id: id))
53
+ end
54
+
55
+ def cost
56
+ Cost.from_records(store.list_turns(conversation_id: id))
57
+ end
58
+
51
59
  def messages_for_turn(turn)
52
60
  store.list_messages(id, through_sequence: turn.context_message_sequence, turn_id: turn.id).map { |attrs| Message.new(attrs) }
53
61
  end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurnKit
4
+ class Cost
5
+ COMPONENTS = %i[input output cache_read cache_write].freeze
6
+ PER_MILLION = 1_000_000.0
7
+
8
+ attr_reader :input, :output, :cache_read, :cache_write
9
+
10
+ def self.aggregate(costs)
11
+ costs = costs.compact
12
+ return new unless costs.any?
13
+
14
+ if costs.any? { |cost| COMPONENTS.any? { |component| !cost.public_send(component).nil? } }
15
+ values = COMPONENTS.to_h do |component|
16
+ amounts = costs.filter_map { |cost| cost.public_send(component) }
17
+ [ component, amounts.any? ? amounts.sum : nil ]
18
+ end
19
+ return new(**values)
20
+ end
21
+
22
+ totals = costs.map(&:total)
23
+ return new(total: totals.sum) if totals.none?(&:nil?)
24
+
25
+ new
26
+ end
27
+
28
+ def self.from_usage(usage, model: nil)
29
+ return new(total: usage.cost) if usage.cost
30
+
31
+ custom = custom_cost(usage, model)
32
+ return custom if custom
33
+
34
+ rates = TurnKit.cost_rates[model.to_s] || TurnKit.cost_rates[model&.to_sym]
35
+ rates ? from_rates(usage, rates) : from_ruby_llm(usage, model)
36
+ end
37
+
38
+ def self.from_records(records)
39
+ aggregate(records.map { |record| from_record(record) })
40
+ end
41
+
42
+ def self.from_record(record)
43
+ attrs = record.transform_keys(&:to_s)
44
+ usage = attrs["usage"] || {}
45
+ return from_hash(usage["cost_details"] || usage[:cost_details]) if usage["cost_details"] || usage[:cost_details]
46
+ return new(total: attrs["cost"]) if attrs["cost"]
47
+
48
+ from_usage(Usage.from_h(usage), model: attrs["model"])
49
+ end
50
+
51
+ def self.from_rates(usage, rates)
52
+ rates = rates.transform_keys(&:to_sym)
53
+ new(
54
+ input: amount(usage.input_tokens, rates[:input] || rates[:input_per_million]),
55
+ output: amount(usage.output_tokens, rates[:output] || rates[:output_per_million]),
56
+ cache_read: amount(usage.cached_tokens, rates[:cache_read] || rates[:cached_input] || rates[:cache_read_input_per_million] || rates[:cached_input_per_million]),
57
+ cache_write: amount(usage.cache_write_tokens, rates[:cache_write] || rates[:cache_creation] || rates[:cache_write_input_per_million] || rates[:cache_creation_input_per_million]),
58
+ strict: true
59
+ )
60
+ end
61
+
62
+ def self.from_ruby_llm(usage, model)
63
+ require "ruby_llm"
64
+
65
+ model_info = ::RubyLLM.models.find(model) if model
66
+ return new unless model_info
67
+
68
+ if defined?(::RubyLLM::Cost)
69
+ tokens = ::RubyLLM::Tokens.new(
70
+ input: usage.input_tokens,
71
+ output: usage.output_tokens,
72
+ cached: usage.cached_tokens,
73
+ cache_creation: usage.cache_write_tokens
74
+ )
75
+ from_hash(::RubyLLM::Cost.new(tokens: tokens, model: model_info).to_h)
76
+ else
77
+ from_rates(
78
+ usage,
79
+ input: model_info.input_price_per_million,
80
+ output: model_info.output_price_per_million,
81
+ cached_input: model_info.pricing&.text_tokens&.cached_input
82
+ )
83
+ end
84
+ rescue LoadError, StandardError
85
+ new
86
+ end
87
+
88
+ def self.from_hash(hash)
89
+ hash = hash.transform_keys(&:to_sym)
90
+ new(
91
+ input: hash[:input],
92
+ output: hash[:output],
93
+ cache_read: hash[:cache_read] || hash[:cached_input],
94
+ cache_write: hash[:cache_write] || hash[:cache_creation],
95
+ total: hash[:total]
96
+ )
97
+ end
98
+
99
+ def self.custom_cost(usage, model)
100
+ return unless TurnKit.cost_calculator
101
+
102
+ value = TurnKit.cost_calculator.call(usage, model)
103
+ case value
104
+ when nil
105
+ nil
106
+ when Cost
107
+ value
108
+ when Hash
109
+ from_hash(value)
110
+ else
111
+ new(total: value)
112
+ end
113
+ end
114
+
115
+ def self.amount(tokens, price)
116
+ return nil if tokens.to_i.positive? && price.nil?
117
+ return 0.0 if tokens.to_i.zero?
118
+
119
+ tokens.to_i * price.to_f / PER_MILLION
120
+ end
121
+
122
+ def initialize(input: nil, output: nil, cache_read: nil, cache_write: nil, total: nil, strict: false)
123
+ @input = number(input)
124
+ @output = number(output)
125
+ @cache_read = number(cache_read)
126
+ @cache_write = number(cache_write)
127
+ @total = number(total)
128
+ @strict = strict
129
+ end
130
+
131
+ def total
132
+ return @total if @total
133
+ return nil if @strict && COMPONENTS.any? { |component| public_send(component).nil? }
134
+
135
+ values = COMPONENTS.filter_map { |component| public_send(component) }
136
+ values.empty? ? nil : values.sum
137
+ end
138
+
139
+ def to_h
140
+ {
141
+ "input" => input,
142
+ "output" => output,
143
+ "cache_read" => cache_read,
144
+ "cache_write" => cache_write,
145
+ "total" => total
146
+ }.compact
147
+ end
148
+
149
+ private
150
+ def number(value)
151
+ value.nil? ? nil : value.to_f
152
+ end
153
+ end
154
+ end
@@ -68,11 +68,12 @@ module TurnKit
68
68
  end
69
69
  end
70
70
 
71
- def list_turns(root_turn_id: nil, conversation_id: nil)
71
+ def list_turns(root_turn_id: nil, conversation_id: nil, agent_name: nil)
72
72
  @mutex.synchronize do
73
73
  rows = @turns.values
74
74
  rows = rows.select { |turn| turn["root_turn_id"] == root_turn_id } if root_turn_id
75
75
  rows = rows.select { |turn| turn["conversation_id"] == conversation_id } if conversation_id
76
+ rows = rows.select { |turn| turn["agent_name"] == agent_name } if agent_name
76
77
  rows.sort_by { |turn| [ turn["created_at"].to_f, turn["id"] ] }.map { |turn| duplicate(turn) }
77
78
  end
78
79
  end
data/lib/turnkit/store.rb CHANGED
@@ -12,7 +12,7 @@ module TurnKit
12
12
  def create_turn(_attributes) = raise(NotImplementedError)
13
13
  def load_turn(_id) = raise(NotImplementedError)
14
14
  def update_turn(_id, _attributes) = raise(NotImplementedError)
15
- def list_turns(root_turn_id: nil, conversation_id: nil) = raise(NotImplementedError)
15
+ def list_turns(root_turn_id: nil, conversation_id: nil, agent_name: nil) = raise(NotImplementedError)
16
16
 
17
17
  def create_tool_execution(_attributes) = raise(NotImplementedError)
18
18
  def load_tool_execution(_id) = raise(NotImplementedError)
@@ -89,10 +89,11 @@ module TurnKit
89
89
  turn_hash(record)
90
90
  end
91
91
 
92
- def list_turns(root_turn_id: nil, conversation_id: nil)
92
+ def list_turns(root_turn_id: nil, conversation_id: nil, agent_name: nil)
93
93
  scope = turn_class.all
94
94
  scope = scope.where(root_turn_uid: root_turn_id) if root_turn_id
95
95
  scope = scope.where(conversation_uid: conversation_id) if conversation_id
96
+ scope = scope.where(agent_name: agent_name) if agent_name
96
97
  scope.order(:created_at, :uid).map { |record| turn_hash(record) }
97
98
  end
98
99
 
data/lib/turnkit/turn.rb CHANGED
@@ -42,9 +42,10 @@ module TurnKit
42
42
  instructions: agent.system_prompt_for(turn: self, conversation: conversation),
43
43
  metadata: { turn_id: id, conversation_id: conversation.id }
44
44
  )
45
+ result_cost = Cost.from_usage(result.usage, model: result.model || model)
45
46
 
46
- budget.add_usage!(result.usage)
47
- add_usage!(result.usage)
47
+ budget.add_cost!(result_cost.total)
48
+ add_usage!(result.usage, cost: result_cost)
48
49
  persist_assistant_message(result)
49
50
 
50
51
  if result.tool_calls?
@@ -79,6 +80,14 @@ module TurnKit
79
80
  @record["output_text"].to_s
80
81
  end
81
82
 
83
+ def usage
84
+ Usage.from_h(@record["usage"] || {})
85
+ end
86
+
87
+ def cost
88
+ Cost.from_record(@record)
89
+ end
90
+
82
91
  def tool_executions
83
92
  store.list_tool_executions(turn_id: id).map { |attrs| ToolExecution.new(attrs) }
84
93
  end
@@ -117,7 +126,7 @@ module TurnKit
117
126
  update!(status: "completed", output_text: message, completed_at: Clock.now)
118
127
  end
119
128
 
120
- def add_usage!(usage)
129
+ def add_usage!(usage, cost: nil)
121
130
  current = @record["usage"] || {}
122
131
  totals = {
123
132
  "input_tokens" => current["input_tokens"].to_i + usage.input_tokens,
@@ -126,11 +135,18 @@ module TurnKit
126
135
  "cache_write_tokens" => current["cache_write_tokens"].to_i + usage.cache_write_tokens,
127
136
  "total_tokens" => current["total_tokens"].to_i + usage.total_tokens
128
137
  }
138
+ totals["cost_details"] = aggregate_cost(current["cost_details"], cost).to_h if cost&.total
129
139
  attributes = { usage: totals, heartbeat_at: Clock.now }
130
- attributes[:cost] = @record["cost"].to_f + usage.cost.to_f if usage.cost
140
+ attributes[:cost] = @record["cost"].to_f + cost.total if cost&.total
131
141
  update!(attributes)
132
142
  end
133
143
 
144
+ def aggregate_cost(current, cost)
145
+ return cost unless current
146
+
147
+ Cost.aggregate([ Cost.from_hash(current), cost ])
148
+ end
149
+
134
150
  def update!(attributes)
135
151
  @record = store.update_turn(id, attributes)
136
152
  @started_at = @record["started_at"]
data/lib/turnkit/usage.rb CHANGED
@@ -4,6 +4,35 @@ module TurnKit
4
4
  class Usage
5
5
  attr_reader :input_tokens, :output_tokens, :cached_tokens, :cache_write_tokens, :cost
6
6
 
7
+ def self.aggregate(usages)
8
+ usages = usages.compact
9
+ costs = usages.map(&:cost).compact
10
+ cost = costs.sum if costs.any?
11
+ new(
12
+ input_tokens: usages.sum(&:input_tokens),
13
+ output_tokens: usages.sum(&:output_tokens),
14
+ cached_tokens: usages.sum(&:cached_tokens),
15
+ cache_write_tokens: usages.sum(&:cache_write_tokens),
16
+ cost: cost
17
+ )
18
+ end
19
+
20
+ def self.from_records(records)
21
+ aggregate(records.map { |record| from_h(record.fetch("usage", {})) })
22
+ end
23
+
24
+ def self.from_h(hash)
25
+ attrs = hash.transform_keys(&:to_s)
26
+ cost = attrs["cost"] unless attrs["cost"].is_a?(Hash)
27
+ new(
28
+ input_tokens: attrs["input_tokens"],
29
+ output_tokens: attrs["output_tokens"],
30
+ cached_tokens: attrs["cached_tokens"],
31
+ cache_write_tokens: attrs["cache_write_tokens"],
32
+ cost: cost
33
+ )
34
+ end
35
+
7
36
  def initialize(input_tokens: 0, output_tokens: 0, cached_tokens: 0, cache_write_tokens: 0, cost: nil)
8
37
  @input_tokens = input_tokens.to_i
9
38
  @output_tokens = output_tokens.to_i
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TurnKit
4
- VERSION = "0.2.3"
4
+ VERSION = "0.2.4"
5
5
  end
data/lib/turnkit.rb CHANGED
@@ -10,6 +10,7 @@ require_relative "turnkit/version"
10
10
  require_relative "turnkit/error"
11
11
  require_relative "turnkit/id"
12
12
  require_relative "turnkit/clock"
13
+ require_relative "turnkit/cost"
13
14
  require_relative "turnkit/budget"
14
15
  require_relative "turnkit/agent"
15
16
  require_relative "turnkit/client"
@@ -42,6 +43,7 @@ module TurnKit
42
43
  attr_accessor :default_model, :client, :store, :logger
43
44
  attr_accessor :max_iterations, :timeout, :max_depth, :max_tool_executions
44
45
  attr_accessor :cost_limit, :prompt_cache
46
+ attr_accessor :cost_rates, :cost_calculator
45
47
  attr_accessor :prompt_sections, :prompt_behavior, :available_skills
46
48
  attr_accessor :prompt_data_max_chars, :context_contributors
47
49
  attr_accessor :system_prompt_contributors, :model_prompt_contributors
@@ -57,6 +59,7 @@ module TurnKit
57
59
  self.max_depth = 3
58
60
  self.max_tool_executions = 100
59
61
  self.prompt_cache = :auto
62
+ self.cost_rates = {}
60
63
  self.prompt_sections = SystemPrompt::DEFAULT_SECTIONS.dup
61
64
  self.prompt_data_max_chars = 20_000
62
65
  self.available_skills = []
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turnkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Couch
@@ -43,6 +43,7 @@ files:
43
43
  - lib/turnkit/client.rb
44
44
  - lib/turnkit/clock.rb
45
45
  - lib/turnkit/conversation.rb
46
+ - lib/turnkit/cost.rb
46
47
  - lib/turnkit/error.rb
47
48
  - lib/turnkit/generators/turnkit/install/templates/conversation.rb
48
49
  - lib/turnkit/generators/turnkit/install/templates/create_turnkit_tables.rb