@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.js CHANGED
@@ -239,6 +239,41 @@ function toFormAnswer(form, answers = {}) {
239
239
  __name(toFormAnswer, "toFormAnswer");
240
240
 
241
241
  // src/turn.ts
242
+ function emptyUsage() {
243
+ return {
244
+ inputTokens: 0,
245
+ outputTokens: 0,
246
+ cacheReadTokens: 0,
247
+ cacheWriteTokens: 0,
248
+ reasoningTokens: 0,
249
+ costUsd: 0
250
+ };
251
+ }
252
+ __name(emptyUsage, "emptyUsage");
253
+ function addUsage(a, b) {
254
+ const x = a ?? emptyUsage();
255
+ const y = b ?? emptyUsage();
256
+ return {
257
+ inputTokens: x.inputTokens + (y.inputTokens ?? 0),
258
+ outputTokens: x.outputTokens + (y.outputTokens ?? 0),
259
+ cacheReadTokens: (x.cacheReadTokens ?? 0) + (y.cacheReadTokens ?? 0),
260
+ cacheWriteTokens: (x.cacheWriteTokens ?? 0) + (y.cacheWriteTokens ?? 0),
261
+ reasoningTokens: (x.reasoningTokens ?? 0) + (y.reasoningTokens ?? 0),
262
+ costUsd: x.costUsd + (y.costUsd ?? 0)
263
+ };
264
+ }
265
+ __name(addUsage, "addUsage");
266
+ function subUsage(a, b) {
267
+ return {
268
+ inputTokens: a.inputTokens - b.inputTokens,
269
+ outputTokens: a.outputTokens - b.outputTokens,
270
+ cacheReadTokens: a.cacheReadTokens - b.cacheReadTokens,
271
+ cacheWriteTokens: a.cacheWriteTokens - b.cacheWriteTokens,
272
+ reasoningTokens: a.reasoningTokens - b.reasoningTokens,
273
+ costUsd: a.costUsd - b.costUsd
274
+ };
275
+ }
276
+ __name(subUsage, "subUsage");
242
277
  var ASK_TOOL_NAME = "ask";
243
278
  var FAILED_STATUSES = /* @__PURE__ */ new Set([
244
279
  "error",
@@ -304,11 +339,22 @@ var OpenCodeTurn = class {
304
339
  hidden = /* @__PURE__ */ new Set();
305
340
  milestones = [];
306
341
  waiting;
307
- usage = {
308
- inputTokens: 0,
309
- outputTokens: 0
342
+ /** What this turn object saw spent, and how much of it a milestone already reported. */
343
+ spent = emptyUsage();
344
+ reported = emptyUsage();
345
+ priced = false;
346
+ model;
347
+ /** What the session had spent when this turn prompted it; what OpenCode reported since. */
348
+ baseline;
349
+ seen = {
350
+ cost: 0,
351
+ input: 0,
352
+ output: 0,
353
+ reasoning: 0,
354
+ read: 0,
355
+ write: 0
310
356
  };
311
- costUsd;
357
+ compacted = false;
312
358
  stepOpen = false;
313
359
  sawText = false;
314
360
  separator = false;
@@ -318,6 +364,24 @@ var OpenCodeTurn = class {
318
364
  stepError;
319
365
  constructor(a) {
320
366
  this.a = a;
367
+ this.model = modelOfLabel(a.input.model);
368
+ }
369
+ /**
370
+ * What the session had spent before this turn prompted it (`session.get`). When the execution
371
+ * ends, what the session spent since that this turn's events did not report — the title OpenCode
372
+ * generates, a compaction: OpenCode records those calls on the session without streaming them — is
373
+ * recorded as a usage row of its own.
374
+ */
375
+ setBaseline(info) {
376
+ if (info?.tokens === void 0) return;
377
+ this.baseline = {
378
+ cost: Number(info.cost) || 0,
379
+ tokens: info.tokens
380
+ };
381
+ }
382
+ /** What the run spent so far: carried from before this turn object, plus what it saw. */
383
+ totalUsage() {
384
+ return addUsage(this.a.carried, this.spent);
321
385
  }
322
386
  get sessionId() {
323
387
  return this.a.sessionId;
@@ -400,7 +464,15 @@ var OpenCodeTurn = class {
400
464
  this.chain = this.chain.then(() => ask.kind === "approval" ? this.decided(ask, reply, tell) : this.answered(ask, reply, tell)).then(resolve, reject);
401
465
  });
402
466
  }
403
- reach(milestone) {
467
+ reach(reached) {
468
+ const usage = subUsage(this.spent, this.reported);
469
+ this.reported = {
470
+ ...this.spent
471
+ };
472
+ const milestone = {
473
+ ...reached,
474
+ usage
475
+ };
404
476
  const waiting = this.waiting;
405
477
  if (waiting !== void 0) {
406
478
  this.waiting = void 0;
@@ -556,9 +628,26 @@ var OpenCodeTurn = class {
556
628
  }
557
629
  }
558
630
  async onEvent(e) {
559
- if (this.ended) return;
560
631
  const d = e.data ?? {};
632
+ if (e.type === "session.usage.recorded") {
633
+ await this.usageRecorded(d);
634
+ return;
635
+ }
636
+ if (this.ended) return;
561
637
  switch (e.type) {
638
+ case "session.compaction.started":
639
+ case "session.compaction.ended":
640
+ this.compacted = true;
641
+ return;
642
+ case "session.step.started":
643
+ case "session.model.selected": {
644
+ const model = d.model;
645
+ if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
646
+ providerID: model.providerID,
647
+ id: model.id
648
+ };
649
+ return;
650
+ }
562
651
  case "session.text.delta": {
563
652
  const delta = typeof d.delta === "string" ? d.delta : "";
564
653
  if (!delta) return;
@@ -712,42 +801,70 @@ ${delta}` : delta;
712
801
  });
713
802
  }
714
803
  }
715
- async stepEnded(d) {
716
- const usage = {
717
- inputTokens: Number(d.tokens?.input) || 0,
718
- outputTokens: Number(d.tokens?.output) || 0,
719
- ...Number(d.tokens?.cache?.read) ? {
720
- cacheReadTokens: Number(d.tokens.cache.read)
721
- } : {},
722
- ...Number(d.tokens?.cache?.write) ? {
723
- cacheWriteTokens: Number(d.tokens.cache.write)
724
- } : {}
725
- };
726
- const costUsd = typeof d.cost === "number" ? d.cost : void 0;
727
- this.usage.inputTokens += usage.inputTokens;
728
- this.usage.outputTokens += usage.outputTokens;
729
- if (costUsd !== void 0) this.costUsd = (this.costUsd ?? 0) + costUsd;
804
+ modelId() {
805
+ return this.model !== void 0 ? `${this.model.providerID}/${this.model.id}` : this.a.modelLabel;
806
+ }
807
+ /** One model call's usage and cost, into the ledger and the run's total. */
808
+ async spend(d, purpose) {
809
+ const usage = usageOf(d.tokens);
810
+ const reportedCostUsd = typeof d.cost === "number" && Number.isFinite(d.cost) && d.cost >= 0 ? d.cost : void 0;
811
+ const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
812
+ this.seen.cost += reportedCostUsd ?? 0;
813
+ this.seen.input += n(d.tokens?.input);
814
+ this.seen.output += n(d.tokens?.output);
815
+ this.seen.reasoning += n(d.tokens?.reasoning);
816
+ this.seen.read += n(d.tokens?.cache?.read);
817
+ this.seen.write += n(d.tokens?.cache?.write);
818
+ const fallback = reportedCostUsd !== void 0 ? {
819
+ costUsd: reportedCostUsd,
820
+ costSource: "provider"
821
+ } : {};
822
+ let cost = fallback;
823
+ if (this.a.priceStep !== void 0) {
824
+ cost = await this.a.priceStep({
825
+ model: this.model,
826
+ usage,
827
+ reportedCostUsd
828
+ }).catch(() => fallback);
829
+ }
830
+ this.spent = addUsage(this.spent, {
831
+ inputTokens: usage.inputTokens,
832
+ outputTokens: usage.outputTokens,
833
+ cacheReadTokens: usage.cacheReadTokens ?? 0,
834
+ cacheWriteTokens: usage.cacheWriteTokens ?? 0,
835
+ reasoningTokens: usage.reasoningTokens ?? 0,
836
+ costUsd: cost.costUsd ?? 0
837
+ });
838
+ if (cost.costUsd !== void 0) this.priced = true;
730
839
  await this.a.store.recordUsage({
731
840
  threadId: this.a.input.threadId,
732
841
  actorRef: this.a.input.actor.id,
733
- modelId: this.a.modelLabel,
734
- purpose: "chat",
842
+ modelId: this.modelId(),
843
+ purpose,
735
844
  usage,
736
- ...costUsd !== void 0 ? {
737
- costUsd,
738
- costSource: "provider"
739
- } : {}
845
+ ...cost
740
846
  });
847
+ return {
848
+ usage,
849
+ cost
850
+ };
851
+ }
852
+ /** `session.usage.recorded`: a call OpenCode made besides the steps (the title, a compaction). */
853
+ async usageRecorded(d) {
854
+ await this.spend(d, d.source === "title" ? "title" : "history_summary");
855
+ }
856
+ async stepEnded(d) {
857
+ const { usage, cost } = await this.spend(d, "chat");
741
858
  if (!this.stepOpen) return;
742
859
  this.stepOpen = false;
743
860
  await this.write({
744
861
  kind: "step-finish",
745
862
  usage,
746
- costUsd: costUsd ?? null,
863
+ costUsd: cost.costUsd ?? null,
747
864
  ...this.segment.reasoningMs > 0 ? {
748
865
  reasoningMs: this.segment.reasoningMs
749
866
  } : {},
750
- model: this.a.modelLabel
867
+ model: this.modelId()
751
868
  });
752
869
  }
753
870
  /**
@@ -766,12 +883,7 @@ ${delta}` : delta;
766
883
  content: s.text,
767
884
  runId: this.a.runId,
768
885
  ...final ? {
769
- usage: {
770
- ...this.usage,
771
- ...this.costUsd !== void 0 ? {
772
- costUsd: this.costUsd
773
- } : {}
774
- }
886
+ usage: this.messageUsage()
775
887
  } : {},
776
888
  ...this.a.input.agentName !== void 0 ? {
777
889
  agentName: this.a.input.agentName
@@ -799,6 +911,26 @@ ${delta}` : delta;
799
911
  ]);
800
912
  return message.id;
801
913
  }
914
+ /** The run's usage as its last message carries it: the whole run, cache tokens included. */
915
+ messageUsage() {
916
+ const total = this.totalUsage();
917
+ return {
918
+ inputTokens: total.inputTokens,
919
+ outputTokens: total.outputTokens,
920
+ ...total.cacheReadTokens > 0 ? {
921
+ cacheReadTokens: total.cacheReadTokens
922
+ } : {},
923
+ ...total.cacheWriteTokens > 0 ? {
924
+ cacheWriteTokens: total.cacheWriteTokens
925
+ } : {},
926
+ ...total.reasoningTokens > 0 ? {
927
+ reasoningTokens: total.reasoningTokens
928
+ } : {},
929
+ ...this.priced || (this.a.carried?.costUsd ?? 0) > 0 ? {
930
+ costUsd: total.costUsd
931
+ } : {}
932
+ };
933
+ }
802
934
  /** A request recorded by an earlier process (a turn resumed after a restart). */
803
935
  async alreadyRecorded(id) {
804
936
  return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
@@ -1150,9 +1282,39 @@ ${delta}` : delta;
1150
1282
  this.messageResults.set(messageId, results);
1151
1283
  await this.a.store.setMessageToolResults(messageId, results);
1152
1284
  }
1285
+ /** What the session spent since the baseline that no event of this turn reported. */
1286
+ async unreported() {
1287
+ const baseline = this.baseline;
1288
+ if (baseline === void 0) return;
1289
+ this.baseline = void 0;
1290
+ const info = await this.a.client.session.get?.({
1291
+ sessionID: this.a.sessionId
1292
+ }).catch(() => void 0);
1293
+ const after = info?.tokens;
1294
+ if (after === void 0) return;
1295
+ const before = baseline.tokens;
1296
+ const left = /* @__PURE__ */ __name((now, then, seen) => Math.max(0, (Number(now) || 0) - (Number(then) || 0) - seen), "left");
1297
+ const tokens = {
1298
+ input: left(after.input, before.input, this.seen.input),
1299
+ output: left(after.output, before.output, this.seen.output),
1300
+ reasoning: left(after.reasoning, before.reasoning, this.seen.reasoning),
1301
+ cache: {
1302
+ read: left(after.cache?.read, before.cache?.read, this.seen.read),
1303
+ write: left(after.cache?.write, before.cache?.write, this.seen.write)
1304
+ }
1305
+ };
1306
+ const total = tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write;
1307
+ if (total === 0) return;
1308
+ const cost = left(info?.cost, baseline.cost, this.seen.cost);
1309
+ await this.spend({
1310
+ tokens,
1311
+ cost: cost < 1e-9 ? 0 : cost
1312
+ }, this.compacted ? "history_summary" : "title");
1313
+ }
1153
1314
  async end(outcome) {
1154
1315
  if (this.ended) return;
1155
1316
  this.ended = true;
1317
+ await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
1156
1318
  if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
1157
1319
  await this.flush(true);
1158
1320
  }
@@ -1168,6 +1330,35 @@ ${delta}` : delta;
1168
1330
  });
1169
1331
  }
1170
1332
  };
1333
+ function modelOfLabel(label) {
1334
+ const slash = label?.indexOf("/") ?? -1;
1335
+ if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
1336
+ return {
1337
+ providerID: label.slice(0, slash),
1338
+ id: label.slice(slash + 1)
1339
+ };
1340
+ }
1341
+ __name(modelOfLabel, "modelOfLabel");
1342
+ function usageOf(tokens) {
1343
+ const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
1344
+ const read = n(tokens?.cache?.read);
1345
+ const write = n(tokens?.cache?.write);
1346
+ const reasoning = n(tokens?.reasoning);
1347
+ return {
1348
+ inputTokens: n(tokens?.input) + read + write,
1349
+ outputTokens: n(tokens?.output),
1350
+ ...read > 0 ? {
1351
+ cacheReadTokens: read
1352
+ } : {},
1353
+ ...write > 0 ? {
1354
+ cacheWriteTokens: write
1355
+ } : {},
1356
+ ...reasoning > 0 ? {
1357
+ reasoningTokens: reasoning
1358
+ } : {}
1359
+ };
1360
+ }
1361
+ __name(usageOf, "usageOf");
1171
1362
 
1172
1363
  // src/mcp.ts
1173
1364
  var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
@@ -1432,7 +1623,7 @@ import { Inject as Inject3, Injectable as Injectable2 } from "@nestjs/common";
1432
1623
  // src/turns.ts
1433
1624
  import { randomUUID } from "crypto";
1434
1625
  import { AGENT_CHAT_QUEUE } from "@dudousxd/nestjs-agent";
1435
- import { AGENT_DEPS_FACTORY, AGENT_MEMORY, AGENT_SINK, AGENT_SKILLS, AGENT_STORE, AGENT_TOOL_REGISTRY, DEFAULT_MAX_FACT_CHARS, DefaultApprovalPolicy, RUN_ENDED_BEFORE_TOOL_CALL, actorScope, buildMemoryBlock, defaultScopeResolver, encodeStreamEvent as encodeStreamEvent2, memoryWriteVerdict, publishAgentRunFailed, releaseThreadRun, resolveMemoryDigest, resolveSkillCatalog, streamFailure } from "@dudousxd/nestjs-agent-core";
1626
+ import { AGENT_DEPS_FACTORY, AGENT_MEMORY, AGENT_OPTIONS, AGENT_PRICING_STORE, AGENT_SINK, AGENT_SKILLS, AGENT_STORE, AGENT_TOOL_REGISTRY, DEFAULT_MAX_FACT_CHARS, DefaultApprovalPolicy, ModelPriceResolver, RUN_ENDED_BEFORE_TOOL_CALL, actorScope, buildMemoryBlock, defaultScopeResolver, encodeStreamEvent as encodeStreamEvent2, memoryWriteVerdict, parseBedrockModelId, publishAgentRunFailed, releaseThreadRun, resolveMemoryDigest, resolveSkillCatalog, resolveUsageCost, streamFailure } from "@dudousxd/nestjs-agent-core";
1436
1627
  import { Inject as Inject2, Injectable, Logger as Logger2, Optional } from "@nestjs/common";
1437
1628
 
1438
1629
  // src/event-hub.ts
@@ -1599,7 +1790,7 @@ var OpenCodeTurns = class {
1599
1790
  spent = /* @__PURE__ */ new Set();
1600
1791
  /** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
1601
1792
  toolsIssued = /* @__PURE__ */ new Map();
1602
- constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2) {
1793
+ constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
1603
1794
  this.host = host;
1604
1795
  this.sessions = sessions;
1605
1796
  this.settings = settings;
@@ -1611,6 +1802,30 @@ var OpenCodeTurns = class {
1611
1802
  this.memory = memory;
1612
1803
  this.queue = queue;
1613
1804
  this.toolsTokens = toolsTokens2;
1805
+ this.prices = new ModelPriceResolver({
1806
+ pricingStore,
1807
+ catalog: agentOptions?.priceCatalog,
1808
+ log: {
1809
+ info: /* @__PURE__ */ __name((message) => this.logger.log(message), "info"),
1810
+ warn: /* @__PURE__ */ __name((message) => this.logger.warn(message), "warn")
1811
+ }
1812
+ });
1813
+ }
1814
+ prices;
1815
+ /**
1816
+ * What a model call OpenCode made cost, by the same rule the library's loop records its own steps
1817
+ * at: a reported figure wins, else an estimate off the model's price — see
1818
+ * {@link OpenCodeEngineSettings.cost}.
1819
+ */
1820
+ async priceStep(step) {
1821
+ const { model, usage, reportedCostUsd } = step;
1822
+ const tokens = usage.inputTokens + usage.outputTokens;
1823
+ const reported = reportedCostUsd !== void 0 && (reportedCostUsd > 0 || tokens === 0);
1824
+ if (reported && this.settings.cost !== "estimate") {
1825
+ return resolveUsageCost(usage, reportedCostUsd, void 0);
1826
+ }
1827
+ const price = model !== void 0 && tokens > 0 ? await this.prices.priceFor(priceRef(model)) : void 0;
1828
+ return resolveUsageCost(usage, price === void 0 ? reportedCostUsd : void 0, price);
1614
1829
  }
1615
1830
  /** The tools server's name in OpenCode. */
1616
1831
  get toolsServer() {
@@ -2200,8 +2415,8 @@ ${body}
2200
2415
  }
2201
2416
  // ---- prompt / observe / reply --------------------------------------------------------------
2202
2417
  /** Start listening to the session (so nothing it says is missed), then send the user message. */
2203
- async prompt(runId, input, handle) {
2204
- const live = await this.ensureLive(runId, input, handle, false);
2418
+ async prompt(runId, input, handle, spent) {
2419
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2205
2420
  try {
2206
2421
  const prompt = await this.host.promptFor?.({
2207
2422
  input,
@@ -2216,6 +2431,7 @@ ${body}
2216
2431
  })
2217
2432
  } : {}
2218
2433
  };
2434
+ live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
2219
2435
  await live.client.session.prompt({
2220
2436
  sessionID: handle.sessionId,
2221
2437
  text: prompt.text,
@@ -2231,9 +2447,12 @@ ${body}
2231
2447
  * Wait for the turn's next milestone. In a process that was not following the turn (a resumed
2232
2448
  * durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
2233
2449
  * `session.wait` is the safety net for a terminal event that was missed.
2450
+ *
2451
+ * `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
2452
+ * rebuilt here starts from it, so the answer it persists carries the whole run's usage.
2234
2453
  */
2235
- async observe(runId, input, handle) {
2236
- const live = await this.ensureLive(runId, input, handle, true);
2454
+ async observe(runId, input, handle, spent) {
2455
+ const live = await this.ensureLive(runId, input, handle, true, spent);
2237
2456
  const { turn, client } = live;
2238
2457
  if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
2239
2458
  let done = false;
@@ -2272,32 +2491,43 @@ ${body}
2272
2491
  * request is gone with it: a new session is opened, told what the person decided, and prompted
2273
2492
  * to go on.
2274
2493
  */
2275
- async reply(runId, input, handle, ask, reply) {
2494
+ async reply(runId, input, handle, ask, reply, spent) {
2276
2495
  const server = await this.host.server(input.actor);
2277
2496
  if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
2278
- return this.continueAfterRestart(runId, input, server, ask, reply);
2497
+ return this.continueAfterRestart(runId, input, server, ask, reply, spent);
2279
2498
  }
2280
- const live = await this.ensureLive(runId, input, handle, false);
2499
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2281
2500
  await live.turn.decide(ask, reply);
2282
2501
  return handle;
2283
2502
  }
2284
- async continueAfterRestart(runId, input, server, ask, reply) {
2503
+ async continueAfterRestart(runId, input, server, ask, reply, spent) {
2285
2504
  const approved = "approved" in reply && reply.approved === true;
2286
2505
  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.`;
2287
2506
  const thread = await this.store.getThread(input.threadId);
2288
2507
  const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
2289
2508
  await this.refreshInstructions(runId, input, server.client, handle.sessionId);
2290
2509
  this.drop(runId);
2291
- const live = await this.ensureLive(runId, input, handle, false);
2510
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2292
2511
  await live.turn.decide(ask, reply, {
2293
2512
  tellOpenCode: false
2294
2513
  });
2514
+ live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
2295
2515
  await live.client.session.prompt({
2296
2516
  sessionID: handle.sessionId,
2297
2517
  text: note
2298
2518
  });
2299
2519
  return handle;
2300
2520
  }
2521
+ /** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
2522
+ async sessionUsage(client, sessionId) {
2523
+ try {
2524
+ return await client.session.get?.({
2525
+ sessionID: sessionId
2526
+ });
2527
+ } catch {
2528
+ return void 0;
2529
+ }
2530
+ }
2301
2531
  /** Interrupt the thread's session (cancel). */
2302
2532
  async interrupt(input) {
2303
2533
  const known = await this.sessions.get(input.threadId);
@@ -2308,7 +2538,7 @@ ${body}
2308
2538
  sessionID: known.sessionId
2309
2539
  });
2310
2540
  }
2311
- async ensureLive(runId, input, handle, catchUp) {
2541
+ async ensureLive(runId, input, handle, catchUp, spent) {
2312
2542
  const current = this.live.get(runId);
2313
2543
  if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
2314
2544
  current?.stop();
@@ -2334,6 +2564,10 @@ ${body}
2334
2564
  } : {}
2335
2565
  }), "approvalFor"),
2336
2566
  modelLabel: input.model ?? "opencode",
2567
+ priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
2568
+ ...spent !== void 0 ? {
2569
+ carried: spent
2570
+ } : {},
2337
2571
  logger: this.logger,
2338
2572
  onGranted: /* @__PURE__ */ __name((action, id) => {
2339
2573
  const grants = this.grants.get(runId) ?? [];
@@ -2380,20 +2614,14 @@ ${body}
2380
2614
  }
2381
2615
  return milestone;
2382
2616
  }
2383
- /** What the run produced, as the host's settle hooks see it. */
2384
- async runResult(runId, input, outcome, durationMs) {
2617
+ /**
2618
+ * What the run produced, as the host's settle hooks see it. `usage` is what the run spent — the
2619
+ * runner's sum of its milestones' usage, which a resumed run reads back from its journal — else
2620
+ * what the turn this process follows saw.
2621
+ */
2622
+ async runResult(runId, input, outcome, durationMs, usage) {
2385
2623
  const thread = await this.store.getThread(input.threadId);
2386
2624
  const messages = (thread?.messages ?? []).filter((m) => m.role === "assistant" && m.runId === runId);
2387
- const usage = {
2388
- inputTokens: 0,
2389
- outputTokens: 0,
2390
- costUsd: 0
2391
- };
2392
- for (const m of messages) {
2393
- usage.inputTokens += m.usage?.inputTokens ?? 0;
2394
- usage.outputTokens += m.usage?.outputTokens ?? 0;
2395
- usage.costUsd += m.usage?.costUsd ?? 0;
2396
- }
2397
2625
  return {
2398
2626
  runId,
2399
2627
  input,
@@ -2438,15 +2666,31 @@ ${body}
2438
2666
  this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
2439
2667
  }
2440
2668
  }
2441
- /** End the run's stream and bookkeeping for how its execution ended. */
2442
- async settle(runId, input, outcome, durationMs) {
2669
+ /**
2670
+ * What the run spent: the runner's sum of its milestones, or what the turn this process follows
2671
+ * saw (which starts from that sum) when it saw more — a run that failed or was cancelled between
2672
+ * two milestones.
2673
+ */
2674
+ spentBy(runId, spent) {
2675
+ const seen = this.live.get(runId)?.turn.totalUsage();
2676
+ if (spent === void 0) return seen ?? emptyUsage();
2677
+ if (seen === void 0) return spent;
2678
+ const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
2679
+ return tokens(seen) > tokens(spent) ? seen : spent;
2680
+ }
2681
+ /**
2682
+ * End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
2683
+ * (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
2684
+ */
2685
+ async settle(runId, input, outcome, durationMs, spent) {
2443
2686
  if (outcome.status === "failed") {
2444
- await this.settleFailed(runId, input, outcome.error, durationMs);
2687
+ await this.settleFailed(runId, input, outcome.error, durationMs, spent);
2445
2688
  return;
2446
2689
  }
2690
+ const usage = this.spentBy(runId, spent);
2447
2691
  this.forget(runId);
2448
2692
  const writer = await this.sink.open(runId);
2449
- const result = await this.runResult(runId, input, outcome, durationMs);
2693
+ const result = await this.runResult(runId, input, outcome, durationMs, usage);
2450
2694
  await this.amend(writer, result);
2451
2695
  if (outcome.status === "interrupted") {
2452
2696
  this.logger.log(`agent run ${runId} cancelled`);
@@ -2474,13 +2718,14 @@ ${body}
2474
2718
  }
2475
2719
  await this.settled(result);
2476
2720
  }
2477
- async settleFailed(runId, input, error, durationMs = 0) {
2721
+ async settleFailed(runId, input, error, durationMs = 0, spent) {
2722
+ const usage = this.spentBy(runId, spent);
2478
2723
  this.forget(runId);
2479
2724
  const writer = await this.sink.open(runId);
2480
2725
  const result = await this.runResult(runId, input, {
2481
2726
  status: "failed",
2482
2727
  error
2483
- }, durationMs);
2728
+ }, durationMs, usage);
2484
2729
  const worded = (await this.amend(writer, result)).error;
2485
2730
  const message = worded ?? streamFailure(new Error(error)).message;
2486
2731
  this.logger.error(`agent run ${runId} failed: ${error}`);
@@ -2550,6 +2795,10 @@ OpenCodeTurns = _ts_decorate2([
2550
2795
  _ts_param2(9, Inject2(AGENT_CHAT_QUEUE)),
2551
2796
  _ts_param2(10, Optional()),
2552
2797
  _ts_param2(10, Inject2(OPENCODE_TOOLS_TOKENS)),
2798
+ _ts_param2(11, Optional()),
2799
+ _ts_param2(11, Inject2(AGENT_PRICING_STORE)),
2800
+ _ts_param2(12, Optional()),
2801
+ _ts_param2(12, Inject2(AGENT_OPTIONS)),
2553
2802
  _ts_metadata2("design:type", Function),
2554
2803
  _ts_metadata2("design:paramtypes", [
2555
2804
  typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
@@ -2562,6 +2811,8 @@ OpenCodeTurns = _ts_decorate2([
2562
2811
  typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
2563
2812
  typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
2564
2813
  typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
2814
+ Object,
2815
+ typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
2565
2816
  Object
2566
2817
  ])
2567
2818
  ], OpenCodeTurns);
@@ -2604,6 +2855,19 @@ function uiEmitter(scope, push) {
2604
2855
  };
2605
2856
  }
2606
2857
  __name(uiEmitter, "uiEmitter");
2858
+ function priceRef(model) {
2859
+ const aliases = [
2860
+ model.id
2861
+ ];
2862
+ const tail = model.id.slice(model.id.lastIndexOf("/") + 1);
2863
+ if (tail !== model.id && parseBedrockModelId(tail) !== void 0) aliases.push(tail);
2864
+ return {
2865
+ modelId: `${model.providerID}/${model.id}`,
2866
+ provider: model.providerID,
2867
+ aliases
2868
+ };
2869
+ }
2870
+ __name(priceRef, "priceRef");
2607
2871
 
2608
2872
  // src/runner.ts
2609
2873
  function _ts_decorate3(decorators, target, key, desc) {
@@ -2705,18 +2969,20 @@ var OpenCodeAgentRunner = class {
2705
2969
  async run(runId, input) {
2706
2970
  const started = Date.now();
2707
2971
  this.inputs.set(runId, input);
2972
+ let spent = emptyUsage();
2708
2973
  try {
2709
2974
  let handle = await this.turns.begin(runId, input);
2710
2975
  if (this.cancelled.has(runId)) throw new RunCancelledError();
2711
- await this.turns.prompt(runId, input, handle);
2976
+ await this.turns.prompt(runId, input, handle, spent);
2712
2977
  for (; ; ) {
2713
- const milestone = await this.turns.observe(runId, input, handle);
2978
+ const milestone = await this.turns.observe(runId, input, handle, spent);
2979
+ spent = addUsage(spent, milestone.usage);
2714
2980
  if (milestone.kind === "finished") {
2715
- await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
2981
+ await this.turns.settle(runId, input, milestone.outcome, Date.now() - started, spent);
2716
2982
  return;
2717
2983
  }
2718
2984
  const reply = await this.park(runId, milestone.ask.id, milestone.ask.kind === "approval" ? "approval" : "answers", milestone.timeoutMs);
2719
- handle = await this.turns.reply(runId, input, handle, milestone.ask, reply);
2985
+ handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
2720
2986
  }
2721
2987
  } catch (error) {
2722
2988
  if (error instanceof RunCancelledError) {
@@ -2726,7 +2992,7 @@ var OpenCodeAgentRunner = class {
2726
2992
  }, Date.now() - started);
2727
2993
  return;
2728
2994
  }
2729
- await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"));
2995
+ await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
2730
2996
  } finally {
2731
2997
  this.inputs.delete(runId);
2732
2998
  }
@@ -2878,6 +3144,8 @@ export {
2878
3144
  OpenCodeToolsTokens,
2879
3145
  OpenCodeTurn,
2880
3146
  OpenCodeTurns,
3147
+ addUsage,
3148
+ emptyUsage,
2881
3149
  keyValueOpenCodeSessionStore,
2882
3150
  openCode,
2883
3151
  openCodeControllers,
@@ -2885,6 +3153,7 @@ export {
2885
3153
  sessionOf,
2886
3154
  toElicitation,
2887
3155
  toFormAnswer,
2888
- toQuestion
3156
+ toQuestion,
3157
+ usageOf
2889
3158
  };
2890
3159
  //# sourceMappingURL=index.js.map