faberun 0.10.0 → 0.12.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 (60) hide show
  1. package/README.md +6 -0
  2. package/integrations/claude-code/statusline.sh +31 -2
  3. package/package.json +2 -2
  4. package/skills/faberun/references/spec-format.md +9 -0
  5. package/src/campaign/chain.mjs +2 -1
  6. package/src/campaign/index.mjs +102 -1
  7. package/src/campaign/metrics.mjs +2 -2
  8. package/src/cli/brand.mjs +2 -0
  9. package/src/cli/campaign-contract.mjs +55 -0
  10. package/src/cli/campaign.mjs +48 -13
  11. package/src/cli/init.mjs +8 -2
  12. package/src/cli/launch.mjs +2 -1
  13. package/src/cli/plan.mjs +3 -2
  14. package/src/cli/project.mjs +150 -0
  15. package/src/cli/skills.mjs +2 -3
  16. package/src/cli/update.mjs +7 -10
  17. package/src/cli.mjs +30 -4
  18. package/src/contract/index.mjs +44 -0
  19. package/src/contract/task-packet.mjs +12 -3
  20. package/src/engine/cancel.mjs +13 -0
  21. package/src/engine/dispatch.mjs +20 -2
  22. package/src/engine/lifecycle.mjs +38 -61
  23. package/src/engine/live-preflight.mjs +2 -1
  24. package/src/engine/notify-queue.mjs +9 -1
  25. package/src/engine/pricing-seed.json +543 -0
  26. package/src/engine/pricing-seed.mjs +54 -0
  27. package/src/engine/process.mjs +8 -1
  28. package/src/engine/result-file.mjs +2 -1
  29. package/src/engine/resume.mjs +6 -2
  30. package/src/engine/run-identity.mjs +9 -2
  31. package/src/engine/scheduler.mjs +40 -6
  32. package/src/engine/settle-judge.mjs +141 -0
  33. package/src/engine/settle.mjs +1 -1
  34. package/src/harnesses/protocol.mjs +46 -10
  35. package/src/harnesses/replay/bin.mjs +10 -1
  36. package/src/host/home.mjs +10 -3
  37. package/src/host/platform.mjs +111 -0
  38. package/src/host/preflight.mjs +43 -21
  39. package/src/host/projects.mjs +155 -0
  40. package/src/notify/index.mjs +88 -10
  41. package/src/plan/pipeline.mjs +32 -9
  42. package/src/plan/repo-facts.mjs +80 -2
  43. package/src/plan/spec.mjs +2 -1
  44. package/src/repo/declared-paths.mjs +7 -1
  45. package/src/repo/integrate.mjs +1 -1
  46. package/src/repo/scope-closure.mjs +2 -1
  47. package/src/repo/signal.mjs +24 -7
  48. package/src/repo/source-identity.mjs +7 -0
  49. package/src/repo/workspace.mjs +12 -2
  50. package/src/repo/worktree.mjs +89 -22
  51. package/src/report/next.mjs +66 -6
  52. package/src/report/progress.mjs +724 -0
  53. package/src/report/render.mjs +3 -3
  54. package/src/run/migrate.mjs +278 -0
  55. package/src/run/paths.mjs +189 -0
  56. package/src/seat/index.mjs +2 -1
  57. package/src/web/app.css +170 -0
  58. package/src/web/app.mjs +690 -0
  59. package/src/web/index.html +38 -283
  60. package/src/web/server.mjs +197 -182
@@ -24,6 +24,7 @@ import { tmpdir } from "node:os";
24
24
  import { validateContract } from "../contract/index.mjs";
25
25
  import { validateNodeSnapshot } from "../contract/snapshot.mjs";
26
26
  import { compactCost as formatCost } from "../util.mjs";
27
+ import { runsRoot } from "../run/paths.mjs";
27
28
 
28
29
  /** @typedef {import("../harnesses/index.mjs").ProbeResult} ProbeResult */
29
30
  /** @typedef {import("../harnesses/index.mjs").ProviderEnvelope} ProviderEnvelope */
@@ -263,7 +264,7 @@ function redactProviderText(value) {
263
264
  export function reusedDoneWarnings(contract) {
264
265
  /** @type {string[]} */
265
266
  const warnings = [];
266
- const runsDir = join(contract.cwd, ".runs");
267
+ const runsDir = runsRoot(contract.cwd);
267
268
  if (!existsSync(runsDir)) return warnings;
268
269
  const ownRunDir = join(runsDir, contract.id);
269
270
  for (const name of readdirSync(runsDir)) {
@@ -222,6 +222,12 @@ export function attentionDedupeKey(runId, anchor, slot) {
222
222
  * already announced. A receipt is the durable record, so a restarted
223
223
  * supervisor reads the same slot as already sent.
224
224
  *
225
+ * The message is rendered exactly once, with `runDir` set, and that one
226
+ * string is handed to both the inbox entry and the notify queue: `enqueue`
227
+ * never re-renders a `summary` it is given. Rendering it twice -- once for
228
+ * the inbox with no `runDir` and once inside `enqueue` with `runDir` filled
229
+ * in -- is what let the inbox and the transport disagree before.
230
+ *
225
231
  * @param {string} runDir
226
232
  * @param {{anchor: string, code?: string|null, campaignId?: string|null, now?: number}} options
227
233
  * @returns {Promise<number|null>} the emitted slot, or null when none was due
@@ -234,10 +240,11 @@ export async function emitScheduledAttention(runDir, options) {
234
240
  const runId = basename(runDir);
235
241
  const dedupeKey = attentionDedupeKey(runId, options.anchor, slot);
236
242
  if (alreadyNotified(runDir, dedupeKey)) return null;
237
- const summary = renderNotification({
243
+ const summary = await renderNotification({
238
244
  type: "attention",
239
245
  runId,
240
246
  errorCode: options.code ?? null,
247
+ runDir,
241
248
  });
242
249
  // The run-level attention also lands in the campaign-level inbox, which is
243
250
  // the append-only record the managed signal block summarises.
@@ -255,6 +262,7 @@ export async function emitScheduledAttention(runDir, options) {
255
262
  runId,
256
263
  errorCode: options.code ?? null,
257
264
  dedupeKey,
265
+ summary,
258
266
  });
259
267
  return slot;
260
268
  }
@@ -0,0 +1,543 @@
1
+ {
2
+ "fetchedAt": "2026-09-19",
3
+ "source": "https://models.dev/api.json",
4
+ "providers": [
5
+ "anthropic",
6
+ "deepseek",
7
+ "google",
8
+ "openai",
9
+ "zhipuai"
10
+ ],
11
+ "models": {
12
+ "claude-fable-5": {
13
+ "inputPerMTok": 10,
14
+ "cachedInputPerMTok": 1,
15
+ "outputPerMTok": 50
16
+ },
17
+ "claude-fable-5-1": {
18
+ "inputPerMTok": 10,
19
+ "cachedInputPerMTok": 0.25,
20
+ "outputPerMTok": 50
21
+ },
22
+ "claude-haiku-4-5": {
23
+ "inputPerMTok": 1,
24
+ "cachedInputPerMTok": 0.1,
25
+ "outputPerMTok": 5
26
+ },
27
+ "claude-haiku-4-5-20251001": {
28
+ "inputPerMTok": 1,
29
+ "cachedInputPerMTok": 0.1,
30
+ "outputPerMTok": 5
31
+ },
32
+ "claude-opus-4-5": {
33
+ "inputPerMTok": 5,
34
+ "cachedInputPerMTok": 0.5,
35
+ "outputPerMTok": 25
36
+ },
37
+ "claude-opus-4-5-20251101": {
38
+ "inputPerMTok": 5,
39
+ "cachedInputPerMTok": 0.5,
40
+ "outputPerMTok": 25
41
+ },
42
+ "claude-opus-4-6": {
43
+ "inputPerMTok": 5,
44
+ "cachedInputPerMTok": 0.5,
45
+ "outputPerMTok": 25
46
+ },
47
+ "claude-opus-4-7": {
48
+ "inputPerMTok": 5,
49
+ "cachedInputPerMTok": 0.5,
50
+ "outputPerMTok": 25
51
+ },
52
+ "claude-opus-4-8": {
53
+ "inputPerMTok": 5,
54
+ "cachedInputPerMTok": 0.5,
55
+ "outputPerMTok": 25
56
+ },
57
+ "claude-opus-5": {
58
+ "inputPerMTok": 5,
59
+ "cachedInputPerMTok": 0.5,
60
+ "outputPerMTok": 25
61
+ },
62
+ "claude-sonnet-4-5": {
63
+ "inputPerMTok": 3,
64
+ "cachedInputPerMTok": 0.3,
65
+ "outputPerMTok": 15
66
+ },
67
+ "claude-sonnet-4-5-20250929": {
68
+ "inputPerMTok": 3,
69
+ "cachedInputPerMTok": 0.3,
70
+ "outputPerMTok": 15
71
+ },
72
+ "claude-sonnet-4-6": {
73
+ "inputPerMTok": 3,
74
+ "cachedInputPerMTok": 0.3,
75
+ "outputPerMTok": 15
76
+ },
77
+ "claude-sonnet-5": {
78
+ "inputPerMTok": 2,
79
+ "cachedInputPerMTok": 0.2,
80
+ "outputPerMTok": 10
81
+ },
82
+ "deep-research-max-preview-04-2026": {
83
+ "inputPerMTok": 2,
84
+ "cachedInputPerMTok": 0.2,
85
+ "outputPerMTok": 12
86
+ },
87
+ "deep-research-preview-04-2026": {
88
+ "inputPerMTok": 2,
89
+ "cachedInputPerMTok": 0.2,
90
+ "outputPerMTok": 12
91
+ },
92
+ "deepseek-flash": {
93
+ "inputPerMTok": 0.15,
94
+ "cachedInputPerMTok": 0.003,
95
+ "outputPerMTok": 0.6
96
+ },
97
+ "deepseek-v4-flash": {
98
+ "inputPerMTok": 0.15,
99
+ "cachedInputPerMTok": 0.003,
100
+ "outputPerMTok": 0.6
101
+ },
102
+ "deepseek-v4-flash-vision-exp": {
103
+ "inputPerMTok": 0.15,
104
+ "cachedInputPerMTok": 0.003,
105
+ "outputPerMTok": 0.6
106
+ },
107
+ "deepseek-v4-pro": {
108
+ "inputPerMTok": 0.435,
109
+ "cachedInputPerMTok": 0.003625,
110
+ "outputPerMTok": 0.87
111
+ },
112
+ "gemini-2.5-computer-use-preview-10-2025": {
113
+ "inputPerMTok": 1.25,
114
+ "outputPerMTok": 10
115
+ },
116
+ "gemini-2.5-flash": {
117
+ "inputPerMTok": 0.3,
118
+ "cachedInputPerMTok": 0.03,
119
+ "outputPerMTok": 2.5
120
+ },
121
+ "gemini-2.5-flash-image": {
122
+ "inputPerMTok": 0.3,
123
+ "cachedInputPerMTok": 0.075,
124
+ "outputPerMTok": 30
125
+ },
126
+ "gemini-2.5-flash-lite": {
127
+ "inputPerMTok": 0.1,
128
+ "cachedInputPerMTok": 0.01,
129
+ "outputPerMTok": 0.4
130
+ },
131
+ "gemini-2.5-flash-preview-tts": {
132
+ "inputPerMTok": 0.5,
133
+ "outputPerMTok": 10
134
+ },
135
+ "gemini-2.5-pro": {
136
+ "inputPerMTok": 1.25,
137
+ "cachedInputPerMTok": 0.125,
138
+ "outputPerMTok": 10
139
+ },
140
+ "gemini-2.5-pro-preview-tts": {
141
+ "inputPerMTok": 1,
142
+ "outputPerMTok": 20
143
+ },
144
+ "gemini-3-flash-preview": {
145
+ "inputPerMTok": 0.5,
146
+ "cachedInputPerMTok": 0.05,
147
+ "outputPerMTok": 3
148
+ },
149
+ "gemini-3-pro-image": {
150
+ "inputPerMTok": 2,
151
+ "outputPerMTok": 120
152
+ },
153
+ "gemini-3-pro-image-preview": {
154
+ "inputPerMTok": 2,
155
+ "outputPerMTok": 120
156
+ },
157
+ "gemini-3.1-flash-image": {
158
+ "inputPerMTok": 0.5,
159
+ "outputPerMTok": 60
160
+ },
161
+ "gemini-3.1-flash-image-preview": {
162
+ "inputPerMTok": 0.5,
163
+ "outputPerMTok": 60
164
+ },
165
+ "gemini-3.1-flash-lite": {
166
+ "inputPerMTok": 0.25,
167
+ "cachedInputPerMTok": 0.025,
168
+ "outputPerMTok": 1.5
169
+ },
170
+ "gemini-3.1-flash-lite-image": {
171
+ "inputPerMTok": 0.25,
172
+ "outputPerMTok": 30
173
+ },
174
+ "gemini-3.1-flash-lite-preview": {
175
+ "inputPerMTok": 0.25,
176
+ "cachedInputPerMTok": 0.025,
177
+ "outputPerMTok": 1.5
178
+ },
179
+ "gemini-3.1-flash-live-preview": {
180
+ "inputPerMTok": 0.75,
181
+ "outputPerMTok": 4.5
182
+ },
183
+ "gemini-3.1-flash-tts-preview": {
184
+ "inputPerMTok": 1,
185
+ "outputPerMTok": 20
186
+ },
187
+ "gemini-3.1-pro-preview": {
188
+ "inputPerMTok": 2,
189
+ "cachedInputPerMTok": 0.2,
190
+ "outputPerMTok": 12
191
+ },
192
+ "gemini-3.1-pro-preview-customtools": {
193
+ "inputPerMTok": 2,
194
+ "cachedInputPerMTok": 0.2,
195
+ "outputPerMTok": 12
196
+ },
197
+ "gemini-3.5-flash": {
198
+ "inputPerMTok": 1.5,
199
+ "cachedInputPerMTok": 0.15,
200
+ "outputPerMTok": 9
201
+ },
202
+ "gemini-3.5-flash-lite": {
203
+ "inputPerMTok": 0.3,
204
+ "cachedInputPerMTok": 0.03,
205
+ "outputPerMTok": 2.5
206
+ },
207
+ "gemini-3.5-live-translate-preview": {
208
+ "inputPerMTok": 3.5,
209
+ "outputPerMTok": 21
210
+ },
211
+ "gemini-3.6-flash": {
212
+ "inputPerMTok": 0.75,
213
+ "cachedInputPerMTok": 0.075,
214
+ "outputPerMTok": 3.75
215
+ },
216
+ "gemini-3.7-flash": {
217
+ "inputPerMTok": 0.75,
218
+ "cachedInputPerMTok": 0.075,
219
+ "outputPerMTok": 3.75
220
+ },
221
+ "gemini-3.8-flash": {
222
+ "inputPerMTok": 0.75,
223
+ "cachedInputPerMTok": 0.075,
224
+ "outputPerMTok": 3.75
225
+ },
226
+ "gemini-embedding-001": {
227
+ "inputPerMTok": 0.15,
228
+ "outputPerMTok": 0
229
+ },
230
+ "gemini-embedding-2": {
231
+ "inputPerMTok": 0.2,
232
+ "outputPerMTok": 0
233
+ },
234
+ "gemini-flash-latest": {
235
+ "inputPerMTok": 0.75,
236
+ "cachedInputPerMTok": 0.075,
237
+ "outputPerMTok": 3.75
238
+ },
239
+ "gemini-flash-lite-latest": {
240
+ "inputPerMTok": 0.3,
241
+ "cachedInputPerMTok": 0.03,
242
+ "outputPerMTok": 2.5
243
+ },
244
+ "gemini-omni-flash-preview": {
245
+ "inputPerMTok": 1.5,
246
+ "outputPerMTok": 17.5
247
+ },
248
+ "glm-4.5": {
249
+ "inputPerMTok": 0.6,
250
+ "cachedInputPerMTok": 0.11,
251
+ "outputPerMTok": 2.2
252
+ },
253
+ "glm-4.5-air": {
254
+ "inputPerMTok": 0.2,
255
+ "cachedInputPerMTok": 0.03,
256
+ "outputPerMTok": 1.1
257
+ },
258
+ "glm-4.5-flash": {
259
+ "inputPerMTok": 0,
260
+ "cachedInputPerMTok": 0,
261
+ "outputPerMTok": 0
262
+ },
263
+ "glm-4.5v": {
264
+ "inputPerMTok": 0.6,
265
+ "outputPerMTok": 1.8
266
+ },
267
+ "glm-4.6": {
268
+ "inputPerMTok": 0.6,
269
+ "cachedInputPerMTok": 0.11,
270
+ "outputPerMTok": 2.2
271
+ },
272
+ "glm-4.6v": {
273
+ "inputPerMTok": 0.3,
274
+ "outputPerMTok": 0.9
275
+ },
276
+ "glm-4.7": {
277
+ "inputPerMTok": 0.6,
278
+ "cachedInputPerMTok": 0.11,
279
+ "outputPerMTok": 2.2
280
+ },
281
+ "glm-4.7-flash": {
282
+ "inputPerMTok": 0,
283
+ "cachedInputPerMTok": 0,
284
+ "outputPerMTok": 0
285
+ },
286
+ "glm-4.7-flashx": {
287
+ "inputPerMTok": 0.07,
288
+ "cachedInputPerMTok": 0.01,
289
+ "outputPerMTok": 0.4
290
+ },
291
+ "glm-5": {
292
+ "inputPerMTok": 1,
293
+ "cachedInputPerMTok": 0.2,
294
+ "outputPerMTok": 3.2
295
+ },
296
+ "glm-5.1": {
297
+ "inputPerMTok": 1.4,
298
+ "cachedInputPerMTok": 0.26,
299
+ "outputPerMTok": 4.4
300
+ },
301
+ "glm-5.2": {
302
+ "inputPerMTok": 1.4,
303
+ "cachedInputPerMTok": 0.26,
304
+ "outputPerMTok": 4.4
305
+ },
306
+ "glm-5.3": {
307
+ "inputPerMTok": 1.4,
308
+ "cachedInputPerMTok": 0.26,
309
+ "outputPerMTok": 4.4
310
+ },
311
+ "glm-5.3-flash": {
312
+ "inputPerMTok": 0.15,
313
+ "cachedInputPerMTok": 0.03,
314
+ "outputPerMTok": 0.5
315
+ },
316
+ "glm-5.3-flashx": {
317
+ "inputPerMTok": 0.37,
318
+ "cachedInputPerMTok": 0.075,
319
+ "outputPerMTok": 1.25
320
+ },
321
+ "glm-5v-turbo": {
322
+ "inputPerMTok": 5,
323
+ "cachedInputPerMTok": 1.2,
324
+ "outputPerMTok": 22
325
+ },
326
+ "gpt-3.5-turbo": {
327
+ "inputPerMTok": 0.5,
328
+ "cachedInputPerMTok": 0,
329
+ "outputPerMTok": 1.5
330
+ },
331
+ "gpt-4": {
332
+ "inputPerMTok": 30,
333
+ "outputPerMTok": 60
334
+ },
335
+ "gpt-4-turbo": {
336
+ "inputPerMTok": 10,
337
+ "outputPerMTok": 30
338
+ },
339
+ "gpt-4.1": {
340
+ "inputPerMTok": 2,
341
+ "cachedInputPerMTok": 0.5,
342
+ "outputPerMTok": 8
343
+ },
344
+ "gpt-4.1-mini": {
345
+ "inputPerMTok": 0.4,
346
+ "cachedInputPerMTok": 0.1,
347
+ "outputPerMTok": 1.6
348
+ },
349
+ "gpt-4.1-nano": {
350
+ "inputPerMTok": 0.1,
351
+ "cachedInputPerMTok": 0.025,
352
+ "outputPerMTok": 0.4
353
+ },
354
+ "gpt-4o": {
355
+ "inputPerMTok": 2.5,
356
+ "cachedInputPerMTok": 1.25,
357
+ "outputPerMTok": 10
358
+ },
359
+ "gpt-4o-2024-05-13": {
360
+ "inputPerMTok": 5,
361
+ "outputPerMTok": 15
362
+ },
363
+ "gpt-4o-2024-08-06": {
364
+ "inputPerMTok": 2.5,
365
+ "cachedInputPerMTok": 1.25,
366
+ "outputPerMTok": 10
367
+ },
368
+ "gpt-4o-2024-11-20": {
369
+ "inputPerMTok": 2.5,
370
+ "cachedInputPerMTok": 1.25,
371
+ "outputPerMTok": 10
372
+ },
373
+ "gpt-4o-mini": {
374
+ "inputPerMTok": 0.15,
375
+ "cachedInputPerMTok": 0.075,
376
+ "outputPerMTok": 0.6
377
+ },
378
+ "gpt-5": {
379
+ "inputPerMTok": 1.25,
380
+ "cachedInputPerMTok": 0.125,
381
+ "outputPerMTok": 10
382
+ },
383
+ "gpt-5-mini": {
384
+ "inputPerMTok": 0.25,
385
+ "cachedInputPerMTok": 0.025,
386
+ "outputPerMTok": 2
387
+ },
388
+ "gpt-5-nano": {
389
+ "inputPerMTok": 0.05,
390
+ "cachedInputPerMTok": 0.005,
391
+ "outputPerMTok": 0.4
392
+ },
393
+ "gpt-5-pro": {
394
+ "inputPerMTok": 15,
395
+ "outputPerMTok": 120
396
+ },
397
+ "gpt-5.1": {
398
+ "inputPerMTok": 1.25,
399
+ "cachedInputPerMTok": 0.125,
400
+ "outputPerMTok": 10
401
+ },
402
+ "gpt-5.2": {
403
+ "inputPerMTok": 1.75,
404
+ "cachedInputPerMTok": 0.175,
405
+ "outputPerMTok": 14
406
+ },
407
+ "gpt-5.2-chat-latest": {
408
+ "inputPerMTok": 1.75,
409
+ "cachedInputPerMTok": 0.175,
410
+ "outputPerMTok": 14
411
+ },
412
+ "gpt-5.2-pro": {
413
+ "inputPerMTok": 21,
414
+ "outputPerMTok": 168
415
+ },
416
+ "gpt-5.3-chat-latest": {
417
+ "inputPerMTok": 1.75,
418
+ "cachedInputPerMTok": 0.175,
419
+ "outputPerMTok": 14
420
+ },
421
+ "gpt-5.3-codex": {
422
+ "inputPerMTok": 1.75,
423
+ "cachedInputPerMTok": 0.175,
424
+ "outputPerMTok": 14
425
+ },
426
+ "gpt-5.3-codex-spark": {
427
+ "inputPerMTok": 1.75,
428
+ "cachedInputPerMTok": 0.175,
429
+ "outputPerMTok": 14
430
+ },
431
+ "gpt-5.4": {
432
+ "inputPerMTok": 2.5,
433
+ "cachedInputPerMTok": 0.25,
434
+ "outputPerMTok": 15
435
+ },
436
+ "gpt-5.4-mini": {
437
+ "inputPerMTok": 0.75,
438
+ "cachedInputPerMTok": 0.075,
439
+ "outputPerMTok": 4.5
440
+ },
441
+ "gpt-5.4-nano": {
442
+ "inputPerMTok": 0.2,
443
+ "cachedInputPerMTok": 0.02,
444
+ "outputPerMTok": 1.25
445
+ },
446
+ "gpt-5.4-pro": {
447
+ "inputPerMTok": 30,
448
+ "outputPerMTok": 180
449
+ },
450
+ "gpt-5.5": {
451
+ "inputPerMTok": 5,
452
+ "cachedInputPerMTok": 0.5,
453
+ "outputPerMTok": 30
454
+ },
455
+ "gpt-5.5-pro": {
456
+ "inputPerMTok": 30,
457
+ "outputPerMTok": 180
458
+ },
459
+ "gpt-5.6": {
460
+ "inputPerMTok": 4,
461
+ "cachedInputPerMTok": 0.4,
462
+ "outputPerMTok": 20
463
+ },
464
+ "gpt-5.6-luna": {
465
+ "inputPerMTok": 0.2,
466
+ "cachedInputPerMTok": 0.02,
467
+ "outputPerMTok": 1.2
468
+ },
469
+ "gpt-5.6-sol": {
470
+ "inputPerMTok": 4,
471
+ "cachedInputPerMTok": 0.4,
472
+ "outputPerMTok": 20
473
+ },
474
+ "gpt-5.6-terra": {
475
+ "inputPerMTok": 2,
476
+ "cachedInputPerMTok": 0.2,
477
+ "outputPerMTok": 12
478
+ },
479
+ "gpt-6-astra": {
480
+ "inputPerMTok": 10,
481
+ "cachedInputPerMTok": 1,
482
+ "outputPerMTok": 50
483
+ },
484
+ "gpt-image-2": {
485
+ "inputPerMTok": 5,
486
+ "cachedInputPerMTok": 1.25,
487
+ "outputPerMTok": 30
488
+ },
489
+ "gpt-realtime-2.1": {
490
+ "inputPerMTok": 4,
491
+ "cachedInputPerMTok": 0.4,
492
+ "outputPerMTok": 24
493
+ },
494
+ "lyria-3-clip-preview": {
495
+ "inputPerMTok": 0,
496
+ "outputPerMTok": 0
497
+ },
498
+ "lyria-3-pro-preview": {
499
+ "inputPerMTok": 0,
500
+ "outputPerMTok": 0
501
+ },
502
+ "o1": {
503
+ "inputPerMTok": 15,
504
+ "cachedInputPerMTok": 7.5,
505
+ "outputPerMTok": 60
506
+ },
507
+ "o1-pro": {
508
+ "inputPerMTok": 150,
509
+ "outputPerMTok": 600
510
+ },
511
+ "o3": {
512
+ "inputPerMTok": 2,
513
+ "cachedInputPerMTok": 0.5,
514
+ "outputPerMTok": 8
515
+ },
516
+ "o3-mini": {
517
+ "inputPerMTok": 1.1,
518
+ "cachedInputPerMTok": 0.55,
519
+ "outputPerMTok": 4.4
520
+ },
521
+ "o3-pro": {
522
+ "inputPerMTok": 20,
523
+ "outputPerMTok": 80
524
+ },
525
+ "o4-mini": {
526
+ "inputPerMTok": 1.1,
527
+ "cachedInputPerMTok": 0.275,
528
+ "outputPerMTok": 4.4
529
+ },
530
+ "text-embedding-3-large": {
531
+ "inputPerMTok": 0.13,
532
+ "outputPerMTok": 0
533
+ },
534
+ "text-embedding-3-small": {
535
+ "inputPerMTok": 0.02,
536
+ "outputPerMTok": 0
537
+ },
538
+ "text-embedding-ada-002": {
539
+ "inputPerMTok": 0.1,
540
+ "outputPerMTok": 0
541
+ }
542
+ }
543
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The vendored price snapshot every invocation is priced against when its
3
+ * runtime declares no rates of its own: models.dev's published first-party
4
+ * rates for the five vendors this product's harnesses talk to (anthropic,
5
+ * deepseek, google, openai, zhipuai), filtered at vendor time.
6
+ *
7
+ * A dated snapshot, not a live query, by design: the live per-invocation spend
8
+ * surfaces that motivated the idea were investigated and closed (DeepSeek
9
+ * exposes only an account-wide balance scalar, Zhipu publishes no billing
10
+ * endpoint at all, Google Cloud Billing exposes a budget cap without a
11
+ * pre-configured BigQuery export), so the owner's decision was to vendor
12
+ * models.dev and always compute cost from these rates and the token counts.
13
+ * Update it only by re-vendoring, never by fetching at run time.
14
+ *
15
+ * The lookup keys by model id alone: at vendor time (2026-09-19) these five
16
+ * providers carried zero colliding model ids, so no vendor or harness
17
+ * disambiguation is needed for exactly this set. A model with a documented
18
+ * higher-context tier (e.g. openai's gpt-5.6-sol above 200k tokens) is
19
+ * recorded at its base-tier rate only -- a known, unmeasured simplification,
20
+ * not a bug; tiers are deliberately not modelled.
21
+ */
22
+ import { readFileSync } from "node:fs";
23
+ import { fileURLToPath } from "node:url";
24
+
25
+ /** @typedef {import("./process.mjs").RuntimePricing} RuntimePricing */
26
+
27
+ /** @type {{fetchedAt: string, source: string, providers: string[], models: Record<string, RuntimePricing>}} */
28
+ const SEED = JSON.parse(readFileSync(fileURLToPath(new URL("./pricing-seed.json", import.meta.url)), "utf8"));
29
+
30
+ const DAY_MS = 86_400_000;
31
+
32
+ /**
33
+ * The vendored rate for one model id, or undefined when the seed does not
34
+ * know it -- an unknown model keeps its cost unknown rather than unpriced.
35
+ *
36
+ * @param {string|null|undefined} model
37
+ * @returns {RuntimePricing|undefined}
38
+ */
39
+ export function seedPricing(model) {
40
+ if (typeof model !== "string") return undefined;
41
+ return SEED.models[model];
42
+ }
43
+
44
+ /**
45
+ * How old the vendored snapshot is, in whole days, and whether it is stale.
46
+ *
47
+ * @param {Date} [now]
48
+ * @returns {{fetchedAt: string, ageDays: number, stale: boolean}}
49
+ */
50
+ export function pricingSeedAge(now = new Date()) {
51
+ const ageDays = Math.floor((now.getTime() - Date.parse(SEED.fetchedAt)) / DAY_MS);
52
+ // The 90-day staleness threshold is a first guess, not a measurement (2026-09-19).
53
+ return { fetchedAt: SEED.fetchedAt, ageDays, stale: ageDays > 90 };
54
+ }
@@ -14,6 +14,7 @@ import { errorCode, errorMessage } from "../util.mjs";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import { harnessCapabilities, normalizeProviderResult, providerCommand } from "../harnesses/index.mjs";
16
16
  import { latestTimeoutSec } from "./backoff.mjs";
17
+ import { seedPricing } from "./pricing-seed.mjs";
17
18
  import { attemptWorkspace, sealAttempt } from "../repo/worktree.mjs";
18
19
 
19
20
  import { invocationOwned, processGroupAlive, processStartTokenMatches } from "./process-identity.mjs";
@@ -550,6 +551,11 @@ export function invocationResult(invocation, runtime, options = {}) {
550
551
  * missing measurement, not a zero contribution, so it keeps the whole record
551
552
  * `unknown` (`costUsd: null`) rather than understating it.
552
553
  *
554
+ * When the runtime declares no pricing of its own, the rates fall back to the
555
+ * vendored models.dev snapshot (`pricing-seed.mjs`) keyed by the runtime's
556
+ * model, and a seed-priced invocation is exactly as `priced` as an
557
+ * operator-declared one; a model the seed does not know stays unknown.
558
+ *
553
559
  * It lives beside `invocationResult`, the second source point, rather than in
554
560
  * `run/usage.mjs`, which re-exports it: that module already imports this one,
555
561
  * so defining it here is what keeps the two source points acyclic.
@@ -561,7 +567,8 @@ export function invocationResult(invocation, runtime, options = {}) {
561
567
  */
562
568
  export function priceUsage(runtime, usage, reportedCostUsd) {
563
569
  if (typeof reportedCostUsd === "number") return { costUsd: reportedCostUsd, costProvenance: undefined };
564
- const pricing = /** @type {{pricing?: RuntimePricing}|null|undefined} */ (runtime)?.pricing;
570
+ const declaredRuntime = /** @type {{pricing?: RuntimePricing, model?: string}|null|undefined} */ (runtime);
571
+ const pricing = declaredRuntime?.pricing ?? seedPricing(declaredRuntime?.model);
565
572
  if (!pricing) return { costUsd: null, costProvenance: undefined };
566
573
  /** @type {[number|null|undefined, number|undefined][]} */
567
574
  const terms = [
@@ -22,6 +22,7 @@ import { parseJudge } from "./prompts.mjs";
22
22
  import { discoveryOutput, parseWorkerResult } from "../contract/worker-result.mjs";
23
23
  import { readJson, writeJsonAtomic, writeTextAtomic } from "../run/store.mjs";
24
24
  import { routeRuntimeForState, runtimeSnapshot } from "./failover.mjs";
25
+ import { RUNS_DIR_NAME } from "../run/paths.mjs";
25
26
 
26
27
  /**
27
28
  * Thrown by `resolveWorkerResult` when an execution result declares `output`.
@@ -47,7 +48,7 @@ export function workerResultPath(runDir, nodeId) {
47
48
  }
48
49
  /** @param {string} runDir @param {string} nodeId @param {string} workspace @returns {string} */
49
50
  export function attemptWorkerResultPath(runDir, nodeId, workspace) {
50
- return join(workspace, ".runs", "results", `${nodeId}.json`);
51
+ return join(workspace, RUNS_DIR_NAME, "results", `${nodeId}.json`);
51
52
  }
52
53
  /** @param {string} workspace @param {string} nodeId */
53
54
  export function clearAttemptWorkerResult(workspace, nodeId) {