@namzu/sdk 21.1.0 → 22.1.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.
- package/CHANGELOG.md +173 -0
- package/dist/advisory/executor.d.ts +9 -4
- package/dist/advisory/executor.d.ts.map +1 -1
- package/dist/advisory/executor.js +16 -9
- package/dist/advisory/executor.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +12 -0
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/constants/limits.d.ts +13 -0
- package/dist/constants/limits.d.ts.map +1 -1
- package/dist/constants/limits.js +14 -2
- package/dist/constants/limits.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts +2 -2
- package/dist/manager/run/persistence.d.ts +37 -2
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +48 -7
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/pricing/catalogue.generated.d.ts +15 -0
- package/dist/pricing/catalogue.generated.d.ts.map +1 -0
- package/dist/pricing/catalogue.generated.js +293 -0
- package/dist/pricing/catalogue.generated.js.map +1 -0
- package/dist/pricing/index.d.ts +46 -0
- package/dist/pricing/index.d.ts.map +1 -0
- package/dist/pricing/index.js +70 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/public-runtime.d.ts +2 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +6 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +3 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/run/LimitChecker.d.ts +10 -0
- package/dist/run/LimitChecker.d.ts.map +1 -1
- package/dist/run/LimitChecker.js +14 -2
- package/dist/run/LimitChecker.js.map +1 -1
- package/dist/runtime/query/checkpoint.d.ts +23 -2
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +24 -3
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/guard.d.ts.map +1 -1
- package/dist/runtime/query/guard.js +1 -0
- package/dist/runtime/query/guard.js.map +1 -1
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +42 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +44 -8
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +71 -13
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/advisory.js +9 -1
- package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +11 -2
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/store/run/conformance.d.ts.map +1 -1
- package/dist/store/run/conformance.js +8 -2
- package/dist/store/run/conformance.js.map +1 -1
- package/dist/types/agent/supervisor.d.ts +29 -0
- package/dist/types/agent/supervisor.d.ts.map +1 -1
- package/dist/types/common/index.d.ts +38 -2
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/run/stop-reason.d.ts +31 -1
- package/dist/types/run/stop-reason.d.ts.map +1 -1
- package/dist/utils/cost.d.ts +60 -2
- package/dist/utils/cost.d.ts.map +1 -1
- package/dist/utils/cost.js +126 -11
- package/dist/utils/cost.js.map +1 -1
- package/package.json +1 -1
- package/src/advisory/executor.ts +16 -9
- package/src/agents/SupervisorAgent.ts +12 -0
- package/src/constants/limits.ts +14 -2
- package/src/manager/agent/lifecycle.ts +2 -2
- package/src/manager/run/persistence.ts +67 -7
- package/src/pricing/catalogue.generated.ts +308 -0
- package/src/pricing/index.ts +78 -0
- package/src/pricing/rates.source.json +206 -0
- package/src/public-runtime.ts +8 -0
- package/src/public-types.ts +3 -1
- package/src/run/LimitChecker.ts +24 -2
- package/src/runtime/query/checkpoint.ts +24 -3
- package/src/runtime/query/guard.ts +1 -0
- package/src/runtime/query/index.ts +42 -0
- package/src/runtime/query/iteration/index.ts +76 -13
- package/src/runtime/query/iteration/phases/advisory.ts +9 -1
- package/src/runtime/query/iteration/phases/compaction.ts +12 -2
- package/src/store/run/conformance.ts +8 -2
- package/src/types/agent/supervisor.ts +30 -0
- package/src/types/common/index.ts +38 -2
- package/src/types/run/stop-reason.ts +30 -0
- package/src/utils/cost.ts +186 -16
package/src/utils/cost.ts
CHANGED
|
@@ -1,42 +1,195 @@
|
|
|
1
1
|
import { ZERO_COST } from '../constants/limits.js'
|
|
2
2
|
import type { CostInfo, TokenUsage } from '../types/common/index.js'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* What a driver's cache tokens cost, and what they mean.
|
|
6
|
+
*
|
|
7
|
+
* `promptIncludesCacheReads` is a property of the DRIVER, not of the model, and
|
|
8
|
+
* it is why this is a nested object rather than two more rate fields. The
|
|
9
|
+
* drivers in this repository disagree about it: two report `promptTokens`
|
|
10
|
+
* excluding cache reads and charge them on top, one reports `promptTokens`
|
|
11
|
+
* already containing them. Applying a read rate without knowing which one you
|
|
12
|
+
* have is wrong by the entire cache volume, in a direction that changes with
|
|
13
|
+
* whoever served the turn — so the fact travels with the rates and is never
|
|
14
|
+
* inferred.
|
|
15
|
+
*/
|
|
16
|
+
export interface CacheRates {
|
|
17
|
+
/**
|
|
18
|
+
* `true`: `usage.cachedTokens` is a SUBSET of `usage.promptTokens`, so the
|
|
19
|
+
* billable input is the difference.
|
|
20
|
+
* `false`: they are additional to it.
|
|
21
|
+
*/
|
|
22
|
+
readonly promptIncludesCacheReads: boolean
|
|
23
|
+
readonly readCostPer1M: number
|
|
24
|
+
/**
|
|
25
|
+
* Absent when the driver never reports a cache write. Tokens that arrive
|
|
26
|
+
* anyway are counted as unpriced rather than charged at some neighbouring
|
|
27
|
+
* rate, so a driver that starts reporting them surfaces as a gap instead of
|
|
28
|
+
* a quietly wrong total.
|
|
29
|
+
*/
|
|
30
|
+
readonly writeCostPer1M?: number
|
|
31
|
+
}
|
|
32
|
+
|
|
4
33
|
export interface ModelPricing {
|
|
5
34
|
inputCostPer1M: number
|
|
6
35
|
outputCostPer1M: number
|
|
36
|
+
/**
|
|
37
|
+
* Absent means the caller declared a two-rate card and gets a two-rate
|
|
38
|
+
* answer: prompt and completion tokens are priced, and cache tokens are
|
|
39
|
+
* left inside whichever of those the driver already counted them in. That
|
|
40
|
+
* is the host-supplied case and it is a declaration, not a guess on our
|
|
41
|
+
* part. The catalogue always supplies this.
|
|
42
|
+
*/
|
|
43
|
+
cache?: CacheRates
|
|
7
44
|
}
|
|
8
45
|
|
|
9
46
|
export { ZERO_COST }
|
|
10
47
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
cacheDiscount
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
48
|
+
interface Priced {
|
|
49
|
+
readonly cost: number
|
|
50
|
+
/** What the cache reads saved against paying the full input rate. */
|
|
51
|
+
readonly cacheDiscount: number
|
|
52
|
+
/** Tokens this rate card had no rate for. */
|
|
53
|
+
readonly unpriced: number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function priceUsage(usage: TokenUsage, pricing: ModelPricing): Priced {
|
|
57
|
+
const perMillion = (tokens: number, rate: number) => (tokens / 1_000_000) * rate
|
|
58
|
+
|
|
59
|
+
const cache = pricing.cache
|
|
60
|
+
if (cache === undefined) {
|
|
61
|
+
return {
|
|
62
|
+
cost:
|
|
63
|
+
perMillion(usage.promptTokens, pricing.inputCostPer1M) +
|
|
64
|
+
perMillion(usage.completionTokens, pricing.outputCostPer1M),
|
|
65
|
+
cacheDiscount: 0,
|
|
66
|
+
unpriced: 0,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Never negative: a driver that reports more cached tokens than prompt
|
|
71
|
+
// tokens is contradicting itself, and clamping keeps that from turning into
|
|
72
|
+
// a credit on the bill.
|
|
73
|
+
const billableInput = cache.promptIncludesCacheReads
|
|
74
|
+
? Math.max(0, usage.promptTokens - usage.cachedTokens)
|
|
75
|
+
: usage.promptTokens
|
|
19
76
|
|
|
77
|
+
const writesRated = cache.writeCostPer1M !== undefined
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
cost:
|
|
81
|
+
perMillion(billableInput, pricing.inputCostPer1M) +
|
|
82
|
+
perMillion(usage.completionTokens, pricing.outputCostPer1M) +
|
|
83
|
+
perMillion(usage.cachedTokens, cache.readCostPer1M) +
|
|
84
|
+
(writesRated ? perMillion(usage.cacheWriteTokens, cache.writeCostPer1M as number) : 0),
|
|
85
|
+
// Reported, not subtracted. The saving is already inside `cost` — the
|
|
86
|
+
// reads were charged at the read rate rather than the input rate — so
|
|
87
|
+
// taking it off again would double-count it. This field was previously
|
|
88
|
+
// declared, defaulted to zero at both call sites, and passed by nobody;
|
|
89
|
+
// it now carries the one quantity it was always named for.
|
|
90
|
+
cacheDiscount: perMillion(usage.cachedTokens, pricing.inputCostPer1M - cache.readCostPer1M),
|
|
91
|
+
unpriced: writesRated ? 0 : usage.cacheWriteTokens,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function calculateCost(usage: TokenUsage, pricing: ModelPricing): CostInfo {
|
|
96
|
+
const priced = priceUsage(usage, pricing)
|
|
20
97
|
return {
|
|
21
98
|
inputCostPer1M: pricing.inputCostPer1M,
|
|
22
99
|
outputCostPer1M: pricing.outputCostPer1M,
|
|
23
|
-
totalCost:
|
|
24
|
-
cacheDiscount,
|
|
100
|
+
totalCost: priced.cost,
|
|
101
|
+
cacheDiscount: priced.cacheDiscount,
|
|
102
|
+
unpricedTokens: priced.unpriced,
|
|
25
103
|
}
|
|
26
104
|
}
|
|
27
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Whether `current` is a total nothing has been added to yet.
|
|
108
|
+
*
|
|
109
|
+
* All three conditions, not one. A run whose only turn so far was unpriced has
|
|
110
|
+
* a zero total and no rate fields too, and adopting the next turn's rate card
|
|
111
|
+
* as though it described the whole total would be exactly the wrong claim.
|
|
112
|
+
*
|
|
113
|
+
* This predicate is only sound while every writer of a `CostInfo` goes through
|
|
114
|
+
* this module or states the truth in its vocabulary. One did not:
|
|
115
|
+
* `projectEmergencyToCheckpoint` wrote `ZERO_COST` beside a real, non-zero
|
|
116
|
+
* `tokenUsage`, which is byte-identical to a fresh total — so a run resumed
|
|
117
|
+
* from an emergency dump would have adopted its next turn's rate card as
|
|
118
|
+
* covering spend that happened before the crash. That projection now records
|
|
119
|
+
* the pre-crash tokens as unpriced, which is both true and, usefully, not
|
|
120
|
+
* fresh-shaped.
|
|
121
|
+
*/
|
|
122
|
+
function isFresh(current: CostInfo): boolean {
|
|
123
|
+
return (
|
|
124
|
+
current.totalCost === 0 &&
|
|
125
|
+
current.unpricedTokens === 0 &&
|
|
126
|
+
current.inputCostPer1M === undefined &&
|
|
127
|
+
current.outputCostPer1M === undefined
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The rate fields that honestly describe `current + pricing`.
|
|
133
|
+
*
|
|
134
|
+
* Kept when one card still covers the whole total; dropped when it does not.
|
|
135
|
+
* Dropping rather than overwriting is the change: the previous version wrote
|
|
136
|
+
* the incoming card over whatever was there, so a run that swapped models
|
|
137
|
+
* reported the last card applied as though it had priced every token.
|
|
138
|
+
*
|
|
139
|
+
* Equal-but-distinct cards are treated as one, deliberately. Two models at the
|
|
140
|
+
* same published rate produce a total that a single rate card DOES describe,
|
|
141
|
+
* which is the only claim these two fields make — they name a rate, not a
|
|
142
|
+
* model, and `Run.steps[].servedBy` carries which model served each turn.
|
|
143
|
+
*/
|
|
144
|
+
function ratesFor(
|
|
145
|
+
current: CostInfo,
|
|
146
|
+
pricing: ModelPricing,
|
|
147
|
+
): Pick<CostInfo, 'inputCostPer1M' | 'outputCostPer1M'> {
|
|
148
|
+
const incoming = {
|
|
149
|
+
inputCostPer1M: pricing.inputCostPer1M,
|
|
150
|
+
outputCostPer1M: pricing.outputCostPer1M,
|
|
151
|
+
}
|
|
152
|
+
if (isFresh(current)) return incoming
|
|
153
|
+
if (
|
|
154
|
+
current.inputCostPer1M === pricing.inputCostPer1M &&
|
|
155
|
+
current.outputCostPer1M === pricing.outputCostPer1M
|
|
156
|
+
) {
|
|
157
|
+
return incoming
|
|
158
|
+
}
|
|
159
|
+
return {}
|
|
160
|
+
}
|
|
161
|
+
|
|
28
162
|
export function accumulateCost(
|
|
29
163
|
current: CostInfo,
|
|
30
164
|
additionalUsage: TokenUsage,
|
|
31
165
|
pricing: ModelPricing,
|
|
32
|
-
cacheDiscount = 0,
|
|
33
166
|
): CostInfo {
|
|
34
|
-
const
|
|
167
|
+
const priced = priceUsage(additionalUsage, pricing)
|
|
168
|
+
const rates = priced.unpriced > 0 ? {} : ratesFor(current, pricing)
|
|
35
169
|
return {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
170
|
+
...rates,
|
|
171
|
+
totalCost: current.totalCost + priced.cost,
|
|
172
|
+
cacheDiscount: current.cacheDiscount + priced.cacheDiscount,
|
|
173
|
+
unpricedTokens: current.unpricedTokens + priced.unpriced,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Record tokens that were consumed at a rate nobody has.
|
|
179
|
+
*
|
|
180
|
+
* The alternative was to add nothing and leave the total alone, which is how
|
|
181
|
+
* every run came to report `$0.00` for work that cost real money. Counting the
|
|
182
|
+
* tokens instead makes the gap a fact the caller can read and the budget guard
|
|
183
|
+
* can refuse on, rather than an absence that looks like an answer.
|
|
184
|
+
*
|
|
185
|
+
* The rate fields go, if they were there: a total that omits part of a run is
|
|
186
|
+
* not described by any single card.
|
|
187
|
+
*/
|
|
188
|
+
export function accumulateUnpricedCost(current: CostInfo, additionalUsage: TokenUsage): CostInfo {
|
|
189
|
+
return {
|
|
190
|
+
totalCost: current.totalCost,
|
|
191
|
+
cacheDiscount: current.cacheDiscount,
|
|
192
|
+
unpricedTokens: current.unpricedTokens + additionalUsage.totalTokens,
|
|
40
193
|
}
|
|
41
194
|
}
|
|
42
195
|
|
|
@@ -45,3 +198,20 @@ export function formatCost(usd: number): string {
|
|
|
45
198
|
if (usd < 0.01) return `$${usd.toFixed(4)}`
|
|
46
199
|
return `$${usd.toFixed(2)}`
|
|
47
200
|
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* How a total should be shown, given what is and is not known about it.
|
|
204
|
+
*
|
|
205
|
+
* Exists so that no surface has to re-derive the free/unknown distinction from
|
|
206
|
+
* two fields and get it subtly wrong. `@namzu/cli` printed
|
|
207
|
+
* `'$0.0000 (this provider reported no price)'` for every run, because every
|
|
208
|
+
* run was unpriced; now the two cases really are different and the string has
|
|
209
|
+
* to follow.
|
|
210
|
+
*/
|
|
211
|
+
export function describeCost(cost: CostInfo): string {
|
|
212
|
+
if (cost.unpricedTokens === 0) return formatCost(cost.totalCost)
|
|
213
|
+
if (cost.totalCost === 0) {
|
|
214
|
+
return `unknown (${cost.unpricedTokens} tokens at no known rate)`
|
|
215
|
+
}
|
|
216
|
+
return `at least ${formatCost(cost.totalCost)} (${cost.unpricedTokens} tokens at no known rate)`
|
|
217
|
+
}
|