@namzu/sdk 62.5.0 → 62.6.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 (58) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -1
  3. package/dist/agents/file-definitions.d.ts +1 -1
  4. package/dist/agents/file-definitions.js +1 -1
  5. package/dist/agents/runAgent.d.ts +1 -1
  6. package/dist/agents/runAgent.js +1 -1
  7. package/dist/compaction/model-summary.d.ts.map +1 -1
  8. package/dist/compaction/model-summary.js +2 -1
  9. package/dist/compaction/model-summary.js.map +1 -1
  10. package/dist/compaction/verifier.d.ts.map +1 -1
  11. package/dist/compaction/verifier.js +2 -1
  12. package/dist/compaction/verifier.js.map +1 -1
  13. package/dist/eval/judge.d.ts.map +1 -1
  14. package/dist/eval/judge.js +2 -1
  15. package/dist/eval/judge.js.map +1 -1
  16. package/dist/pricing/catalogue.generated.d.ts.map +1 -1
  17. package/dist/pricing/catalogue.generated.js +19 -0
  18. package/dist/pricing/catalogue.generated.js.map +1 -1
  19. package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -1
  20. package/dist/runtime/query/auto-review/classifier.js +2 -1
  21. package/dist/runtime/query/auto-review/classifier.js.map +1 -1
  22. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  23. package/dist/runtime/query/iteration/index.js +3 -0
  24. package/dist/runtime/query/iteration/index.js.map +1 -1
  25. package/dist/runtime/query/iteration/request-repair.d.ts +9 -1
  26. package/dist/runtime/query/iteration/request-repair.d.ts.map +1 -1
  27. package/dist/runtime/query/iteration/request-repair.js +29 -0
  28. package/dist/runtime/query/iteration/request-repair.js.map +1 -1
  29. package/dist/runtime/query/iteration/side-call.d.ts +16 -0
  30. package/dist/runtime/query/iteration/side-call.d.ts.map +1 -0
  31. package/dist/runtime/query/iteration/side-call.js +50 -0
  32. package/dist/runtime/query/iteration/side-call.js.map +1 -0
  33. package/dist/types/common/index.d.ts +31 -0
  34. package/dist/types/common/index.d.ts.map +1 -1
  35. package/dist/types/common/index.js +18 -2
  36. package/dist/types/common/index.js.map +1 -1
  37. package/dist/types/session/events.d.ts +10 -0
  38. package/dist/types/session/events.d.ts.map +1 -1
  39. package/dist/types/session/events.js.map +1 -1
  40. package/dist/utils/cost.d.ts +6 -0
  41. package/dist/utils/cost.d.ts.map +1 -1
  42. package/dist/utils/cost.js +19 -0
  43. package/dist/utils/cost.js.map +1 -1
  44. package/package.json +2 -2
  45. package/src/agents/file-definitions.ts +1 -1
  46. package/src/agents/runAgent.ts +1 -1
  47. package/src/compaction/model-summary.ts +2 -1
  48. package/src/compaction/verifier.ts +2 -1
  49. package/src/eval/judge.ts +2 -1
  50. package/src/pricing/catalogue.generated.ts +19 -0
  51. package/src/pricing/rates.source.json +7 -0
  52. package/src/runtime/query/auto-review/classifier.ts +2 -1
  53. package/src/runtime/query/iteration/index.ts +3 -0
  54. package/src/runtime/query/iteration/request-repair.ts +42 -1
  55. package/src/runtime/query/iteration/side-call.ts +60 -0
  56. package/src/types/common/index.ts +54 -2
  57. package/src/types/session/events.ts +7 -0
  58. package/src/utils/cost.ts +26 -0
@@ -33,6 +33,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
33
33
  inputCostPer1M: 10,
34
34
  outputCostPer1M: 50,
35
35
  cache: { promptIncludesCacheReads: false, readCostPer1M: 1, writeCostPer1M: 12.5 },
36
+ webSearchCostPer1000: 10,
36
37
  },
37
38
  ],
38
39
  [
@@ -41,6 +42,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
41
42
  inputCostPer1M: 10,
42
43
  outputCostPer1M: 50,
43
44
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.25, writeCostPer1M: 12.5 },
45
+ webSearchCostPer1000: 10,
44
46
  },
45
47
  ],
46
48
  [
@@ -50,6 +52,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
50
52
  outputCostPer1M: 40,
51
53
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.2, writeCostPer1M: 10 },
52
54
  fastMultiplier: 2,
55
+ webSearchCostPer1000: 10,
53
56
  },
54
57
  ],
55
58
  [
@@ -58,6 +61,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
58
61
  inputCostPer1M: 1,
59
62
  outputCostPer1M: 5,
60
63
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.1, writeCostPer1M: 1.25 },
64
+ webSearchCostPer1000: 10,
61
65
  },
62
66
  ],
63
67
  [
@@ -66,6 +70,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
66
70
  inputCostPer1M: 10,
67
71
  outputCostPer1M: 50,
68
72
  cache: { promptIncludesCacheReads: false, readCostPer1M: 1, writeCostPer1M: 12.5 },
73
+ webSearchCostPer1000: 10,
69
74
  },
70
75
  ],
71
76
  [
@@ -74,6 +79,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
74
79
  inputCostPer1M: 10,
75
80
  outputCostPer1M: 50,
76
81
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.25, writeCostPer1M: 12.5 },
82
+ webSearchCostPer1000: 10,
77
83
  },
78
84
  ],
79
85
  [
@@ -83,6 +89,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
83
89
  outputCostPer1M: 40,
84
90
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.2, writeCostPer1M: 10 },
85
91
  fastMultiplier: 2,
92
+ webSearchCostPer1000: 10,
86
93
  },
87
94
  ],
88
95
  [
@@ -91,6 +98,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
91
98
  inputCostPer1M: 15,
92
99
  outputCostPer1M: 75,
93
100
  cache: { promptIncludesCacheReads: false, readCostPer1M: 1.5, writeCostPer1M: 18.75 },
101
+ webSearchCostPer1000: 10,
94
102
  },
95
103
  ],
96
104
  [
@@ -99,6 +107,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
99
107
  inputCostPer1M: 15,
100
108
  outputCostPer1M: 75,
101
109
  cache: { promptIncludesCacheReads: false, readCostPer1M: 1.5, writeCostPer1M: 18.75 },
110
+ webSearchCostPer1000: 10,
102
111
  },
103
112
  ],
104
113
  [
@@ -107,6 +116,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
107
116
  inputCostPer1M: 5,
108
117
  outputCostPer1M: 25,
109
118
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.5, writeCostPer1M: 6.25 },
119
+ webSearchCostPer1000: 10,
110
120
  },
111
121
  ],
112
122
  [
@@ -115,6 +125,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
115
125
  inputCostPer1M: 5,
116
126
  outputCostPer1M: 25,
117
127
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.5, writeCostPer1M: 6.25 },
128
+ webSearchCostPer1000: 10,
118
129
  },
119
130
  ],
120
131
  [
@@ -124,6 +135,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
124
135
  outputCostPer1M: 25,
125
136
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.5, writeCostPer1M: 6.25 },
126
137
  fastMultiplier: 2,
138
+ webSearchCostPer1000: 10,
127
139
  },
128
140
  ],
129
141
  [
@@ -133,6 +145,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
133
145
  outputCostPer1M: 25,
134
146
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.5, writeCostPer1M: 6.25 },
135
147
  fastMultiplier: 2,
148
+ webSearchCostPer1000: 10,
136
149
  },
137
150
  ],
138
151
  [
@@ -142,6 +155,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
142
155
  outputCostPer1M: 20,
143
156
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.2, writeCostPer1M: 5 },
144
157
  fastMultiplier: 2,
158
+ webSearchCostPer1000: 10,
145
159
  },
146
160
  ],
147
161
  [
@@ -150,6 +164,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
150
164
  inputCostPer1M: 3,
151
165
  outputCostPer1M: 15,
152
166
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.3, writeCostPer1M: 3.75 },
167
+ webSearchCostPer1000: 10,
153
168
  },
154
169
  ],
155
170
  [
@@ -158,6 +173,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
158
173
  inputCostPer1M: 3,
159
174
  outputCostPer1M: 15,
160
175
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.3, writeCostPer1M: 3.75 },
176
+ webSearchCostPer1000: 10,
161
177
  },
162
178
  ],
163
179
  [
@@ -166,6 +182,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
166
182
  inputCostPer1M: 3,
167
183
  outputCostPer1M: 15,
168
184
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.3, writeCostPer1M: 3.75 },
185
+ webSearchCostPer1000: 10,
169
186
  },
170
187
  ],
171
188
  [
@@ -174,6 +191,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
174
191
  inputCostPer1M: 2,
175
192
  outputCostPer1M: 10,
176
193
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.2, writeCostPer1M: 2.5 },
194
+ webSearchCostPer1000: 10,
177
195
  },
178
196
  ],
179
197
  [
@@ -182,6 +200,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
182
200
  inputCostPer1M: 2,
183
201
  outputCostPer1M: 10,
184
202
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.1, writeCostPer1M: 2.5 },
203
+ webSearchCostPer1000: 10,
185
204
  },
186
205
  ],
187
206
  ]),
@@ -24,6 +24,12 @@
24
24
  "2x on the full context window, stacking with cache pricing). A model without it",
25
25
  "has no published fast price, so a fast request on it is reported as unpriced.",
26
26
  "",
27
+ "webSearchPer1000 (optional, per vendor) is the price in USD of 1,000 hosted web",
28
+ "searches, billed per request on top of tokens (verified 2026-11-10 against the",
29
+ "pricing page's web search section: $10 per 1,000, a failed search is not billed).",
30
+ "It is copied onto every model row of that vendor. Absent means searches are not",
31
+ "part of the total.",
32
+ "",
27
33
  "Matching is EXACT on a normalised id (lowercased, one trailing -YYYYMMDD or",
28
34
  "@YYYYMMDD snapshot suffix removed). Deliberately not prefix or substring:",
29
35
  "within one vendor here, ids one character apart differ in price by 4x, so a",
@@ -64,6 +70,7 @@
64
70
  "verified": "2026-11-10",
65
71
  "promptIncludesCacheReads": false,
66
72
  "reportsCacheWrites": true,
73
+ "webSearchPer1000": 10,
67
74
  "note": "The last two rows exist because the driver's own offline catalogue offers those models and this table did not price them — a lookup-key gap that reads as `cost unknown` and is invisible to the generator's own check, which only proves the module matches this file. `providers/anthropic` asserts the two lists agree. Vendor list prices, every row checked against its pricing page on the `verified` date. Cache read is the vendor's published 0.1x of the input rate, except on the six models it prices differently: 0.05x on claude-opus-5-5 and claude-sonnet-5-5 and 0.025x on claude-fable-5-1, claude-mythos-5-1, claude-fable-5-2 and claude-mythos-5-2, entered at the published rate rather than derived. claude-haiku-5-5 has no row on purpose: the vendor prices it by prompt length ($0.10 in / $0.50 out up to 100,000 prompt tokens, $0.50 / $2.50 above, cache counted in the length), this table holds one rate per model, and either single rate would be wrong for the other half of requests, so its cost reports unknown. Cache write is its published 1.25x for the five-minute entry, which is the default TTL and the one the driver's `cacheControl: { type: 'auto' }` asks for. claude-sonnet-5 launched at an introductory $2/$10 with $3/$15 scheduled from 2026-09-01, and this table recorded the scheduled rate; the vendor has since made $2/$10 the standard price and cancelled the increase. The table has no time dimension, so a future promotional window is still recorded at the list rate and over-reported rather than mis-modelled.",
68
75
  "models": [
69
76
  {
@@ -38,6 +38,7 @@ import {
38
38
  } from '../../../utils/backoff.js'
39
39
  import { SCOPE_ATTRIBUTE } from '../../../utils/log/types.js'
40
40
  import { type Logger, resolveLogger } from '../../../utils/logger.js'
41
+ import { chatStreamWithSamplingRepair } from '../iteration/side-call.js'
41
42
  import {
42
43
  STAGE_ONE_INSTRUCTION,
43
44
  STAGE_TWO_INSTRUCTION,
@@ -209,7 +210,7 @@ export function createAutoReviewClassifier(
209
210
  // ignores it would otherwise hold the call open past its deadline, and
210
211
  // a turn cancelled while it waits would wait with it.
211
212
  const collected = collectChatCompletion(
212
- options.provider.chatStream({
213
+ chatStreamWithSamplingRepair(options.provider, {
213
214
  model: options.model,
214
215
  messages,
215
216
  maxTokens: stage === 1 ? 64 : 8_192,
@@ -1383,6 +1383,9 @@ export class IterationOrchestrator {
1383
1383
  },
1384
1384
  }
1385
1385
  : {}),
1386
+ ...(response.usage.cacheMiss !== undefined
1387
+ ? { cacheMiss: response.usage.cacheMiss }
1388
+ : {}),
1386
1389
  ...contextFigures,
1387
1390
  })
1388
1391
 
@@ -39,7 +39,13 @@ import { isProviderRejectedImageError } from './provider-rejected-image.js'
39
39
  * compaction summaries, do not pass through it.
40
40
  */
41
41
 
42
- export type RejectedParameter = 'effort' | 'thinking' | 'temperature' | 'topP' | 'responseFormat'
42
+ export type RejectedParameter =
43
+ | 'effort'
44
+ | 'thinking'
45
+ | 'temperature'
46
+ | 'topP'
47
+ | 'topK'
48
+ | 'responseFormat'
43
49
 
44
50
  export interface MediaRemoval {
45
51
  readonly count: number
@@ -110,6 +116,7 @@ const PARAMETER_NAMES: Record<RejectedParameter, string> = {
110
116
  thinking: 'thinking',
111
117
  temperature: 'temperature',
112
118
  topP: 'top[_ ]?p',
119
+ topK: 'top[_ ]?k',
113
120
  responseFormat:
114
121
  '(?:response_format|output_config(?:\\.format)?|json_schema|json_object|structured\\s+output)',
115
122
  }
@@ -124,6 +131,10 @@ const PARAMETER_PATTERNS = Object.fromEntries(
124
131
  ]),
125
132
  ) as Record<RejectedParameter, { named: RegExp; refused: RegExp }>
126
133
 
134
+ const SAMPLING = ['temperature', 'topP', 'topK'] as const satisfies readonly RejectedParameter[]
135
+ const SAMPLING_GROUP =
136
+ /\btemperature\b.{0,80}\btop[_ ]?[pk]\b|\btop[_ ]?[pk]\b.{0,80}\btemperature\b/i
137
+
127
138
  const BOTH_SAMPLING =
128
139
  /temperature.{0,60}top[_ ]?p.{0,80}(?:cannot|can['’]t|may\s+not|should\s+not).{0,20}(?:both|together)/i
129
140
 
@@ -156,6 +167,7 @@ function rejectedParameters(
156
167
  thinking: params.thinking !== undefined,
157
168
  temperature: params.temperature !== undefined,
158
169
  topP: params.topP !== undefined,
170
+ topK: params.topK !== undefined,
159
171
  responseFormat: params.responseFormat !== undefined,
160
172
  } satisfies Record<RejectedParameter, boolean>
161
173
  const found: RejectedParameter[] = []
@@ -165,6 +177,12 @@ function rejectedParameters(
165
177
  if (structured ? pattern.named.test(text) : pattern.refused.test(text)) found.push(name)
166
178
  }
167
179
  if (found.length === 0 && set.topP && BOTH_SAMPLING.test(text)) found.push('topP')
180
+ // The vendor words a refusal of sampling controls as a group ("Omit
181
+ // temperature, top_p, and top_k"), so naming one of them in a refusal that
182
+ // names the others is enough for whichever of them was sent.
183
+ if (found.length === 0 && SAMPLING_GROUP.test(text)) {
184
+ for (const name of SAMPLING) if (set[name]) found.push(name)
185
+ }
168
186
  return found
169
187
  }
170
188
 
@@ -726,6 +744,7 @@ function withoutParameters(
726
744
  if (names.has('thinking')) next.thinking = undefined
727
745
  if (names.has('temperature')) next.temperature = undefined
728
746
  if (names.has('topP')) next.topP = undefined
747
+ if (names.has('topK')) next.topK = undefined
729
748
  if (names.has('responseFormat')) next.responseFormat = undefined
730
749
  return next
731
750
  }
@@ -741,3 +760,25 @@ function isHandledElsewhere(error: unknown, params: ChatCompletionParams): boole
741
760
  }
742
761
  return isImageInputRefusal(error)
743
762
  }
763
+
764
+ /**
765
+ * Which of `temperature`, `top_p` and `top_k` a provider refused on this
766
+ * request, from the same reading of its rejection the main turn uses. Empty
767
+ * for any other failure, so a caller can strip exactly these and nothing else.
768
+ */
769
+ export function rejectedSamplingParameters(
770
+ error: unknown,
771
+ params: ChatCompletionParams,
772
+ ): RejectedParameter[] {
773
+ const rejection = classifyRequestRejection(error, params)
774
+ if (rejection?.kind !== 'parameter') return []
775
+ return rejection.parameters.filter((name) => (SAMPLING as readonly string[]).includes(name))
776
+ }
777
+
778
+ /** The request without the named sampling parameters. */
779
+ export function withoutSamplingParameters(
780
+ params: ChatCompletionParams,
781
+ names: ReadonlySet<RejectedParameter>,
782
+ ): ChatCompletionParams {
783
+ return withoutParameters(params, names)
784
+ }
@@ -0,0 +1,60 @@
1
+ import type {
2
+ ChatCompletionParams,
3
+ LLMProvider,
4
+ StreamChunk,
5
+ } from '../../../types/provider/index.js'
6
+ import {
7
+ type RejectedParameter,
8
+ rejectedSamplingParameters,
9
+ withoutSamplingParameters,
10
+ } from './request-repair.js'
11
+
12
+ /**
13
+ * What each model was found to refuse, for the life of the provider object.
14
+ * Weakly keyed so a dropped provider takes its entry with it.
15
+ */
16
+ const refused = new WeakMap<object, Map<string, Set<RejectedParameter>>>()
17
+
18
+ /**
19
+ * A model call made outside the turn loop (a compaction summary, its verifier,
20
+ * the auto-review classifier) that asks for a deterministic answer with
21
+ * `temperature: 0`. Current models refuse sampling parameters outright, and
22
+ * the turn loop already recovers from that; this gives side calls the same
23
+ * recovery instead of letting the call fail and the feature with it.
24
+ *
25
+ * The request goes out as asked. If the provider refuses a sampling parameter
26
+ * BEFORE any output arrived, it is resent without exactly those parameters,
27
+ * and the finding is kept for the model so later calls omit them up front.
28
+ * Nothing is kept unless the resend was accepted, and any other failure is
29
+ * thrown unchanged.
30
+ */
31
+ export async function* chatStreamWithSamplingRepair(
32
+ provider: LLMProvider,
33
+ params: ChatCompletionParams,
34
+ ): AsyncGenerator<StreamChunk> {
35
+ const perModel = refused.get(provider) ?? new Map<string, Set<RejectedParameter>>()
36
+ refused.set(provider, perModel)
37
+ const known = perModel.get(params.model)
38
+ let request = known ? withoutSamplingParameters(params, known) : params
39
+ let stripped: Set<RejectedParameter> | undefined
40
+ for (;;) {
41
+ let yielded = false
42
+ try {
43
+ for await (const chunk of provider.chatStream(request)) {
44
+ yielded = true
45
+ yield chunk
46
+ }
47
+ if (stripped) {
48
+ const merged = new Set([...(perModel.get(params.model) ?? []), ...stripped])
49
+ perModel.set(params.model, merged)
50
+ }
51
+ return
52
+ } catch (error) {
53
+ if (yielded || stripped) throw error
54
+ const names = rejectedSamplingParameters(error, request)
55
+ if (names.length === 0) throw error
56
+ stripped = new Set(names)
57
+ request = withoutSamplingParameters(request, stripped)
58
+ }
59
+ }
60
+ }
@@ -50,6 +50,42 @@ export interface TokenUsage {
50
50
  * unreported.
51
51
  */
52
52
  speed?: import('../provider/chat.js').InferenceSpeed
53
+
54
+ /**
55
+ * Hosted web searches the provider ran for this call and bills per request.
56
+ * Unlike the per-call facts below it IS a count of work done, so it is
57
+ * summed across calls. Absent means the provider did not report one.
58
+ */
59
+ webSearchRequests?: number
60
+
61
+ /**
62
+ * The service tier the provider says served this call (`standard`,
63
+ * `priority`, `batch`). A per-call fact, never summed. Absent means
64
+ * unreported.
65
+ */
66
+ serviceTier?: string
67
+
68
+ /** The region the provider says ran this call, when it says. Per call, never summed. */
69
+ inferenceGeo?: string
70
+
71
+ /**
72
+ * Why the provider's prompt cache did not serve a prefix it had before, when
73
+ * it explains. Per call, never summed. Absent means no miss was explained: the
74
+ * provider reported nothing, a pending answer, or a hit.
75
+ */
76
+ cacheMiss?: CacheMiss
77
+ }
78
+
79
+ /** A prompt-cache miss as the provider explains it. See {@link TokenUsage.cacheMiss}. */
80
+ export interface CacheMiss {
81
+ /**
82
+ * The provider's own reason, e.g. `model_changed`, `system_changed`,
83
+ * `tools_changed`, `messages_changed`, `previous_message_not_found`,
84
+ * `unavailable`.
85
+ */
86
+ reason: string
87
+ /** Input tokens that missed the cache because of it, when the provider counts them. */
88
+ missedTokens?: number
53
89
  }
54
90
 
55
91
  export function accumulateTokenUsage(current: TokenUsage, addition: TokenUsage): TokenUsage {
@@ -66,8 +102,12 @@ export function accumulateTokenUsage(current: TokenUsage, addition: TokenUsage):
66
102
  ...(current.reasoningTokens !== undefined || addition.reasoningTokens !== undefined
67
103
  ? { reasoningTokens: (current.reasoningTokens ?? 0) + (addition.reasoningTokens ?? 0) }
68
104
  : {}),
69
- // `cacheTtlMs` and `cacheWriteTtlTokens` are per-call facts and are
70
- // deliberately not carried into a sum.
105
+ ...(current.webSearchRequests !== undefined || addition.webSearchRequests !== undefined
106
+ ? { webSearchRequests: (current.webSearchRequests ?? 0) + (addition.webSearchRequests ?? 0) }
107
+ : {}),
108
+ // `cacheTtlMs`, `cacheWriteTtlTokens`, `speed`, `serviceTier`,
109
+ // `inferenceGeo` and `cacheMiss` are per-call facts and are deliberately
110
+ // not carried into a sum.
71
111
  }
72
112
  }
73
113
 
@@ -112,6 +152,18 @@ export function mergeTokenUsage(current: TokenUsage, next: TokenUsage): TokenUsa
112
152
  ...(next.speed !== undefined || current.speed !== undefined
113
153
  ? { speed: next.speed ?? current.speed }
114
154
  : {}),
155
+ ...(next.webSearchRequests !== undefined || current.webSearchRequests !== undefined
156
+ ? { webSearchRequests: Math.max(current.webSearchRequests ?? 0, next.webSearchRequests ?? 0) }
157
+ : {}),
158
+ ...(next.serviceTier !== undefined || current.serviceTier !== undefined
159
+ ? { serviceTier: next.serviceTier ?? current.serviceTier }
160
+ : {}),
161
+ ...(next.inferenceGeo !== undefined || current.inferenceGeo !== undefined
162
+ ? { inferenceGeo: next.inferenceGeo ?? current.inferenceGeo }
163
+ : {}),
164
+ ...(next.cacheMiss !== undefined || current.cacheMiss !== undefined
165
+ ? { cacheMiss: next.cacheMiss ?? current.cacheMiss }
166
+ : {}),
115
167
  }
116
168
  }
117
169
 
@@ -926,6 +926,13 @@ type CoreSessionEvent =
926
926
  * absent means unknown, and a host must not assume one.
927
927
  */
928
928
  cache?: { ttlMs: number; touched: boolean }
929
+ /**
930
+ * Why the provider says the latest model call did not read the cache
931
+ * prefix it had before, when it explains one. Absent means no miss was
932
+ * explained: nothing reported, a pending answer, or a hit. A host that
933
+ * shows it should do so quietly; a miss after an edit is ordinary.
934
+ */
935
+ cacheMiss?: { reason: string; missedTokens?: number }
929
936
  /**
930
937
  * How large the CONTEXT is right now, and how large it may get.
931
938
  *
package/src/utils/cost.ts CHANGED
@@ -48,6 +48,12 @@ export interface ModelPricing {
48
48
  * charged at the standard rate, which would under-report it.
49
49
  */
50
50
  fastMultiplier?: number
51
+ /**
52
+ * What the vendor charges per 1,000 hosted web searches
53
+ * (`TokenUsage.webSearchRequests`). Absent means no published search price:
54
+ * searches are then not part of the total, as before this field existed.
55
+ */
56
+ webSearchCostPer1000?: number
51
57
  }
52
58
 
53
59
  export { ZERO_COST }
@@ -61,6 +67,26 @@ interface Priced {
61
67
  }
62
68
 
63
69
  function priceUsage(usage: TokenUsage, pricing: ModelPricing): Priced {
70
+ const tokens = priceTokens(usage, pricing)
71
+ const searches = usage.webSearchRequests ?? 0
72
+ if (searches === 0 || pricing.webSearchCostPer1000 === undefined) return tokens
73
+ // Per request, so no speed or tier multiple applies to it.
74
+ return { ...tokens, cost: tokens.cost + (searches / 1000) * pricing.webSearchCostPer1000 }
75
+ }
76
+
77
+ /**
78
+ * Tiers with a published per-token price (anything but `priority` and `batch`; a compatible endpoint may report `default` or `auto`). The vendor lists none for `priority`
79
+ * (a capacity commitment) or `batch` (a different endpoint), so tokens served
80
+ * on one are unpriced rather than charged at the standard rate.
81
+ */
82
+ function isStandardTier(tier: string | undefined): boolean {
83
+ return tier !== 'priority' && tier !== 'batch'
84
+ }
85
+
86
+ function priceTokens(usage: TokenUsage, pricing: ModelPricing): Priced {
87
+ if (!isStandardTier(usage.serviceTier)) {
88
+ return { cost: 0, cacheDiscount: 0, unpriced: usage.totalTokens }
89
+ }
64
90
  if (usage.speed === 'fast') {
65
91
  const multiplier = pricing.fastMultiplier
66
92
  if (multiplier === undefined) {