@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.
Files changed (91) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/dist/advisory/executor.d.ts +9 -4
  3. package/dist/advisory/executor.d.ts.map +1 -1
  4. package/dist/advisory/executor.js +16 -9
  5. package/dist/advisory/executor.js.map +1 -1
  6. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  7. package/dist/agents/SupervisorAgent.js +12 -0
  8. package/dist/agents/SupervisorAgent.js.map +1 -1
  9. package/dist/constants/limits.d.ts +13 -0
  10. package/dist/constants/limits.d.ts.map +1 -1
  11. package/dist/constants/limits.js +14 -2
  12. package/dist/constants/limits.js.map +1 -1
  13. package/dist/manager/agent/lifecycle.d.ts +2 -2
  14. package/dist/manager/run/persistence.d.ts +37 -2
  15. package/dist/manager/run/persistence.d.ts.map +1 -1
  16. package/dist/manager/run/persistence.js +48 -7
  17. package/dist/manager/run/persistence.js.map +1 -1
  18. package/dist/pricing/catalogue.generated.d.ts +15 -0
  19. package/dist/pricing/catalogue.generated.d.ts.map +1 -0
  20. package/dist/pricing/catalogue.generated.js +293 -0
  21. package/dist/pricing/catalogue.generated.js.map +1 -0
  22. package/dist/pricing/index.d.ts +46 -0
  23. package/dist/pricing/index.d.ts.map +1 -0
  24. package/dist/pricing/index.js +70 -0
  25. package/dist/pricing/index.js.map +1 -0
  26. package/dist/public-runtime.d.ts +2 -1
  27. package/dist/public-runtime.d.ts.map +1 -1
  28. package/dist/public-runtime.js +6 -1
  29. package/dist/public-runtime.js.map +1 -1
  30. package/dist/public-types.d.ts +3 -1
  31. package/dist/public-types.d.ts.map +1 -1
  32. package/dist/run/LimitChecker.d.ts +10 -0
  33. package/dist/run/LimitChecker.d.ts.map +1 -1
  34. package/dist/run/LimitChecker.js +14 -2
  35. package/dist/run/LimitChecker.js.map +1 -1
  36. package/dist/runtime/query/checkpoint.d.ts +23 -2
  37. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  38. package/dist/runtime/query/checkpoint.js +24 -3
  39. package/dist/runtime/query/checkpoint.js.map +1 -1
  40. package/dist/runtime/query/guard.d.ts.map +1 -1
  41. package/dist/runtime/query/guard.js +1 -0
  42. package/dist/runtime/query/guard.js.map +1 -1
  43. package/dist/runtime/query/index.d.ts.map +1 -1
  44. package/dist/runtime/query/index.js +42 -0
  45. package/dist/runtime/query/index.js.map +1 -1
  46. package/dist/runtime/query/iteration/index.d.ts +44 -8
  47. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  48. package/dist/runtime/query/iteration/index.js +71 -13
  49. package/dist/runtime/query/iteration/index.js.map +1 -1
  50. package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
  51. package/dist/runtime/query/iteration/phases/advisory.js +9 -1
  52. package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
  53. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  54. package/dist/runtime/query/iteration/phases/compaction.js +11 -2
  55. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  56. package/dist/store/run/conformance.d.ts.map +1 -1
  57. package/dist/store/run/conformance.js +8 -2
  58. package/dist/store/run/conformance.js.map +1 -1
  59. package/dist/types/agent/supervisor.d.ts +29 -0
  60. package/dist/types/agent/supervisor.d.ts.map +1 -1
  61. package/dist/types/common/index.d.ts +38 -2
  62. package/dist/types/common/index.d.ts.map +1 -1
  63. package/dist/types/run/stop-reason.d.ts +31 -1
  64. package/dist/types/run/stop-reason.d.ts.map +1 -1
  65. package/dist/utils/cost.d.ts +60 -2
  66. package/dist/utils/cost.d.ts.map +1 -1
  67. package/dist/utils/cost.js +126 -11
  68. package/dist/utils/cost.js.map +1 -1
  69. package/package.json +1 -1
  70. package/src/advisory/executor.ts +16 -9
  71. package/src/agents/SupervisorAgent.ts +12 -0
  72. package/src/constants/limits.ts +14 -2
  73. package/src/manager/agent/lifecycle.ts +2 -2
  74. package/src/manager/run/persistence.ts +67 -7
  75. package/src/pricing/catalogue.generated.ts +308 -0
  76. package/src/pricing/index.ts +78 -0
  77. package/src/pricing/rates.source.json +206 -0
  78. package/src/public-runtime.ts +8 -0
  79. package/src/public-types.ts +3 -1
  80. package/src/run/LimitChecker.ts +24 -2
  81. package/src/runtime/query/checkpoint.ts +24 -3
  82. package/src/runtime/query/guard.ts +1 -0
  83. package/src/runtime/query/index.ts +42 -0
  84. package/src/runtime/query/iteration/index.ts +76 -13
  85. package/src/runtime/query/iteration/phases/advisory.ts +9 -1
  86. package/src/runtime/query/iteration/phases/compaction.ts +12 -2
  87. package/src/store/run/conformance.ts +8 -2
  88. package/src/types/agent/supervisor.ts +30 -0
  89. package/src/types/common/index.ts +38 -2
  90. package/src/types/run/stop-reason.ts +30 -0
  91. 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
- export function calculateCost(
12
- usage: TokenUsage,
13
- pricing: ModelPricing,
14
- cacheDiscount = 0,
15
- ): CostInfo {
16
- const inputCost = (usage.promptTokens / 1_000_000) * pricing.inputCostPer1M
17
- const outputCost = (usage.completionTokens / 1_000_000) * pricing.outputCostPer1M
18
- const gross = inputCost + outputCost
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: gross - cacheDiscount,
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 additional = calculateCost(additionalUsage, pricing, cacheDiscount)
167
+ const priced = priceUsage(additionalUsage, pricing)
168
+ const rates = priced.unpriced > 0 ? {} : ratesFor(current, pricing)
35
169
  return {
36
- inputCostPer1M: pricing.inputCostPer1M,
37
- outputCostPer1M: pricing.outputCostPer1M,
38
- totalCost: current.totalCost + additional.totalCost,
39
- cacheDiscount: current.cacheDiscount + additional.cacheDiscount,
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
+ }