@dudousxd/nestjs-agent-opencode 0.1.2 → 0.2.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.
@@ -249,6 +249,41 @@ function toFormAnswer(form, answers = {}) {
249
249
  __name(toFormAnswer, "toFormAnswer");
250
250
 
251
251
  // src/turn.ts
252
+ function emptyUsage() {
253
+ return {
254
+ inputTokens: 0,
255
+ outputTokens: 0,
256
+ cacheReadTokens: 0,
257
+ cacheWriteTokens: 0,
258
+ reasoningTokens: 0,
259
+ costUsd: 0
260
+ };
261
+ }
262
+ __name(emptyUsage, "emptyUsage");
263
+ function addUsage(a, b) {
264
+ const x = a ?? emptyUsage();
265
+ const y = b ?? emptyUsage();
266
+ return {
267
+ inputTokens: x.inputTokens + (y.inputTokens ?? 0),
268
+ outputTokens: x.outputTokens + (y.outputTokens ?? 0),
269
+ cacheReadTokens: (x.cacheReadTokens ?? 0) + (y.cacheReadTokens ?? 0),
270
+ cacheWriteTokens: (x.cacheWriteTokens ?? 0) + (y.cacheWriteTokens ?? 0),
271
+ reasoningTokens: (x.reasoningTokens ?? 0) + (y.reasoningTokens ?? 0),
272
+ costUsd: x.costUsd + (y.costUsd ?? 0)
273
+ };
274
+ }
275
+ __name(addUsage, "addUsage");
276
+ function subUsage(a, b) {
277
+ return {
278
+ inputTokens: a.inputTokens - b.inputTokens,
279
+ outputTokens: a.outputTokens - b.outputTokens,
280
+ cacheReadTokens: a.cacheReadTokens - b.cacheReadTokens,
281
+ cacheWriteTokens: a.cacheWriteTokens - b.cacheWriteTokens,
282
+ reasoningTokens: a.reasoningTokens - b.reasoningTokens,
283
+ costUsd: a.costUsd - b.costUsd
284
+ };
285
+ }
286
+ __name(subUsage, "subUsage");
252
287
  var ASK_TOOL_NAME = "ask";
253
288
  var FAILED_STATUSES = /* @__PURE__ */ new Set([
254
289
  "error",
@@ -314,11 +349,22 @@ var OpenCodeTurn = class {
314
349
  hidden = /* @__PURE__ */ new Set();
315
350
  milestones = [];
316
351
  waiting;
317
- usage = {
318
- inputTokens: 0,
319
- outputTokens: 0
352
+ /** What this turn object saw spent, and how much of it a milestone already reported. */
353
+ spent = emptyUsage();
354
+ reported = emptyUsage();
355
+ priced = false;
356
+ model;
357
+ /** What the session had spent when this turn prompted it; what OpenCode reported since. */
358
+ baseline;
359
+ seen = {
360
+ cost: 0,
361
+ input: 0,
362
+ output: 0,
363
+ reasoning: 0,
364
+ read: 0,
365
+ write: 0
320
366
  };
321
- costUsd;
367
+ compacted = false;
322
368
  stepOpen = false;
323
369
  sawText = false;
324
370
  separator = false;
@@ -328,6 +374,24 @@ var OpenCodeTurn = class {
328
374
  stepError;
329
375
  constructor(a) {
330
376
  this.a = a;
377
+ this.model = modelOfLabel(a.input.model);
378
+ }
379
+ /**
380
+ * What the session had spent before this turn prompted it (`session.get`). When the execution
381
+ * ends, what the session spent since that this turn's events did not report — the title OpenCode
382
+ * generates, a compaction: OpenCode records those calls on the session without streaming them — is
383
+ * recorded as a usage row of its own.
384
+ */
385
+ setBaseline(info) {
386
+ if (info?.tokens === void 0) return;
387
+ this.baseline = {
388
+ cost: Number(info.cost) || 0,
389
+ tokens: info.tokens
390
+ };
391
+ }
392
+ /** What the run spent so far: carried from before this turn object, plus what it saw. */
393
+ totalUsage() {
394
+ return addUsage(this.a.carried, this.spent);
331
395
  }
332
396
  get sessionId() {
333
397
  return this.a.sessionId;
@@ -410,7 +474,15 @@ var OpenCodeTurn = class {
410
474
  this.chain = this.chain.then(() => ask.kind === "approval" ? this.decided(ask, reply, tell) : this.answered(ask, reply, tell)).then(resolve, reject);
411
475
  });
412
476
  }
413
- reach(milestone) {
477
+ reach(reached) {
478
+ const usage = subUsage(this.spent, this.reported);
479
+ this.reported = {
480
+ ...this.spent
481
+ };
482
+ const milestone = {
483
+ ...reached,
484
+ usage
485
+ };
414
486
  const waiting = this.waiting;
415
487
  if (waiting !== void 0) {
416
488
  this.waiting = void 0;
@@ -566,9 +638,26 @@ var OpenCodeTurn = class {
566
638
  }
567
639
  }
568
640
  async onEvent(e) {
569
- if (this.ended) return;
570
641
  const d = e.data ?? {};
642
+ if (e.type === "session.usage.recorded") {
643
+ await this.usageRecorded(d);
644
+ return;
645
+ }
646
+ if (this.ended) return;
571
647
  switch (e.type) {
648
+ case "session.compaction.started":
649
+ case "session.compaction.ended":
650
+ this.compacted = true;
651
+ return;
652
+ case "session.step.started":
653
+ case "session.model.selected": {
654
+ const model = d.model;
655
+ if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
656
+ providerID: model.providerID,
657
+ id: model.id
658
+ };
659
+ return;
660
+ }
572
661
  case "session.text.delta": {
573
662
  const delta = typeof d.delta === "string" ? d.delta : "";
574
663
  if (!delta) return;
@@ -722,42 +811,70 @@ ${delta}` : delta;
722
811
  });
723
812
  }
724
813
  }
725
- async stepEnded(d) {
726
- const usage = {
727
- inputTokens: Number(d.tokens?.input) || 0,
728
- outputTokens: Number(d.tokens?.output) || 0,
729
- ...Number(d.tokens?.cache?.read) ? {
730
- cacheReadTokens: Number(d.tokens.cache.read)
731
- } : {},
732
- ...Number(d.tokens?.cache?.write) ? {
733
- cacheWriteTokens: Number(d.tokens.cache.write)
734
- } : {}
735
- };
736
- const costUsd = typeof d.cost === "number" ? d.cost : void 0;
737
- this.usage.inputTokens += usage.inputTokens;
738
- this.usage.outputTokens += usage.outputTokens;
739
- if (costUsd !== void 0) this.costUsd = (this.costUsd ?? 0) + costUsd;
814
+ modelId() {
815
+ return this.model !== void 0 ? `${this.model.providerID}/${this.model.id}` : this.a.modelLabel;
816
+ }
817
+ /** One model call's usage and cost, into the ledger and the run's total. */
818
+ async spend(d, purpose) {
819
+ const usage = usageOf(d.tokens);
820
+ const reportedCostUsd = typeof d.cost === "number" && Number.isFinite(d.cost) && d.cost >= 0 ? d.cost : void 0;
821
+ const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
822
+ this.seen.cost += reportedCostUsd ?? 0;
823
+ this.seen.input += n(d.tokens?.input);
824
+ this.seen.output += n(d.tokens?.output);
825
+ this.seen.reasoning += n(d.tokens?.reasoning);
826
+ this.seen.read += n(d.tokens?.cache?.read);
827
+ this.seen.write += n(d.tokens?.cache?.write);
828
+ const fallback = reportedCostUsd !== void 0 ? {
829
+ costUsd: reportedCostUsd,
830
+ costSource: "provider"
831
+ } : {};
832
+ let cost = fallback;
833
+ if (this.a.priceStep !== void 0) {
834
+ cost = await this.a.priceStep({
835
+ model: this.model,
836
+ usage,
837
+ reportedCostUsd
838
+ }).catch(() => fallback);
839
+ }
840
+ this.spent = addUsage(this.spent, {
841
+ inputTokens: usage.inputTokens,
842
+ outputTokens: usage.outputTokens,
843
+ cacheReadTokens: usage.cacheReadTokens ?? 0,
844
+ cacheWriteTokens: usage.cacheWriteTokens ?? 0,
845
+ reasoningTokens: usage.reasoningTokens ?? 0,
846
+ costUsd: cost.costUsd ?? 0
847
+ });
848
+ if (cost.costUsd !== void 0) this.priced = true;
740
849
  await this.a.store.recordUsage({
741
850
  threadId: this.a.input.threadId,
742
851
  actorRef: this.a.input.actor.id,
743
- modelId: this.a.modelLabel,
744
- purpose: "chat",
852
+ modelId: this.modelId(),
853
+ purpose,
745
854
  usage,
746
- ...costUsd !== void 0 ? {
747
- costUsd,
748
- costSource: "provider"
749
- } : {}
855
+ ...cost
750
856
  });
857
+ return {
858
+ usage,
859
+ cost
860
+ };
861
+ }
862
+ /** `session.usage.recorded`: a call OpenCode made besides the steps (the title, a compaction). */
863
+ async usageRecorded(d) {
864
+ await this.spend(d, d.source === "title" ? "title" : "history_summary");
865
+ }
866
+ async stepEnded(d) {
867
+ const { usage, cost } = await this.spend(d, "chat");
751
868
  if (!this.stepOpen) return;
752
869
  this.stepOpen = false;
753
870
  await this.write({
754
871
  kind: "step-finish",
755
872
  usage,
756
- costUsd: costUsd ?? null,
873
+ costUsd: cost.costUsd ?? null,
757
874
  ...this.segment.reasoningMs > 0 ? {
758
875
  reasoningMs: this.segment.reasoningMs
759
876
  } : {},
760
- model: this.a.modelLabel
877
+ model: this.modelId()
761
878
  });
762
879
  }
763
880
  /**
@@ -776,12 +893,7 @@ ${delta}` : delta;
776
893
  content: s.text,
777
894
  runId: this.a.runId,
778
895
  ...final ? {
779
- usage: {
780
- ...this.usage,
781
- ...this.costUsd !== void 0 ? {
782
- costUsd: this.costUsd
783
- } : {}
784
- }
896
+ usage: this.messageUsage()
785
897
  } : {},
786
898
  ...this.a.input.agentName !== void 0 ? {
787
899
  agentName: this.a.input.agentName
@@ -809,6 +921,26 @@ ${delta}` : delta;
809
921
  ]);
810
922
  return message.id;
811
923
  }
924
+ /** The run's usage as its last message carries it: the whole run, cache tokens included. */
925
+ messageUsage() {
926
+ const total = this.totalUsage();
927
+ return {
928
+ inputTokens: total.inputTokens,
929
+ outputTokens: total.outputTokens,
930
+ ...total.cacheReadTokens > 0 ? {
931
+ cacheReadTokens: total.cacheReadTokens
932
+ } : {},
933
+ ...total.cacheWriteTokens > 0 ? {
934
+ cacheWriteTokens: total.cacheWriteTokens
935
+ } : {},
936
+ ...total.reasoningTokens > 0 ? {
937
+ reasoningTokens: total.reasoningTokens
938
+ } : {},
939
+ ...this.priced || (this.a.carried?.costUsd ?? 0) > 0 ? {
940
+ costUsd: total.costUsd
941
+ } : {}
942
+ };
943
+ }
812
944
  /** A request recorded by an earlier process (a turn resumed after a restart). */
813
945
  async alreadyRecorded(id) {
814
946
  return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
@@ -1160,9 +1292,39 @@ ${delta}` : delta;
1160
1292
  this.messageResults.set(messageId, results);
1161
1293
  await this.a.store.setMessageToolResults(messageId, results);
1162
1294
  }
1295
+ /** What the session spent since the baseline that no event of this turn reported. */
1296
+ async unreported() {
1297
+ const baseline = this.baseline;
1298
+ if (baseline === void 0) return;
1299
+ this.baseline = void 0;
1300
+ const info = await this.a.client.session.get?.({
1301
+ sessionID: this.a.sessionId
1302
+ }).catch(() => void 0);
1303
+ const after = info?.tokens;
1304
+ if (after === void 0) return;
1305
+ const before = baseline.tokens;
1306
+ const left = /* @__PURE__ */ __name((now, then, seen) => Math.max(0, (Number(now) || 0) - (Number(then) || 0) - seen), "left");
1307
+ const tokens = {
1308
+ input: left(after.input, before.input, this.seen.input),
1309
+ output: left(after.output, before.output, this.seen.output),
1310
+ reasoning: left(after.reasoning, before.reasoning, this.seen.reasoning),
1311
+ cache: {
1312
+ read: left(after.cache?.read, before.cache?.read, this.seen.read),
1313
+ write: left(after.cache?.write, before.cache?.write, this.seen.write)
1314
+ }
1315
+ };
1316
+ const total = tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write;
1317
+ if (total === 0) return;
1318
+ const cost = left(info?.cost, baseline.cost, this.seen.cost);
1319
+ await this.spend({
1320
+ tokens,
1321
+ cost: cost < 1e-9 ? 0 : cost
1322
+ }, this.compacted ? "history_summary" : "title");
1323
+ }
1163
1324
  async end(outcome) {
1164
1325
  if (this.ended) return;
1165
1326
  this.ended = true;
1327
+ await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
1166
1328
  if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
1167
1329
  await this.flush(true);
1168
1330
  }
@@ -1178,6 +1340,35 @@ ${delta}` : delta;
1178
1340
  });
1179
1341
  }
1180
1342
  };
1343
+ function modelOfLabel(label) {
1344
+ const slash = label?.indexOf("/") ?? -1;
1345
+ if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
1346
+ return {
1347
+ providerID: label.slice(0, slash),
1348
+ id: label.slice(slash + 1)
1349
+ };
1350
+ }
1351
+ __name(modelOfLabel, "modelOfLabel");
1352
+ function usageOf(tokens) {
1353
+ const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
1354
+ const read = n(tokens?.cache?.read);
1355
+ const write = n(tokens?.cache?.write);
1356
+ const reasoning = n(tokens?.reasoning);
1357
+ return {
1358
+ inputTokens: n(tokens?.input) + read + write,
1359
+ outputTokens: n(tokens?.output),
1360
+ ...read > 0 ? {
1361
+ cacheReadTokens: read
1362
+ } : {},
1363
+ ...write > 0 ? {
1364
+ cacheWriteTokens: write
1365
+ } : {},
1366
+ ...reasoning > 0 ? {
1367
+ reasoningTokens: reasoning
1368
+ } : {}
1369
+ };
1370
+ }
1371
+ __name(usageOf, "usageOf");
1181
1372
 
1182
1373
  // src/mcp.ts
1183
1374
  var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
@@ -1609,7 +1800,7 @@ var OpenCodeTurns = class {
1609
1800
  spent = /* @__PURE__ */ new Set();
1610
1801
  /** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
1611
1802
  toolsIssued = /* @__PURE__ */ new Map();
1612
- constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2) {
1803
+ constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
1613
1804
  this.host = host;
1614
1805
  this.sessions = sessions;
1615
1806
  this.settings = settings;
@@ -1621,6 +1812,30 @@ var OpenCodeTurns = class {
1621
1812
  this.memory = memory;
1622
1813
  this.queue = queue;
1623
1814
  this.toolsTokens = toolsTokens2;
1815
+ this.prices = new import_nestjs_agent_core4.ModelPriceResolver({
1816
+ pricingStore,
1817
+ catalog: agentOptions?.priceCatalog,
1818
+ log: {
1819
+ info: /* @__PURE__ */ __name((message) => this.logger.log(message), "info"),
1820
+ warn: /* @__PURE__ */ __name((message) => this.logger.warn(message), "warn")
1821
+ }
1822
+ });
1823
+ }
1824
+ prices;
1825
+ /**
1826
+ * What a model call OpenCode made cost, by the same rule the library's loop records its own steps
1827
+ * at: a reported figure wins, else an estimate off the model's price — see
1828
+ * {@link OpenCodeEngineSettings.cost}.
1829
+ */
1830
+ async priceStep(step) {
1831
+ const { model, usage, reportedCostUsd } = step;
1832
+ const tokens = usage.inputTokens + usage.outputTokens;
1833
+ const reported = reportedCostUsd !== void 0 && (reportedCostUsd > 0 || tokens === 0);
1834
+ if (reported && this.settings.cost !== "estimate") {
1835
+ return (0, import_nestjs_agent_core4.resolveUsageCost)(usage, reportedCostUsd, void 0);
1836
+ }
1837
+ const price = model !== void 0 && tokens > 0 ? await this.prices.priceFor(priceRef(model)) : void 0;
1838
+ return (0, import_nestjs_agent_core4.resolveUsageCost)(usage, price === void 0 ? reportedCostUsd : void 0, price);
1624
1839
  }
1625
1840
  /** The tools server's name in OpenCode. */
1626
1841
  get toolsServer() {
@@ -2210,8 +2425,8 @@ ${body}
2210
2425
  }
2211
2426
  // ---- prompt / observe / reply --------------------------------------------------------------
2212
2427
  /** Start listening to the session (so nothing it says is missed), then send the user message. */
2213
- async prompt(runId, input, handle) {
2214
- const live = await this.ensureLive(runId, input, handle, false);
2428
+ async prompt(runId, input, handle, spent) {
2429
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2215
2430
  try {
2216
2431
  const prompt = await this.host.promptFor?.({
2217
2432
  input,
@@ -2226,6 +2441,7 @@ ${body}
2226
2441
  })
2227
2442
  } : {}
2228
2443
  };
2444
+ live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
2229
2445
  await live.client.session.prompt({
2230
2446
  sessionID: handle.sessionId,
2231
2447
  text: prompt.text,
@@ -2241,9 +2457,12 @@ ${body}
2241
2457
  * Wait for the turn's next milestone. In a process that was not following the turn (a resumed
2242
2458
  * durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
2243
2459
  * `session.wait` is the safety net for a terminal event that was missed.
2460
+ *
2461
+ * `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
2462
+ * rebuilt here starts from it, so the answer it persists carries the whole run's usage.
2244
2463
  */
2245
- async observe(runId, input, handle) {
2246
- const live = await this.ensureLive(runId, input, handle, true);
2464
+ async observe(runId, input, handle, spent) {
2465
+ const live = await this.ensureLive(runId, input, handle, true, spent);
2247
2466
  const { turn, client } = live;
2248
2467
  if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
2249
2468
  let done = false;
@@ -2282,32 +2501,43 @@ ${body}
2282
2501
  * request is gone with it: a new session is opened, told what the person decided, and prompted
2283
2502
  * to go on.
2284
2503
  */
2285
- async reply(runId, input, handle, ask, reply) {
2504
+ async reply(runId, input, handle, ask, reply, spent) {
2286
2505
  const server = await this.host.server(input.actor);
2287
2506
  if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
2288
- return this.continueAfterRestart(runId, input, server, ask, reply);
2507
+ return this.continueAfterRestart(runId, input, server, ask, reply, spent);
2289
2508
  }
2290
- const live = await this.ensureLive(runId, input, handle, false);
2509
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2291
2510
  await live.turn.decide(ask, reply);
2292
2511
  return handle;
2293
2512
  }
2294
- async continueAfterRestart(runId, input, server, ask, reply) {
2513
+ async continueAfterRestart(runId, input, server, ask, reply, spent) {
2295
2514
  const approved = "approved" in reply && reply.approved === true;
2296
2515
  const note = ask.kind === "approval" ? approved ? `The user approved the action ${ask.action} you asked permission for. Continue the task.` : `The user did not approve the action ${ask.action}. Continue without it and explain.` : `The user answered your question: ${JSON.stringify("answers" in reply ? reply.answers : {})}. Continue.`;
2297
2516
  const thread = await this.store.getThread(input.threadId);
2298
2517
  const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
2299
2518
  await this.refreshInstructions(runId, input, server.client, handle.sessionId);
2300
2519
  this.drop(runId);
2301
- const live = await this.ensureLive(runId, input, handle, false);
2520
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2302
2521
  await live.turn.decide(ask, reply, {
2303
2522
  tellOpenCode: false
2304
2523
  });
2524
+ live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
2305
2525
  await live.client.session.prompt({
2306
2526
  sessionID: handle.sessionId,
2307
2527
  text: note
2308
2528
  });
2309
2529
  return handle;
2310
2530
  }
2531
+ /** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
2532
+ async sessionUsage(client, sessionId) {
2533
+ try {
2534
+ return await client.session.get?.({
2535
+ sessionID: sessionId
2536
+ });
2537
+ } catch {
2538
+ return void 0;
2539
+ }
2540
+ }
2311
2541
  /** Interrupt the thread's session (cancel). */
2312
2542
  async interrupt(input) {
2313
2543
  const known = await this.sessions.get(input.threadId);
@@ -2318,7 +2548,7 @@ ${body}
2318
2548
  sessionID: known.sessionId
2319
2549
  });
2320
2550
  }
2321
- async ensureLive(runId, input, handle, catchUp) {
2551
+ async ensureLive(runId, input, handle, catchUp, spent) {
2322
2552
  const current = this.live.get(runId);
2323
2553
  if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
2324
2554
  current?.stop();
@@ -2344,6 +2574,10 @@ ${body}
2344
2574
  } : {}
2345
2575
  }), "approvalFor"),
2346
2576
  modelLabel: input.model ?? "opencode",
2577
+ priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
2578
+ ...spent !== void 0 ? {
2579
+ carried: spent
2580
+ } : {},
2347
2581
  logger: this.logger,
2348
2582
  onGranted: /* @__PURE__ */ __name((action, id) => {
2349
2583
  const grants = this.grants.get(runId) ?? [];
@@ -2390,20 +2624,14 @@ ${body}
2390
2624
  }
2391
2625
  return milestone;
2392
2626
  }
2393
- /** What the run produced, as the host's settle hooks see it. */
2394
- async runResult(runId, input, outcome, durationMs) {
2627
+ /**
2628
+ * What the run produced, as the host's settle hooks see it. `usage` is what the run spent — the
2629
+ * runner's sum of its milestones' usage, which a resumed run reads back from its journal — else
2630
+ * what the turn this process follows saw.
2631
+ */
2632
+ async runResult(runId, input, outcome, durationMs, usage) {
2395
2633
  const thread = await this.store.getThread(input.threadId);
2396
2634
  const messages = (thread?.messages ?? []).filter((m) => m.role === "assistant" && m.runId === runId);
2397
- const usage = {
2398
- inputTokens: 0,
2399
- outputTokens: 0,
2400
- costUsd: 0
2401
- };
2402
- for (const m of messages) {
2403
- usage.inputTokens += m.usage?.inputTokens ?? 0;
2404
- usage.outputTokens += m.usage?.outputTokens ?? 0;
2405
- usage.costUsd += m.usage?.costUsd ?? 0;
2406
- }
2407
2635
  return {
2408
2636
  runId,
2409
2637
  input,
@@ -2448,15 +2676,31 @@ ${body}
2448
2676
  this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
2449
2677
  }
2450
2678
  }
2451
- /** End the run's stream and bookkeeping for how its execution ended. */
2452
- async settle(runId, input, outcome, durationMs) {
2679
+ /**
2680
+ * What the run spent: the runner's sum of its milestones, or what the turn this process follows
2681
+ * saw (which starts from that sum) when it saw more — a run that failed or was cancelled between
2682
+ * two milestones.
2683
+ */
2684
+ spentBy(runId, spent) {
2685
+ const seen = this.live.get(runId)?.turn.totalUsage();
2686
+ if (spent === void 0) return seen ?? emptyUsage();
2687
+ if (seen === void 0) return spent;
2688
+ const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
2689
+ return tokens(seen) > tokens(spent) ? seen : spent;
2690
+ }
2691
+ /**
2692
+ * End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
2693
+ * (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
2694
+ */
2695
+ async settle(runId, input, outcome, durationMs, spent) {
2453
2696
  if (outcome.status === "failed") {
2454
- await this.settleFailed(runId, input, outcome.error, durationMs);
2697
+ await this.settleFailed(runId, input, outcome.error, durationMs, spent);
2455
2698
  return;
2456
2699
  }
2700
+ const usage = this.spentBy(runId, spent);
2457
2701
  this.forget(runId);
2458
2702
  const writer = await this.sink.open(runId);
2459
- const result = await this.runResult(runId, input, outcome, durationMs);
2703
+ const result = await this.runResult(runId, input, outcome, durationMs, usage);
2460
2704
  await this.amend(writer, result);
2461
2705
  if (outcome.status === "interrupted") {
2462
2706
  this.logger.log(`agent run ${runId} cancelled`);
@@ -2484,13 +2728,14 @@ ${body}
2484
2728
  }
2485
2729
  await this.settled(result);
2486
2730
  }
2487
- async settleFailed(runId, input, error, durationMs = 0) {
2731
+ async settleFailed(runId, input, error, durationMs = 0, spent) {
2732
+ const usage = this.spentBy(runId, spent);
2488
2733
  this.forget(runId);
2489
2734
  const writer = await this.sink.open(runId);
2490
2735
  const result = await this.runResult(runId, input, {
2491
2736
  status: "failed",
2492
2737
  error
2493
- }, durationMs);
2738
+ }, durationMs, usage);
2494
2739
  const worded = (await this.amend(writer, result)).error;
2495
2740
  const message = worded ?? (0, import_nestjs_agent_core4.streamFailure)(new Error(error)).message;
2496
2741
  this.logger.error(`agent run ${runId} failed: ${error}`);
@@ -2560,6 +2805,10 @@ OpenCodeTurns = _ts_decorate2([
2560
2805
  _ts_param2(9, (0, import_common4.Inject)(import_nestjs_agent.AGENT_CHAT_QUEUE)),
2561
2806
  _ts_param2(10, (0, import_common4.Optional)()),
2562
2807
  _ts_param2(10, (0, import_common4.Inject)(OPENCODE_TOOLS_TOKENS)),
2808
+ _ts_param2(11, (0, import_common4.Optional)()),
2809
+ _ts_param2(11, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_PRICING_STORE)),
2810
+ _ts_param2(12, (0, import_common4.Optional)()),
2811
+ _ts_param2(12, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_OPTIONS)),
2563
2812
  _ts_metadata2("design:type", Function),
2564
2813
  _ts_metadata2("design:paramtypes", [
2565
2814
  typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
@@ -2572,6 +2821,8 @@ OpenCodeTurns = _ts_decorate2([
2572
2821
  typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
2573
2822
  typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
2574
2823
  typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
2824
+ Object,
2825
+ typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
2575
2826
  Object
2576
2827
  ])
2577
2828
  ], OpenCodeTurns);
@@ -2614,6 +2865,19 @@ function uiEmitter(scope, push) {
2614
2865
  };
2615
2866
  }
2616
2867
  __name(uiEmitter, "uiEmitter");
2868
+ function priceRef(model) {
2869
+ const aliases = [
2870
+ model.id
2871
+ ];
2872
+ const tail = model.id.slice(model.id.lastIndexOf("/") + 1);
2873
+ if (tail !== model.id && (0, import_nestjs_agent_core4.parseBedrockModelId)(tail) !== void 0) aliases.push(tail);
2874
+ return {
2875
+ modelId: `${model.providerID}/${model.id}`,
2876
+ provider: model.providerID,
2877
+ aliases
2878
+ };
2879
+ }
2880
+ __name(priceRef, "priceRef");
2617
2881
 
2618
2882
  // src/runner.ts
2619
2883
  function _ts_decorate3(decorators, target, key, desc) {
@@ -2715,18 +2979,20 @@ var OpenCodeAgentRunner = class {
2715
2979
  async run(runId, input) {
2716
2980
  const started = Date.now();
2717
2981
  this.inputs.set(runId, input);
2982
+ let spent = emptyUsage();
2718
2983
  try {
2719
2984
  let handle = await this.turns.begin(runId, input);
2720
2985
  if (this.cancelled.has(runId)) throw new import_nestjs_agent_core5.RunCancelledError();
2721
- await this.turns.prompt(runId, input, handle);
2986
+ await this.turns.prompt(runId, input, handle, spent);
2722
2987
  for (; ; ) {
2723
- const milestone = await this.turns.observe(runId, input, handle);
2988
+ const milestone = await this.turns.observe(runId, input, handle, spent);
2989
+ spent = addUsage(spent, milestone.usage);
2724
2990
  if (milestone.kind === "finished") {
2725
- await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
2991
+ await this.turns.settle(runId, input, milestone.outcome, Date.now() - started, spent);
2726
2992
  return;
2727
2993
  }
2728
2994
  const reply = await this.park(runId, milestone.ask.id, milestone.ask.kind === "approval" ? "approval" : "answers", milestone.timeoutMs);
2729
- handle = await this.turns.reply(runId, input, handle, milestone.ask, reply);
2995
+ handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
2730
2996
  }
2731
2997
  } catch (error) {
2732
2998
  if (error instanceof import_nestjs_agent_core5.RunCancelledError) {
@@ -2736,7 +3002,7 @@ var OpenCodeAgentRunner = class {
2736
3002
  }, Date.now() - started);
2737
3003
  return;
2738
3004
  }
2739
- await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"));
3005
+ await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
2740
3006
  } finally {
2741
3007
  this.inputs.delete(runId);
2742
3008
  }
@@ -2908,6 +3174,7 @@ var OpenCodeRunWorkflow = class {
2908
3174
  this.runs = runs;
2909
3175
  }
2910
3176
  async run(ctx, input) {
3177
+ let spent = emptyUsage();
2911
3178
  try {
2912
3179
  const begun = await ctx.localStep("begin", async () => ({
2913
3180
  handle: await this.turns.begin(ctx.runId, input),
@@ -2915,15 +3182,18 @@ var OpenCodeRunWorkflow = class {
2915
3182
  }));
2916
3183
  let handle = begun.handle;
2917
3184
  await ctx.localStep("prompt", async () => {
2918
- await this.turns.prompt(ctx.runId, input, handle);
3185
+ await this.turns.prompt(ctx.runId, input, handle, spent);
2919
3186
  return true;
2920
3187
  });
2921
3188
  for (let n = 0; ; n += 1) {
2922
- const milestone = await ctx.localStep(`observe:${n}`, () => this.turns.observe(ctx.runId, input, handle));
3189
+ const before = spent;
3190
+ const milestone = await ctx.localStep(`observe:${n}`, () => this.turns.observe(ctx.runId, input, handle, before));
3191
+ spent = addUsage(spent, milestone.usage);
3192
+ const total = spent;
2923
3193
  if (milestone.kind === "finished") {
2924
3194
  await ctx.localStep("finish", async () => {
2925
3195
  if (await this.cancelled(ctx.runId)) this.turns.drop(ctx.runId);
2926
- else await this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt);
3196
+ else await this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt, total);
2927
3197
  return true;
2928
3198
  });
2929
3199
  return {
@@ -2943,12 +3213,12 @@ var OpenCodeRunWorkflow = class {
2943
3213
  };
2944
3214
  }
2945
3215
  const ask = milestone.ask;
2946
- handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply));
3216
+ handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply, total));
2947
3217
  }
2948
3218
  } catch (error) {
2949
3219
  if ((0, import_nestjs_durable_core.isWorkflowControlFlowSignal)(error)) throw error;
2950
3220
  await ctx.localStep("fail", async () => {
2951
- await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"));
3221
+ await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"), 0, spent);
2952
3222
  return true;
2953
3223
  });
2954
3224
  return {