@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.
package/dist/index.cjs CHANGED
@@ -38,6 +38,8 @@ __export(index_exports, {
38
38
  OpenCodeToolsTokens: () => OpenCodeToolsTokens,
39
39
  OpenCodeTurn: () => OpenCodeTurn,
40
40
  OpenCodeTurns: () => OpenCodeTurns,
41
+ addUsage: () => addUsage,
42
+ emptyUsage: () => emptyUsage,
41
43
  keyValueOpenCodeSessionStore: () => keyValueOpenCodeSessionStore,
42
44
  openCode: () => openCode,
43
45
  openCodeControllers: () => openCodeControllers,
@@ -45,7 +47,8 @@ __export(index_exports, {
45
47
  sessionOf: () => sessionOf,
46
48
  toElicitation: () => toElicitation,
47
49
  toFormAnswer: () => toFormAnswer,
48
- toQuestion: () => toQuestion
50
+ toQuestion: () => toQuestion,
51
+ usageOf: () => usageOf
49
52
  });
50
53
  module.exports = __toCommonJS(index_exports);
51
54
 
@@ -287,6 +290,41 @@ function toFormAnswer(form, answers = {}) {
287
290
  __name(toFormAnswer, "toFormAnswer");
288
291
 
289
292
  // src/turn.ts
293
+ function emptyUsage() {
294
+ return {
295
+ inputTokens: 0,
296
+ outputTokens: 0,
297
+ cacheReadTokens: 0,
298
+ cacheWriteTokens: 0,
299
+ reasoningTokens: 0,
300
+ costUsd: 0
301
+ };
302
+ }
303
+ __name(emptyUsage, "emptyUsage");
304
+ function addUsage(a, b) {
305
+ const x = a ?? emptyUsage();
306
+ const y = b ?? emptyUsage();
307
+ return {
308
+ inputTokens: x.inputTokens + (y.inputTokens ?? 0),
309
+ outputTokens: x.outputTokens + (y.outputTokens ?? 0),
310
+ cacheReadTokens: (x.cacheReadTokens ?? 0) + (y.cacheReadTokens ?? 0),
311
+ cacheWriteTokens: (x.cacheWriteTokens ?? 0) + (y.cacheWriteTokens ?? 0),
312
+ reasoningTokens: (x.reasoningTokens ?? 0) + (y.reasoningTokens ?? 0),
313
+ costUsd: x.costUsd + (y.costUsd ?? 0)
314
+ };
315
+ }
316
+ __name(addUsage, "addUsage");
317
+ function subUsage(a, b) {
318
+ return {
319
+ inputTokens: a.inputTokens - b.inputTokens,
320
+ outputTokens: a.outputTokens - b.outputTokens,
321
+ cacheReadTokens: a.cacheReadTokens - b.cacheReadTokens,
322
+ cacheWriteTokens: a.cacheWriteTokens - b.cacheWriteTokens,
323
+ reasoningTokens: a.reasoningTokens - b.reasoningTokens,
324
+ costUsd: a.costUsd - b.costUsd
325
+ };
326
+ }
327
+ __name(subUsage, "subUsage");
290
328
  var ASK_TOOL_NAME = "ask";
291
329
  var FAILED_STATUSES = /* @__PURE__ */ new Set([
292
330
  "error",
@@ -352,11 +390,22 @@ var OpenCodeTurn = class {
352
390
  hidden = /* @__PURE__ */ new Set();
353
391
  milestones = [];
354
392
  waiting;
355
- usage = {
356
- inputTokens: 0,
357
- outputTokens: 0
393
+ /** What this turn object saw spent, and how much of it a milestone already reported. */
394
+ spent = emptyUsage();
395
+ reported = emptyUsage();
396
+ priced = false;
397
+ model;
398
+ /** What the session had spent when this turn prompted it; what OpenCode reported since. */
399
+ baseline;
400
+ seen = {
401
+ cost: 0,
402
+ input: 0,
403
+ output: 0,
404
+ reasoning: 0,
405
+ read: 0,
406
+ write: 0
358
407
  };
359
- costUsd;
408
+ compacted = false;
360
409
  stepOpen = false;
361
410
  sawText = false;
362
411
  separator = false;
@@ -366,6 +415,24 @@ var OpenCodeTurn = class {
366
415
  stepError;
367
416
  constructor(a) {
368
417
  this.a = a;
418
+ this.model = modelOfLabel(a.input.model);
419
+ }
420
+ /**
421
+ * What the session had spent before this turn prompted it (`session.get`). When the execution
422
+ * ends, what the session spent since that this turn's events did not report — the title OpenCode
423
+ * generates, a compaction: OpenCode records those calls on the session without streaming them — is
424
+ * recorded as a usage row of its own.
425
+ */
426
+ setBaseline(info) {
427
+ if (info?.tokens === void 0) return;
428
+ this.baseline = {
429
+ cost: Number(info.cost) || 0,
430
+ tokens: info.tokens
431
+ };
432
+ }
433
+ /** What the run spent so far: carried from before this turn object, plus what it saw. */
434
+ totalUsage() {
435
+ return addUsage(this.a.carried, this.spent);
369
436
  }
370
437
  get sessionId() {
371
438
  return this.a.sessionId;
@@ -448,7 +515,15 @@ var OpenCodeTurn = class {
448
515
  this.chain = this.chain.then(() => ask.kind === "approval" ? this.decided(ask, reply, tell) : this.answered(ask, reply, tell)).then(resolve, reject);
449
516
  });
450
517
  }
451
- reach(milestone) {
518
+ reach(reached) {
519
+ const usage = subUsage(this.spent, this.reported);
520
+ this.reported = {
521
+ ...this.spent
522
+ };
523
+ const milestone = {
524
+ ...reached,
525
+ usage
526
+ };
452
527
  const waiting = this.waiting;
453
528
  if (waiting !== void 0) {
454
529
  this.waiting = void 0;
@@ -604,9 +679,26 @@ var OpenCodeTurn = class {
604
679
  }
605
680
  }
606
681
  async onEvent(e) {
607
- if (this.ended) return;
608
682
  const d = e.data ?? {};
683
+ if (e.type === "session.usage.recorded") {
684
+ await this.usageRecorded(d);
685
+ return;
686
+ }
687
+ if (this.ended) return;
609
688
  switch (e.type) {
689
+ case "session.compaction.started":
690
+ case "session.compaction.ended":
691
+ this.compacted = true;
692
+ return;
693
+ case "session.step.started":
694
+ case "session.model.selected": {
695
+ const model = d.model;
696
+ if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
697
+ providerID: model.providerID,
698
+ id: model.id
699
+ };
700
+ return;
701
+ }
610
702
  case "session.text.delta": {
611
703
  const delta = typeof d.delta === "string" ? d.delta : "";
612
704
  if (!delta) return;
@@ -760,42 +852,70 @@ ${delta}` : delta;
760
852
  });
761
853
  }
762
854
  }
763
- async stepEnded(d) {
764
- const usage = {
765
- inputTokens: Number(d.tokens?.input) || 0,
766
- outputTokens: Number(d.tokens?.output) || 0,
767
- ...Number(d.tokens?.cache?.read) ? {
768
- cacheReadTokens: Number(d.tokens.cache.read)
769
- } : {},
770
- ...Number(d.tokens?.cache?.write) ? {
771
- cacheWriteTokens: Number(d.tokens.cache.write)
772
- } : {}
773
- };
774
- const costUsd = typeof d.cost === "number" ? d.cost : void 0;
775
- this.usage.inputTokens += usage.inputTokens;
776
- this.usage.outputTokens += usage.outputTokens;
777
- if (costUsd !== void 0) this.costUsd = (this.costUsd ?? 0) + costUsd;
855
+ modelId() {
856
+ return this.model !== void 0 ? `${this.model.providerID}/${this.model.id}` : this.a.modelLabel;
857
+ }
858
+ /** One model call's usage and cost, into the ledger and the run's total. */
859
+ async spend(d, purpose) {
860
+ const usage = usageOf(d.tokens);
861
+ const reportedCostUsd = typeof d.cost === "number" && Number.isFinite(d.cost) && d.cost >= 0 ? d.cost : void 0;
862
+ const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
863
+ this.seen.cost += reportedCostUsd ?? 0;
864
+ this.seen.input += n(d.tokens?.input);
865
+ this.seen.output += n(d.tokens?.output);
866
+ this.seen.reasoning += n(d.tokens?.reasoning);
867
+ this.seen.read += n(d.tokens?.cache?.read);
868
+ this.seen.write += n(d.tokens?.cache?.write);
869
+ const fallback = reportedCostUsd !== void 0 ? {
870
+ costUsd: reportedCostUsd,
871
+ costSource: "provider"
872
+ } : {};
873
+ let cost = fallback;
874
+ if (this.a.priceStep !== void 0) {
875
+ cost = await this.a.priceStep({
876
+ model: this.model,
877
+ usage,
878
+ reportedCostUsd
879
+ }).catch(() => fallback);
880
+ }
881
+ this.spent = addUsage(this.spent, {
882
+ inputTokens: usage.inputTokens,
883
+ outputTokens: usage.outputTokens,
884
+ cacheReadTokens: usage.cacheReadTokens ?? 0,
885
+ cacheWriteTokens: usage.cacheWriteTokens ?? 0,
886
+ reasoningTokens: usage.reasoningTokens ?? 0,
887
+ costUsd: cost.costUsd ?? 0
888
+ });
889
+ if (cost.costUsd !== void 0) this.priced = true;
778
890
  await this.a.store.recordUsage({
779
891
  threadId: this.a.input.threadId,
780
892
  actorRef: this.a.input.actor.id,
781
- modelId: this.a.modelLabel,
782
- purpose: "chat",
893
+ modelId: this.modelId(),
894
+ purpose,
783
895
  usage,
784
- ...costUsd !== void 0 ? {
785
- costUsd,
786
- costSource: "provider"
787
- } : {}
896
+ ...cost
788
897
  });
898
+ return {
899
+ usage,
900
+ cost
901
+ };
902
+ }
903
+ /** `session.usage.recorded`: a call OpenCode made besides the steps (the title, a compaction). */
904
+ async usageRecorded(d) {
905
+ await this.spend(d, d.source === "title" ? "title" : "history_summary");
906
+ }
907
+ async stepEnded(d) {
908
+ const { usage, cost } = await this.spend(d, "chat");
789
909
  if (!this.stepOpen) return;
790
910
  this.stepOpen = false;
791
911
  await this.write({
792
912
  kind: "step-finish",
793
913
  usage,
794
- costUsd: costUsd ?? null,
914
+ costUsd: cost.costUsd ?? null,
795
915
  ...this.segment.reasoningMs > 0 ? {
796
916
  reasoningMs: this.segment.reasoningMs
797
917
  } : {},
798
- model: this.a.modelLabel
918
+ model: this.modelId()
799
919
  });
800
920
  }
801
921
  /**
@@ -814,12 +934,7 @@ ${delta}` : delta;
814
934
  content: s.text,
815
935
  runId: this.a.runId,
816
936
  ...final ? {
817
- usage: {
818
- ...this.usage,
819
- ...this.costUsd !== void 0 ? {
820
- costUsd: this.costUsd
821
- } : {}
822
- }
937
+ usage: this.messageUsage()
823
938
  } : {},
824
939
  ...this.a.input.agentName !== void 0 ? {
825
940
  agentName: this.a.input.agentName
@@ -847,6 +962,26 @@ ${delta}` : delta;
847
962
  ]);
848
963
  return message.id;
849
964
  }
965
+ /** The run's usage as its last message carries it: the whole run, cache tokens included. */
966
+ messageUsage() {
967
+ const total = this.totalUsage();
968
+ return {
969
+ inputTokens: total.inputTokens,
970
+ outputTokens: total.outputTokens,
971
+ ...total.cacheReadTokens > 0 ? {
972
+ cacheReadTokens: total.cacheReadTokens
973
+ } : {},
974
+ ...total.cacheWriteTokens > 0 ? {
975
+ cacheWriteTokens: total.cacheWriteTokens
976
+ } : {},
977
+ ...total.reasoningTokens > 0 ? {
978
+ reasoningTokens: total.reasoningTokens
979
+ } : {},
980
+ ...this.priced || (this.a.carried?.costUsd ?? 0) > 0 ? {
981
+ costUsd: total.costUsd
982
+ } : {}
983
+ };
984
+ }
850
985
  /** A request recorded by an earlier process (a turn resumed after a restart). */
851
986
  async alreadyRecorded(id) {
852
987
  return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
@@ -1198,9 +1333,39 @@ ${delta}` : delta;
1198
1333
  this.messageResults.set(messageId, results);
1199
1334
  await this.a.store.setMessageToolResults(messageId, results);
1200
1335
  }
1336
+ /** What the session spent since the baseline that no event of this turn reported. */
1337
+ async unreported() {
1338
+ const baseline = this.baseline;
1339
+ if (baseline === void 0) return;
1340
+ this.baseline = void 0;
1341
+ const info = await this.a.client.session.get?.({
1342
+ sessionID: this.a.sessionId
1343
+ }).catch(() => void 0);
1344
+ const after = info?.tokens;
1345
+ if (after === void 0) return;
1346
+ const before = baseline.tokens;
1347
+ const left = /* @__PURE__ */ __name((now, then, seen) => Math.max(0, (Number(now) || 0) - (Number(then) || 0) - seen), "left");
1348
+ const tokens = {
1349
+ input: left(after.input, before.input, this.seen.input),
1350
+ output: left(after.output, before.output, this.seen.output),
1351
+ reasoning: left(after.reasoning, before.reasoning, this.seen.reasoning),
1352
+ cache: {
1353
+ read: left(after.cache?.read, before.cache?.read, this.seen.read),
1354
+ write: left(after.cache?.write, before.cache?.write, this.seen.write)
1355
+ }
1356
+ };
1357
+ const total = tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write;
1358
+ if (total === 0) return;
1359
+ const cost = left(info?.cost, baseline.cost, this.seen.cost);
1360
+ await this.spend({
1361
+ tokens,
1362
+ cost: cost < 1e-9 ? 0 : cost
1363
+ }, this.compacted ? "history_summary" : "title");
1364
+ }
1201
1365
  async end(outcome) {
1202
1366
  if (this.ended) return;
1203
1367
  this.ended = true;
1368
+ await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
1204
1369
  if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
1205
1370
  await this.flush(true);
1206
1371
  }
@@ -1216,6 +1381,35 @@ ${delta}` : delta;
1216
1381
  });
1217
1382
  }
1218
1383
  };
1384
+ function modelOfLabel(label) {
1385
+ const slash = label?.indexOf("/") ?? -1;
1386
+ if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
1387
+ return {
1388
+ providerID: label.slice(0, slash),
1389
+ id: label.slice(slash + 1)
1390
+ };
1391
+ }
1392
+ __name(modelOfLabel, "modelOfLabel");
1393
+ function usageOf(tokens) {
1394
+ const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
1395
+ const read = n(tokens?.cache?.read);
1396
+ const write = n(tokens?.cache?.write);
1397
+ const reasoning = n(tokens?.reasoning);
1398
+ return {
1399
+ inputTokens: n(tokens?.input) + read + write,
1400
+ outputTokens: n(tokens?.output),
1401
+ ...read > 0 ? {
1402
+ cacheReadTokens: read
1403
+ } : {},
1404
+ ...write > 0 ? {
1405
+ cacheWriteTokens: write
1406
+ } : {},
1407
+ ...reasoning > 0 ? {
1408
+ reasoningTokens: reasoning
1409
+ } : {}
1410
+ };
1411
+ }
1412
+ __name(usageOf, "usageOf");
1219
1413
 
1220
1414
  // src/mcp.ts
1221
1415
  var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
@@ -1647,7 +1841,7 @@ var OpenCodeTurns = class {
1647
1841
  spent = /* @__PURE__ */ new Set();
1648
1842
  /** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
1649
1843
  toolsIssued = /* @__PURE__ */ new Map();
1650
- constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2) {
1844
+ constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
1651
1845
  this.host = host;
1652
1846
  this.sessions = sessions;
1653
1847
  this.settings = settings;
@@ -1659,6 +1853,30 @@ var OpenCodeTurns = class {
1659
1853
  this.memory = memory;
1660
1854
  this.queue = queue;
1661
1855
  this.toolsTokens = toolsTokens2;
1856
+ this.prices = new import_nestjs_agent_core4.ModelPriceResolver({
1857
+ pricingStore,
1858
+ catalog: agentOptions?.priceCatalog,
1859
+ log: {
1860
+ info: /* @__PURE__ */ __name((message) => this.logger.log(message), "info"),
1861
+ warn: /* @__PURE__ */ __name((message) => this.logger.warn(message), "warn")
1862
+ }
1863
+ });
1864
+ }
1865
+ prices;
1866
+ /**
1867
+ * What a model call OpenCode made cost, by the same rule the library's loop records its own steps
1868
+ * at: a reported figure wins, else an estimate off the model's price — see
1869
+ * {@link OpenCodeEngineSettings.cost}.
1870
+ */
1871
+ async priceStep(step) {
1872
+ const { model, usage, reportedCostUsd } = step;
1873
+ const tokens = usage.inputTokens + usage.outputTokens;
1874
+ const reported = reportedCostUsd !== void 0 && (reportedCostUsd > 0 || tokens === 0);
1875
+ if (reported && this.settings.cost !== "estimate") {
1876
+ return (0, import_nestjs_agent_core4.resolveUsageCost)(usage, reportedCostUsd, void 0);
1877
+ }
1878
+ const price = model !== void 0 && tokens > 0 ? await this.prices.priceFor(priceRef(model)) : void 0;
1879
+ return (0, import_nestjs_agent_core4.resolveUsageCost)(usage, price === void 0 ? reportedCostUsd : void 0, price);
1662
1880
  }
1663
1881
  /** The tools server's name in OpenCode. */
1664
1882
  get toolsServer() {
@@ -2248,8 +2466,8 @@ ${body}
2248
2466
  }
2249
2467
  // ---- prompt / observe / reply --------------------------------------------------------------
2250
2468
  /** Start listening to the session (so nothing it says is missed), then send the user message. */
2251
- async prompt(runId, input, handle) {
2252
- const live = await this.ensureLive(runId, input, handle, false);
2469
+ async prompt(runId, input, handle, spent) {
2470
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2253
2471
  try {
2254
2472
  const prompt = await this.host.promptFor?.({
2255
2473
  input,
@@ -2264,6 +2482,7 @@ ${body}
2264
2482
  })
2265
2483
  } : {}
2266
2484
  };
2485
+ live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
2267
2486
  await live.client.session.prompt({
2268
2487
  sessionID: handle.sessionId,
2269
2488
  text: prompt.text,
@@ -2279,9 +2498,12 @@ ${body}
2279
2498
  * Wait for the turn's next milestone. In a process that was not following the turn (a resumed
2280
2499
  * durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
2281
2500
  * `session.wait` is the safety net for a terminal event that was missed.
2501
+ *
2502
+ * `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
2503
+ * rebuilt here starts from it, so the answer it persists carries the whole run's usage.
2282
2504
  */
2283
- async observe(runId, input, handle) {
2284
- const live = await this.ensureLive(runId, input, handle, true);
2505
+ async observe(runId, input, handle, spent) {
2506
+ const live = await this.ensureLive(runId, input, handle, true, spent);
2285
2507
  const { turn, client } = live;
2286
2508
  if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
2287
2509
  let done = false;
@@ -2320,32 +2542,43 @@ ${body}
2320
2542
  * request is gone with it: a new session is opened, told what the person decided, and prompted
2321
2543
  * to go on.
2322
2544
  */
2323
- async reply(runId, input, handle, ask, reply) {
2545
+ async reply(runId, input, handle, ask, reply, spent) {
2324
2546
  const server = await this.host.server(input.actor);
2325
2547
  if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
2326
- return this.continueAfterRestart(runId, input, server, ask, reply);
2548
+ return this.continueAfterRestart(runId, input, server, ask, reply, spent);
2327
2549
  }
2328
- const live = await this.ensureLive(runId, input, handle, false);
2550
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2329
2551
  await live.turn.decide(ask, reply);
2330
2552
  return handle;
2331
2553
  }
2332
- async continueAfterRestart(runId, input, server, ask, reply) {
2554
+ async continueAfterRestart(runId, input, server, ask, reply, spent) {
2333
2555
  const approved = "approved" in reply && reply.approved === true;
2334
2556
  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.`;
2335
2557
  const thread = await this.store.getThread(input.threadId);
2336
2558
  const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
2337
2559
  await this.refreshInstructions(runId, input, server.client, handle.sessionId);
2338
2560
  this.drop(runId);
2339
- const live = await this.ensureLive(runId, input, handle, false);
2561
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2340
2562
  await live.turn.decide(ask, reply, {
2341
2563
  tellOpenCode: false
2342
2564
  });
2565
+ live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
2343
2566
  await live.client.session.prompt({
2344
2567
  sessionID: handle.sessionId,
2345
2568
  text: note
2346
2569
  });
2347
2570
  return handle;
2348
2571
  }
2572
+ /** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
2573
+ async sessionUsage(client, sessionId) {
2574
+ try {
2575
+ return await client.session.get?.({
2576
+ sessionID: sessionId
2577
+ });
2578
+ } catch {
2579
+ return void 0;
2580
+ }
2581
+ }
2349
2582
  /** Interrupt the thread's session (cancel). */
2350
2583
  async interrupt(input) {
2351
2584
  const known = await this.sessions.get(input.threadId);
@@ -2356,7 +2589,7 @@ ${body}
2356
2589
  sessionID: known.sessionId
2357
2590
  });
2358
2591
  }
2359
- async ensureLive(runId, input, handle, catchUp) {
2592
+ async ensureLive(runId, input, handle, catchUp, spent) {
2360
2593
  const current = this.live.get(runId);
2361
2594
  if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
2362
2595
  current?.stop();
@@ -2382,6 +2615,10 @@ ${body}
2382
2615
  } : {}
2383
2616
  }), "approvalFor"),
2384
2617
  modelLabel: input.model ?? "opencode",
2618
+ priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
2619
+ ...spent !== void 0 ? {
2620
+ carried: spent
2621
+ } : {},
2385
2622
  logger: this.logger,
2386
2623
  onGranted: /* @__PURE__ */ __name((action, id) => {
2387
2624
  const grants = this.grants.get(runId) ?? [];
@@ -2428,20 +2665,14 @@ ${body}
2428
2665
  }
2429
2666
  return milestone;
2430
2667
  }
2431
- /** What the run produced, as the host's settle hooks see it. */
2432
- async runResult(runId, input, outcome, durationMs) {
2668
+ /**
2669
+ * What the run produced, as the host's settle hooks see it. `usage` is what the run spent — the
2670
+ * runner's sum of its milestones' usage, which a resumed run reads back from its journal — else
2671
+ * what the turn this process follows saw.
2672
+ */
2673
+ async runResult(runId, input, outcome, durationMs, usage) {
2433
2674
  const thread = await this.store.getThread(input.threadId);
2434
2675
  const messages = (thread?.messages ?? []).filter((m) => m.role === "assistant" && m.runId === runId);
2435
- const usage = {
2436
- inputTokens: 0,
2437
- outputTokens: 0,
2438
- costUsd: 0
2439
- };
2440
- for (const m of messages) {
2441
- usage.inputTokens += m.usage?.inputTokens ?? 0;
2442
- usage.outputTokens += m.usage?.outputTokens ?? 0;
2443
- usage.costUsd += m.usage?.costUsd ?? 0;
2444
- }
2445
2676
  return {
2446
2677
  runId,
2447
2678
  input,
@@ -2486,15 +2717,31 @@ ${body}
2486
2717
  this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
2487
2718
  }
2488
2719
  }
2489
- /** End the run's stream and bookkeeping for how its execution ended. */
2490
- async settle(runId, input, outcome, durationMs) {
2720
+ /**
2721
+ * What the run spent: the runner's sum of its milestones, or what the turn this process follows
2722
+ * saw (which starts from that sum) when it saw more — a run that failed or was cancelled between
2723
+ * two milestones.
2724
+ */
2725
+ spentBy(runId, spent) {
2726
+ const seen = this.live.get(runId)?.turn.totalUsage();
2727
+ if (spent === void 0) return seen ?? emptyUsage();
2728
+ if (seen === void 0) return spent;
2729
+ const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
2730
+ return tokens(seen) > tokens(spent) ? seen : spent;
2731
+ }
2732
+ /**
2733
+ * End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
2734
+ * (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
2735
+ */
2736
+ async settle(runId, input, outcome, durationMs, spent) {
2491
2737
  if (outcome.status === "failed") {
2492
- await this.settleFailed(runId, input, outcome.error, durationMs);
2738
+ await this.settleFailed(runId, input, outcome.error, durationMs, spent);
2493
2739
  return;
2494
2740
  }
2741
+ const usage = this.spentBy(runId, spent);
2495
2742
  this.forget(runId);
2496
2743
  const writer = await this.sink.open(runId);
2497
- const result = await this.runResult(runId, input, outcome, durationMs);
2744
+ const result = await this.runResult(runId, input, outcome, durationMs, usage);
2498
2745
  await this.amend(writer, result);
2499
2746
  if (outcome.status === "interrupted") {
2500
2747
  this.logger.log(`agent run ${runId} cancelled`);
@@ -2522,13 +2769,14 @@ ${body}
2522
2769
  }
2523
2770
  await this.settled(result);
2524
2771
  }
2525
- async settleFailed(runId, input, error, durationMs = 0) {
2772
+ async settleFailed(runId, input, error, durationMs = 0, spent) {
2773
+ const usage = this.spentBy(runId, spent);
2526
2774
  this.forget(runId);
2527
2775
  const writer = await this.sink.open(runId);
2528
2776
  const result = await this.runResult(runId, input, {
2529
2777
  status: "failed",
2530
2778
  error
2531
- }, durationMs);
2779
+ }, durationMs, usage);
2532
2780
  const worded = (await this.amend(writer, result)).error;
2533
2781
  const message = worded ?? (0, import_nestjs_agent_core4.streamFailure)(new Error(error)).message;
2534
2782
  this.logger.error(`agent run ${runId} failed: ${error}`);
@@ -2598,6 +2846,10 @@ OpenCodeTurns = _ts_decorate2([
2598
2846
  _ts_param2(9, (0, import_common4.Inject)(import_nestjs_agent.AGENT_CHAT_QUEUE)),
2599
2847
  _ts_param2(10, (0, import_common4.Optional)()),
2600
2848
  _ts_param2(10, (0, import_common4.Inject)(OPENCODE_TOOLS_TOKENS)),
2849
+ _ts_param2(11, (0, import_common4.Optional)()),
2850
+ _ts_param2(11, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_PRICING_STORE)),
2851
+ _ts_param2(12, (0, import_common4.Optional)()),
2852
+ _ts_param2(12, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_OPTIONS)),
2601
2853
  _ts_metadata2("design:type", Function),
2602
2854
  _ts_metadata2("design:paramtypes", [
2603
2855
  typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
@@ -2610,6 +2862,8 @@ OpenCodeTurns = _ts_decorate2([
2610
2862
  typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
2611
2863
  typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
2612
2864
  typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
2865
+ Object,
2866
+ typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
2613
2867
  Object
2614
2868
  ])
2615
2869
  ], OpenCodeTurns);
@@ -2652,6 +2906,19 @@ function uiEmitter(scope, push) {
2652
2906
  };
2653
2907
  }
2654
2908
  __name(uiEmitter, "uiEmitter");
2909
+ function priceRef(model) {
2910
+ const aliases = [
2911
+ model.id
2912
+ ];
2913
+ const tail = model.id.slice(model.id.lastIndexOf("/") + 1);
2914
+ if (tail !== model.id && (0, import_nestjs_agent_core4.parseBedrockModelId)(tail) !== void 0) aliases.push(tail);
2915
+ return {
2916
+ modelId: `${model.providerID}/${model.id}`,
2917
+ provider: model.providerID,
2918
+ aliases
2919
+ };
2920
+ }
2921
+ __name(priceRef, "priceRef");
2655
2922
 
2656
2923
  // src/runner.ts
2657
2924
  function _ts_decorate3(decorators, target, key, desc) {
@@ -2753,18 +3020,20 @@ var OpenCodeAgentRunner = class {
2753
3020
  async run(runId, input) {
2754
3021
  const started = Date.now();
2755
3022
  this.inputs.set(runId, input);
3023
+ let spent = emptyUsage();
2756
3024
  try {
2757
3025
  let handle = await this.turns.begin(runId, input);
2758
3026
  if (this.cancelled.has(runId)) throw new import_nestjs_agent_core5.RunCancelledError();
2759
- await this.turns.prompt(runId, input, handle);
3027
+ await this.turns.prompt(runId, input, handle, spent);
2760
3028
  for (; ; ) {
2761
- const milestone = await this.turns.observe(runId, input, handle);
3029
+ const milestone = await this.turns.observe(runId, input, handle, spent);
3030
+ spent = addUsage(spent, milestone.usage);
2762
3031
  if (milestone.kind === "finished") {
2763
- await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
3032
+ await this.turns.settle(runId, input, milestone.outcome, Date.now() - started, spent);
2764
3033
  return;
2765
3034
  }
2766
3035
  const reply = await this.park(runId, milestone.ask.id, milestone.ask.kind === "approval" ? "approval" : "answers", milestone.timeoutMs);
2767
- handle = await this.turns.reply(runId, input, handle, milestone.ask, reply);
3036
+ handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
2768
3037
  }
2769
3038
  } catch (error) {
2770
3039
  if (error instanceof import_nestjs_agent_core5.RunCancelledError) {
@@ -2774,7 +3043,7 @@ var OpenCodeAgentRunner = class {
2774
3043
  }, Date.now() - started);
2775
3044
  return;
2776
3045
  }
2777
- await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"));
3046
+ await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
2778
3047
  } finally {
2779
3048
  this.inputs.delete(runId);
2780
3049
  }
@@ -2927,6 +3196,8 @@ __name(openCodeProviders, "openCodeProviders");
2927
3196
  OpenCodeToolsTokens,
2928
3197
  OpenCodeTurn,
2929
3198
  OpenCodeTurns,
3199
+ addUsage,
3200
+ emptyUsage,
2930
3201
  keyValueOpenCodeSessionStore,
2931
3202
  openCode,
2932
3203
  openCodeControllers,
@@ -2934,6 +3205,7 @@ __name(openCodeProviders, "openCodeProviders");
2934
3205
  sessionOf,
2935
3206
  toElicitation,
2936
3207
  toFormAnswer,
2937
- toQuestion
3208
+ toQuestion,
3209
+ usageOf
2938
3210
  });
2939
3211
  //# sourceMappingURL=index.cjs.map