@namzu/sdk 62.5.0 → 62.6.1
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.
- package/CHANGELOG.md +22 -0
- package/README.md +1 -1
- package/dist/agents/file-definitions.d.ts +1 -1
- package/dist/agents/file-definitions.js +1 -1
- package/dist/agents/runAgent.d.ts +1 -1
- package/dist/agents/runAgent.js +1 -1
- package/dist/compaction/model-summary.d.ts.map +1 -1
- package/dist/compaction/model-summary.js +2 -1
- package/dist/compaction/model-summary.js.map +1 -1
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +2 -1
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/eval/judge.d.ts.map +1 -1
- package/dist/eval/judge.js +2 -1
- package/dist/eval/judge.js.map +1 -1
- package/dist/pricing/catalogue.generated.d.ts.map +1 -1
- package/dist/pricing/catalogue.generated.js +19 -0
- package/dist/pricing/catalogue.generated.js.map +1 -1
- package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/classifier.js +2 -1
- package/dist/runtime/query/auto-review/classifier.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +3 -0
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/request-repair.d.ts +9 -1
- package/dist/runtime/query/iteration/request-repair.d.ts.map +1 -1
- package/dist/runtime/query/iteration/request-repair.js +29 -0
- package/dist/runtime/query/iteration/request-repair.js.map +1 -1
- package/dist/runtime/query/iteration/side-call.d.ts +16 -0
- package/dist/runtime/query/iteration/side-call.d.ts.map +1 -0
- package/dist/runtime/query/iteration/side-call.js +50 -0
- package/dist/runtime/query/iteration/side-call.js.map +1 -0
- package/dist/sandbox/file-walk.d.ts.map +1 -1
- package/dist/sandbox/file-walk.js +34 -17
- package/dist/sandbox/file-walk.js.map +1 -1
- package/dist/sandbox/glob-segment.d.ts +28 -0
- package/dist/sandbox/glob-segment.d.ts.map +1 -0
- package/dist/sandbox/glob-segment.js +281 -0
- package/dist/sandbox/glob-segment.js.map +1 -0
- package/dist/types/common/index.d.ts +31 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/index.js +18 -2
- package/dist/types/common/index.js.map +1 -1
- package/dist/types/session/events.d.ts +10 -0
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/utils/cost.d.ts +6 -0
- package/dist/utils/cost.d.ts.map +1 -1
- package/dist/utils/cost.js +19 -0
- package/dist/utils/cost.js.map +1 -1
- package/package.json +3 -3
- package/src/agents/file-definitions.ts +1 -1
- package/src/agents/runAgent.ts +1 -1
- package/src/compaction/model-summary.ts +2 -1
- package/src/compaction/verifier.ts +2 -1
- package/src/eval/judge.ts +2 -1
- package/src/pricing/catalogue.generated.ts +19 -0
- package/src/pricing/rates.source.json +7 -0
- package/src/runtime/query/auto-review/classifier.ts +2 -1
- package/src/runtime/query/iteration/index.ts +3 -0
- package/src/runtime/query/iteration/request-repair.ts +42 -1
- package/src/runtime/query/iteration/side-call.ts +60 -0
- package/src/sandbox/file-walk.ts +38 -17
- package/src/sandbox/glob-segment.ts +285 -0
- package/src/types/common/index.ts +54 -2
- package/src/types/session/events.ts +7 -0
- package/src/utils/cost.ts +26 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiles one path segment of a glob (no `/` in it) to the regular expression
|
|
3
|
+
* the file walk tests names with, in the same dialect the sandbox guest runs.
|
|
4
|
+
* It covers `*`, `?`, `[...]` classes (with `!`/`^` negation, ranges and POSIX
|
|
5
|
+
* classes), the five extglob forms and `\` escapes. A segment with no magic in
|
|
6
|
+
* it comes back as the plain name to compare against.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Written for the `u` flag, which every compiled expression carries.
|
|
10
|
+
const POSIX_CLASSES: Record<string, string> = {
|
|
11
|
+
alnum: '\\p{L}\\p{Nl}\\p{Nd}',
|
|
12
|
+
alpha: '\\p{L}\\p{Nl}',
|
|
13
|
+
ascii: '\\x00-\\x7f',
|
|
14
|
+
blank: '\\p{Zs}\\t',
|
|
15
|
+
cntrl: '\\p{Cc}',
|
|
16
|
+
digit: '\\p{Nd}',
|
|
17
|
+
graph: '\\x21-\\x7e',
|
|
18
|
+
lower: '\\p{Ll}',
|
|
19
|
+
print: '\\x20-\\x7e',
|
|
20
|
+
punct: '\\p{P}\\p{S}',
|
|
21
|
+
space: '\\s',
|
|
22
|
+
upper: '\\p{Lu}',
|
|
23
|
+
word: '\\p{L}\\p{Nl}\\p{Nd}_',
|
|
24
|
+
xdigit: '0-9a-fA-F',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CompiledSegment {
|
|
28
|
+
/** Set when the segment has no magic: the exact name it matches. */
|
|
29
|
+
literal: string | null
|
|
30
|
+
/** Source of an expression anchored with `^` and `$`, for a segment with magic. */
|
|
31
|
+
source: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Raised for a segment that repeats a group (`*( )` or `+( )`) which could match
|
|
36
|
+
* the same text more than one way. Such a group backtracks exponentially on a
|
|
37
|
+
* name that nearly matches, and glob patterns come from untrusted input.
|
|
38
|
+
*/
|
|
39
|
+
export class GlobTooComplexError extends Error {
|
|
40
|
+
readonly segment: string
|
|
41
|
+
constructor(segment: string, reason: 'ambiguous' | 'stretch' = 'ambiguous') {
|
|
42
|
+
super(
|
|
43
|
+
reason === 'stretch'
|
|
44
|
+
? `The pattern '${segment}' has more than three wildcards that can stretch; split it across folders or use fewer *.`
|
|
45
|
+
: `The pattern '${segment}' repeats a group that can match the same text more than one way; use ** or {a,b} instead.`,
|
|
46
|
+
)
|
|
47
|
+
this.name = 'GlobTooComplexError'
|
|
48
|
+
this.segment = segment
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const escapeRegex = (text: string) => text.replace(/[\\^$.*+?()[\]{}|/]/g, '\\$&')
|
|
53
|
+
const escapeInClass = (text: string) => text.replace(/[\\^\]\[/-]/g, '\\$&')
|
|
54
|
+
|
|
55
|
+
interface Parsed {
|
|
56
|
+
source: string
|
|
57
|
+
literal: string
|
|
58
|
+
magic: boolean
|
|
59
|
+
/** Unbounded runs: each `*` (consecutive ones collapsed), `*( )` and `+( )`. */
|
|
60
|
+
runs: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Index of the `)` closing the group opened just before `from`, or -1. */
|
|
64
|
+
function closeGroup(text: string, from: number): number {
|
|
65
|
+
let depth = 1
|
|
66
|
+
for (let index = from; index < text.length; index += 1) {
|
|
67
|
+
const ch = text[index]
|
|
68
|
+
if (ch === '\\') index += 1
|
|
69
|
+
else if (ch === '[') {
|
|
70
|
+
const end = classEnd(text, index)
|
|
71
|
+
if (end > 0) index = end
|
|
72
|
+
} else if (ch === '(') depth += 1
|
|
73
|
+
else if (ch === ')' && --depth === 0) return index
|
|
74
|
+
}
|
|
75
|
+
return -1
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Index of the `]` closing the class opened at `start`, or -1 when it never closes. */
|
|
79
|
+
function classEnd(text: string, start: number): number {
|
|
80
|
+
let index = start + 1
|
|
81
|
+
if (text[index] === '!' || text[index] === '^') index += 1
|
|
82
|
+
if (text[index] === ']') index += 1
|
|
83
|
+
for (; index < text.length; index += 1) {
|
|
84
|
+
const ch = text[index]
|
|
85
|
+
if (ch === '\\') index += 1
|
|
86
|
+
else if (ch === '[' && text[index + 1] === ':') {
|
|
87
|
+
const end = text.indexOf(':]', index + 2)
|
|
88
|
+
if (end > 0 && POSIX_CLASSES[text.slice(index + 2, end)]) index = end + 1
|
|
89
|
+
} else if (ch === ']') return index
|
|
90
|
+
}
|
|
91
|
+
return -1
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function splitAlternatives(body: string): string[] {
|
|
95
|
+
const parts: string[] = []
|
|
96
|
+
let depth = 0
|
|
97
|
+
let current = ''
|
|
98
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
99
|
+
const ch = body[index] as string
|
|
100
|
+
if (ch === '\\') {
|
|
101
|
+
current += ch + (body[index + 1] ?? '')
|
|
102
|
+
index += 1
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
if (ch === '[') {
|
|
106
|
+
const end = classEnd(body, index)
|
|
107
|
+
if (end > 0) {
|
|
108
|
+
current += body.slice(index, end + 1)
|
|
109
|
+
index = end
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (ch === '(') depth += 1
|
|
114
|
+
if (ch === ')') depth -= 1
|
|
115
|
+
if (ch === '|' && depth === 0) {
|
|
116
|
+
parts.push(current)
|
|
117
|
+
current = ''
|
|
118
|
+
} else current += ch
|
|
119
|
+
}
|
|
120
|
+
parts.push(current)
|
|
121
|
+
return parts
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function classSource(body: string): string {
|
|
125
|
+
let index = 0
|
|
126
|
+
let negate = false
|
|
127
|
+
if (body[0] === '!' || body[0] === '^') {
|
|
128
|
+
negate = true
|
|
129
|
+
index = 1
|
|
130
|
+
}
|
|
131
|
+
let out = ''
|
|
132
|
+
for (; index < body.length; index += 1) {
|
|
133
|
+
const ch = body[index] as string
|
|
134
|
+
if (ch === '[' && body[index + 1] === ':') {
|
|
135
|
+
const end = body.indexOf(':]', index + 2)
|
|
136
|
+
const name = end > 0 ? body.slice(index + 2, end) : ''
|
|
137
|
+
if (POSIX_CLASSES[name]) {
|
|
138
|
+
out += POSIX_CLASSES[name]
|
|
139
|
+
index = end + 1
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (ch === '\\' && index + 1 < body.length) {
|
|
144
|
+
index += 1
|
|
145
|
+
out += escapeInClass(body[index] as string)
|
|
146
|
+
} else if (ch === '-' && out !== '' && index + 1 < body.length) out += '-'
|
|
147
|
+
else out += escapeInClass(ch)
|
|
148
|
+
}
|
|
149
|
+
const source = `[${negate ? '^' : ''}${out}]`
|
|
150
|
+
try {
|
|
151
|
+
new RegExp(source, 'u')
|
|
152
|
+
} catch {
|
|
153
|
+
// An out-of-order range such as `[z-a]` matches no name rather than failing the walk.
|
|
154
|
+
return '(?!)'
|
|
155
|
+
}
|
|
156
|
+
return source
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** `guard`, when set, is put before each alternative of a group at the very start that does not itself spell out a leading `.`. */
|
|
160
|
+
function parse(text: string, guard = ''): Parsed {
|
|
161
|
+
let source = ''
|
|
162
|
+
let literal = ''
|
|
163
|
+
let magic = false
|
|
164
|
+
let runs = 0
|
|
165
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
166
|
+
const ch = text[index] as string
|
|
167
|
+
const next = text[index + 1]
|
|
168
|
+
if (ch === '\\') {
|
|
169
|
+
if (next === undefined) {
|
|
170
|
+
source += '\\\\'
|
|
171
|
+
literal += '\\'
|
|
172
|
+
} else {
|
|
173
|
+
source += escapeRegex(next)
|
|
174
|
+
literal += next
|
|
175
|
+
index += 1
|
|
176
|
+
}
|
|
177
|
+
} else if (ch === '*' && next !== '(') {
|
|
178
|
+
while (text[index + 1] === '*') index += 1
|
|
179
|
+
source += '[^/]*?'
|
|
180
|
+
runs += 1
|
|
181
|
+
magic = true
|
|
182
|
+
} else if (ch === '?' && next !== '(') {
|
|
183
|
+
source += '[^/]'
|
|
184
|
+
magic = true
|
|
185
|
+
} else if (ch === '[') {
|
|
186
|
+
const end = classEnd(text, index)
|
|
187
|
+
if (end < 0) {
|
|
188
|
+
source += '\\['
|
|
189
|
+
literal += '['
|
|
190
|
+
} else {
|
|
191
|
+
source += classSource(text.slice(index + 1, end))
|
|
192
|
+
magic = true
|
|
193
|
+
index = end
|
|
194
|
+
}
|
|
195
|
+
} else if ('?*+@!'.includes(ch) && next === '(') {
|
|
196
|
+
const end = closeGroup(text, index + 2)
|
|
197
|
+
if (end < 0) {
|
|
198
|
+
source += escapeRegex(ch)
|
|
199
|
+
literal += ch
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
const parts = splitAlternatives(text.slice(index + 2, end)).map((a) => ({
|
|
203
|
+
a,
|
|
204
|
+
parsed: parse(a),
|
|
205
|
+
}))
|
|
206
|
+
// A repeated group is linear only when each repetition can be read one way:
|
|
207
|
+
// plain literal alternatives, none a prefix of another (and none empty).
|
|
208
|
+
if (ch === '*' || ch === '+') {
|
|
209
|
+
const literals = parts.map((part) => part.parsed.literal)
|
|
210
|
+
// A lone class is one character wide, so each repetition has one reading.
|
|
211
|
+
const lone = parts.length === 1 && isOneClass(parts[0]?.a ?? '')
|
|
212
|
+
const plain = lone || parts.every((part) => !part.parsed.magic)
|
|
213
|
+
const prefixFree =
|
|
214
|
+
lone ||
|
|
215
|
+
literals.every(
|
|
216
|
+
(one, i) =>
|
|
217
|
+
one !== '' && literals.every((other, j) => i === j || !other.startsWith(one)),
|
|
218
|
+
)
|
|
219
|
+
if (!plain || !prefixFree) throw new GlobTooComplexError(text)
|
|
220
|
+
}
|
|
221
|
+
const alternatives = parts.map(
|
|
222
|
+
({ a, parsed }) => (index === 0 && !startsWithDot(a) ? guard : '') + parsed.source,
|
|
223
|
+
)
|
|
224
|
+
const group = `(?:${alternatives.join('|')})`
|
|
225
|
+
runs +=
|
|
226
|
+
parts.reduce((sum, part) => sum + part.parsed.runs, 0) + (ch === '*' || ch === '+' ? 1 : 0)
|
|
227
|
+
magic = true
|
|
228
|
+
if (ch === '!') {
|
|
229
|
+
const tail = parse(text.slice(end + 1))
|
|
230
|
+
const rest = tail.source
|
|
231
|
+
source += `(?:(?!${group}${rest}$)[^/]*?)${rest}`
|
|
232
|
+
return { source, literal, magic, runs: runs + 1 + tail.runs }
|
|
233
|
+
}
|
|
234
|
+
source += ch === '@' ? group : `${group}${ch === '?' ? '?' : ch === '*' ? '*' : '+'}`
|
|
235
|
+
index = end
|
|
236
|
+
} else {
|
|
237
|
+
source += escapeRegex(ch)
|
|
238
|
+
literal += ch
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return { source, literal, magic, runs }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Whether the text is exactly one closed `[...]` class. */
|
|
245
|
+
function isOneClass(text: string): boolean {
|
|
246
|
+
return text.startsWith('[') && classEnd(text, 0) === text.length - 1
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Whether every name this segment can match begins with a literal `.` the pattern spells out. */
|
|
250
|
+
function startsWithDot(text: string): boolean {
|
|
251
|
+
if (text.startsWith('.') || text.startsWith('\\.')) return true
|
|
252
|
+
if (text.startsWith('[')) {
|
|
253
|
+
const end = classEnd(text, 0)
|
|
254
|
+
return end > 0 && text.slice(1, end) === '.'
|
|
255
|
+
}
|
|
256
|
+
return false
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Whether the segment is a single `?(`, `*(`, `@(` or `+(` group, whose alternatives carry the leading-dot guard themselves. */
|
|
260
|
+
function isOneGroup(text: string): boolean {
|
|
261
|
+
return (
|
|
262
|
+
'?*@+'.includes(text[0] ?? '') && text[1] === '(' && closeGroup(text, 2) === text.length - 1
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* `dot` lets a wildcard match a name that starts with `.`; without it only a
|
|
268
|
+
* segment that itself begins with a literal `.` can.
|
|
269
|
+
*/
|
|
270
|
+
export function compileSegment(segment: string, dot: boolean): CompiledSegment {
|
|
271
|
+
const guard = dot ? '' : '(?!\\.)'
|
|
272
|
+
let parsed: Parsed
|
|
273
|
+
try {
|
|
274
|
+
parsed = parse(segment, guard)
|
|
275
|
+
} catch (error) {
|
|
276
|
+
throw error instanceof GlobTooComplexError
|
|
277
|
+
? new GlobTooComplexError(segment, 'ambiguous')
|
|
278
|
+
: error
|
|
279
|
+
}
|
|
280
|
+
if (parsed.runs > 3) throw new GlobTooComplexError(segment, 'stretch')
|
|
281
|
+
if (!parsed.magic) return { literal: parsed.literal, source: `^${parsed.source}$` }
|
|
282
|
+
const grouped = (segment.startsWith('@(') || segment.startsWith('+(')) && !dot
|
|
283
|
+
const lead = grouped || dot || startsWithDot(segment) || isOneGroup(segment) ? '' : guard
|
|
284
|
+
return { literal: null, source: `^${lead}(?:${parsed.source})$` }
|
|
285
|
+
}
|
|
@@ -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
|
-
|
|
70
|
-
|
|
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) {
|