@dudousxd/nestjs-agent-opencode 0.1.1 → 0.2.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.
@@ -1,5 +1,5 @@
1
1
  import { AgentEngine } from '@dudousxd/nestjs-agent';
2
- import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-Bij6AdoF.cjs';
2
+ import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-JnBbcRxh.cjs';
3
3
  import { AgentRunner, AgentStore, AgentRunInput, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
4
4
  import { WorkflowService } from '@dudousxd/nestjs-durable';
5
5
  import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
@@ -1,5 +1,5 @@
1
1
  import { AgentEngine } from '@dudousxd/nestjs-agent';
2
- import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-Bij6AdoF.js';
2
+ import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-JnBbcRxh.js';
3
3
  import { AgentRunner, AgentStore, AgentRunInput, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
4
4
  import { WorkflowService } from '@dudousxd/nestjs-durable';
5
5
  import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
@@ -221,6 +221,41 @@ function toFormAnswer(form, answers = {}) {
221
221
  __name(toFormAnswer, "toFormAnswer");
222
222
 
223
223
  // src/turn.ts
224
+ function emptyUsage() {
225
+ return {
226
+ inputTokens: 0,
227
+ outputTokens: 0,
228
+ cacheReadTokens: 0,
229
+ cacheWriteTokens: 0,
230
+ reasoningTokens: 0,
231
+ costUsd: 0
232
+ };
233
+ }
234
+ __name(emptyUsage, "emptyUsage");
235
+ function addUsage(a, b) {
236
+ const x = a ?? emptyUsage();
237
+ const y = b ?? emptyUsage();
238
+ return {
239
+ inputTokens: x.inputTokens + (y.inputTokens ?? 0),
240
+ outputTokens: x.outputTokens + (y.outputTokens ?? 0),
241
+ cacheReadTokens: (x.cacheReadTokens ?? 0) + (y.cacheReadTokens ?? 0),
242
+ cacheWriteTokens: (x.cacheWriteTokens ?? 0) + (y.cacheWriteTokens ?? 0),
243
+ reasoningTokens: (x.reasoningTokens ?? 0) + (y.reasoningTokens ?? 0),
244
+ costUsd: x.costUsd + (y.costUsd ?? 0)
245
+ };
246
+ }
247
+ __name(addUsage, "addUsage");
248
+ function subUsage(a, b) {
249
+ return {
250
+ inputTokens: a.inputTokens - b.inputTokens,
251
+ outputTokens: a.outputTokens - b.outputTokens,
252
+ cacheReadTokens: a.cacheReadTokens - b.cacheReadTokens,
253
+ cacheWriteTokens: a.cacheWriteTokens - b.cacheWriteTokens,
254
+ reasoningTokens: a.reasoningTokens - b.reasoningTokens,
255
+ costUsd: a.costUsd - b.costUsd
256
+ };
257
+ }
258
+ __name(subUsage, "subUsage");
224
259
  var ASK_TOOL_NAME = "ask";
225
260
  var FAILED_STATUSES = /* @__PURE__ */ new Set([
226
261
  "error",
@@ -286,11 +321,11 @@ var OpenCodeTurn = class {
286
321
  hidden = /* @__PURE__ */ new Set();
287
322
  milestones = [];
288
323
  waiting;
289
- usage = {
290
- inputTokens: 0,
291
- outputTokens: 0
292
- };
293
- costUsd;
324
+ /** What this turn object saw spent, and how much of it a milestone already reported. */
325
+ spent = emptyUsage();
326
+ reported = emptyUsage();
327
+ priced = false;
328
+ model;
294
329
  stepOpen = false;
295
330
  sawText = false;
296
331
  separator = false;
@@ -300,6 +335,11 @@ var OpenCodeTurn = class {
300
335
  stepError;
301
336
  constructor(a) {
302
337
  this.a = a;
338
+ this.model = modelOfLabel(a.input.model);
339
+ }
340
+ /** What the run spent so far: carried from before this turn object, plus what it saw. */
341
+ totalUsage() {
342
+ return addUsage(this.a.carried, this.spent);
303
343
  }
304
344
  get sessionId() {
305
345
  return this.a.sessionId;
@@ -382,7 +422,15 @@ var OpenCodeTurn = class {
382
422
  this.chain = this.chain.then(() => ask.kind === "approval" ? this.decided(ask, reply, tell) : this.answered(ask, reply, tell)).then(resolve, reject);
383
423
  });
384
424
  }
385
- reach(milestone) {
425
+ reach(reached) {
426
+ const usage = subUsage(this.spent, this.reported);
427
+ this.reported = {
428
+ ...this.spent
429
+ };
430
+ const milestone = {
431
+ ...reached,
432
+ usage
433
+ };
386
434
  const waiting = this.waiting;
387
435
  if (waiting !== void 0) {
388
436
  this.waiting = void 0;
@@ -538,9 +586,22 @@ var OpenCodeTurn = class {
538
586
  }
539
587
  }
540
588
  async onEvent(e) {
541
- if (this.ended) return;
542
589
  const d = e.data ?? {};
590
+ if (e.type === "session.usage.recorded") {
591
+ await this.usageRecorded(d);
592
+ return;
593
+ }
594
+ if (this.ended) return;
543
595
  switch (e.type) {
596
+ case "session.step.started":
597
+ case "session.model.selected": {
598
+ const model = d.model;
599
+ if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
600
+ providerID: model.providerID,
601
+ id: model.id
602
+ };
603
+ return;
604
+ }
544
605
  case "session.text.delta": {
545
606
  const delta = typeof d.delta === "string" ? d.delta : "";
546
607
  if (!delta) return;
@@ -694,41 +755,63 @@ ${delta}` : delta;
694
755
  });
695
756
  }
696
757
  }
697
- async stepEnded(d) {
698
- const usage = {
699
- inputTokens: Number(d.tokens?.input) || 0,
700
- outputTokens: Number(d.tokens?.output) || 0,
701
- ...Number(d.tokens?.cache?.read) ? {
702
- cacheReadTokens: Number(d.tokens.cache.read)
703
- } : {},
704
- ...Number(d.tokens?.cache?.write) ? {
705
- cacheWriteTokens: Number(d.tokens.cache.write)
706
- } : {}
707
- };
708
- const costUsd = typeof d.cost === "number" ? d.cost : void 0;
709
- this.usage.inputTokens += usage.inputTokens;
710
- this.usage.outputTokens += usage.outputTokens;
711
- if (costUsd !== void 0) this.costUsd = (this.costUsd ?? 0) + costUsd;
758
+ modelId() {
759
+ return this.model !== void 0 ? `${this.model.providerID}/${this.model.id}` : this.a.modelLabel;
760
+ }
761
+ /** One model call's usage and cost, into the ledger and the run's total. */
762
+ async spend(d, purpose) {
763
+ const usage = usageOf(d.tokens);
764
+ const reportedCostUsd = typeof d.cost === "number" && Number.isFinite(d.cost) && d.cost >= 0 ? d.cost : void 0;
765
+ const fallback = reportedCostUsd !== void 0 ? {
766
+ costUsd: reportedCostUsd,
767
+ costSource: "provider"
768
+ } : {};
769
+ let cost = fallback;
770
+ if (this.a.priceStep !== void 0) {
771
+ cost = await this.a.priceStep({
772
+ model: this.model,
773
+ usage,
774
+ reportedCostUsd
775
+ }).catch(() => fallback);
776
+ }
777
+ this.spent = addUsage(this.spent, {
778
+ inputTokens: usage.inputTokens,
779
+ outputTokens: usage.outputTokens,
780
+ cacheReadTokens: usage.cacheReadTokens ?? 0,
781
+ cacheWriteTokens: usage.cacheWriteTokens ?? 0,
782
+ reasoningTokens: usage.reasoningTokens ?? 0,
783
+ costUsd: cost.costUsd ?? 0
784
+ });
785
+ if (cost.costUsd !== void 0) this.priced = true;
712
786
  await this.a.store.recordUsage({
713
787
  threadId: this.a.input.threadId,
714
788
  actorRef: this.a.input.actor.id,
715
- modelId: this.a.modelLabel,
716
- purpose: "chat",
789
+ modelId: this.modelId(),
790
+ purpose,
717
791
  usage,
718
- ...costUsd !== void 0 ? {
719
- costUsd
720
- } : {}
792
+ ...cost
721
793
  });
794
+ return {
795
+ usage,
796
+ cost
797
+ };
798
+ }
799
+ /** `session.usage.recorded`: a call OpenCode made besides the steps (the title, a compaction). */
800
+ async usageRecorded(d) {
801
+ await this.spend(d, d.source === "title" ? "title" : "history_summary");
802
+ }
803
+ async stepEnded(d) {
804
+ const { usage, cost } = await this.spend(d, "chat");
722
805
  if (!this.stepOpen) return;
723
806
  this.stepOpen = false;
724
807
  await this.write({
725
808
  kind: "step-finish",
726
809
  usage,
727
- costUsd: costUsd ?? null,
810
+ costUsd: cost.costUsd ?? null,
728
811
  ...this.segment.reasoningMs > 0 ? {
729
812
  reasoningMs: this.segment.reasoningMs
730
813
  } : {},
731
- model: this.a.modelLabel
814
+ model: this.modelId()
732
815
  });
733
816
  }
734
817
  /**
@@ -747,12 +830,7 @@ ${delta}` : delta;
747
830
  content: s.text,
748
831
  runId: this.a.runId,
749
832
  ...final ? {
750
- usage: {
751
- ...this.usage,
752
- ...this.costUsd !== void 0 ? {
753
- costUsd: this.costUsd
754
- } : {}
755
- }
833
+ usage: this.messageUsage()
756
834
  } : {},
757
835
  ...this.a.input.agentName !== void 0 ? {
758
836
  agentName: this.a.input.agentName
@@ -780,6 +858,26 @@ ${delta}` : delta;
780
858
  ]);
781
859
  return message.id;
782
860
  }
861
+ /** The run's usage as its last message carries it: the whole run, cache tokens included. */
862
+ messageUsage() {
863
+ const total = this.totalUsage();
864
+ return {
865
+ inputTokens: total.inputTokens,
866
+ outputTokens: total.outputTokens,
867
+ ...total.cacheReadTokens > 0 ? {
868
+ cacheReadTokens: total.cacheReadTokens
869
+ } : {},
870
+ ...total.cacheWriteTokens > 0 ? {
871
+ cacheWriteTokens: total.cacheWriteTokens
872
+ } : {},
873
+ ...total.reasoningTokens > 0 ? {
874
+ reasoningTokens: total.reasoningTokens
875
+ } : {},
876
+ ...this.priced || (this.a.carried?.costUsd ?? 0) > 0 ? {
877
+ costUsd: total.costUsd
878
+ } : {}
879
+ };
880
+ }
783
881
  /** A request recorded by an earlier process (a turn resumed after a restart). */
784
882
  async alreadyRecorded(id) {
785
883
  return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
@@ -1149,6 +1247,35 @@ ${delta}` : delta;
1149
1247
  });
1150
1248
  }
1151
1249
  };
1250
+ function modelOfLabel(label) {
1251
+ const slash = label?.indexOf("/") ?? -1;
1252
+ if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
1253
+ return {
1254
+ providerID: label.slice(0, slash),
1255
+ id: label.slice(slash + 1)
1256
+ };
1257
+ }
1258
+ __name(modelOfLabel, "modelOfLabel");
1259
+ function usageOf(tokens) {
1260
+ const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
1261
+ const read = n(tokens?.cache?.read);
1262
+ const write = n(tokens?.cache?.write);
1263
+ const reasoning = n(tokens?.reasoning);
1264
+ return {
1265
+ inputTokens: n(tokens?.input) + read + write,
1266
+ outputTokens: n(tokens?.output),
1267
+ ...read > 0 ? {
1268
+ cacheReadTokens: read
1269
+ } : {},
1270
+ ...write > 0 ? {
1271
+ cacheWriteTokens: write
1272
+ } : {},
1273
+ ...reasoning > 0 ? {
1274
+ reasoningTokens: reasoning
1275
+ } : {}
1276
+ };
1277
+ }
1278
+ __name(usageOf, "usageOf");
1152
1279
 
1153
1280
  // src/mcp.ts
1154
1281
  var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
@@ -1413,7 +1540,7 @@ import { Inject as Inject3, Injectable as Injectable2 } from "@nestjs/common";
1413
1540
  // src/turns.ts
1414
1541
  import { randomUUID } from "crypto";
1415
1542
  import { AGENT_CHAT_QUEUE } from "@dudousxd/nestjs-agent";
1416
- 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";
1543
+ 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";
1417
1544
  import { Inject as Inject2, Injectable, Logger as Logger2, Optional } from "@nestjs/common";
1418
1545
 
1419
1546
  // src/event-hub.ts
@@ -1580,7 +1707,7 @@ var OpenCodeTurns = class {
1580
1707
  spent = /* @__PURE__ */ new Set();
1581
1708
  /** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
1582
1709
  toolsIssued = /* @__PURE__ */ new Map();
1583
- constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2) {
1710
+ constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
1584
1711
  this.host = host;
1585
1712
  this.sessions = sessions;
1586
1713
  this.settings = settings;
@@ -1592,6 +1719,30 @@ var OpenCodeTurns = class {
1592
1719
  this.memory = memory;
1593
1720
  this.queue = queue;
1594
1721
  this.toolsTokens = toolsTokens2;
1722
+ this.prices = new ModelPriceResolver({
1723
+ pricingStore,
1724
+ catalog: agentOptions?.priceCatalog,
1725
+ log: {
1726
+ info: /* @__PURE__ */ __name((message) => this.logger.log(message), "info"),
1727
+ warn: /* @__PURE__ */ __name((message) => this.logger.warn(message), "warn")
1728
+ }
1729
+ });
1730
+ }
1731
+ prices;
1732
+ /**
1733
+ * What a model call OpenCode made cost, by the same rule the library's loop records its own steps
1734
+ * at: a reported figure wins, else an estimate off the model's price — see
1735
+ * {@link OpenCodeEngineSettings.cost}.
1736
+ */
1737
+ async priceStep(step) {
1738
+ const { model, usage, reportedCostUsd } = step;
1739
+ const tokens = usage.inputTokens + usage.outputTokens;
1740
+ const reported = reportedCostUsd !== void 0 && (reportedCostUsd > 0 || tokens === 0);
1741
+ if (reported && this.settings.cost !== "estimate") {
1742
+ return resolveUsageCost(usage, reportedCostUsd, void 0);
1743
+ }
1744
+ const price = model !== void 0 && tokens > 0 ? await this.prices.priceFor(priceRef(model)) : void 0;
1745
+ return resolveUsageCost(usage, price === void 0 ? reportedCostUsd : void 0, price);
1595
1746
  }
1596
1747
  /** The tools server's name in OpenCode. */
1597
1748
  get toolsServer() {
@@ -2181,8 +2332,8 @@ ${body}
2181
2332
  }
2182
2333
  // ---- prompt / observe / reply --------------------------------------------------------------
2183
2334
  /** Start listening to the session (so nothing it says is missed), then send the user message. */
2184
- async prompt(runId, input, handle) {
2185
- const live = await this.ensureLive(runId, input, handle, false);
2335
+ async prompt(runId, input, handle, spent) {
2336
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2186
2337
  try {
2187
2338
  const prompt = await this.host.promptFor?.({
2188
2339
  input,
@@ -2212,9 +2363,12 @@ ${body}
2212
2363
  * Wait for the turn's next milestone. In a process that was not following the turn (a resumed
2213
2364
  * durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
2214
2365
  * `session.wait` is the safety net for a terminal event that was missed.
2366
+ *
2367
+ * `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
2368
+ * rebuilt here starts from it, so the answer it persists carries the whole run's usage.
2215
2369
  */
2216
- async observe(runId, input, handle) {
2217
- const live = await this.ensureLive(runId, input, handle, true);
2370
+ async observe(runId, input, handle, spent) {
2371
+ const live = await this.ensureLive(runId, input, handle, true, spent);
2218
2372
  const { turn, client } = live;
2219
2373
  if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
2220
2374
  let done = false;
@@ -2253,23 +2407,23 @@ ${body}
2253
2407
  * request is gone with it: a new session is opened, told what the person decided, and prompted
2254
2408
  * to go on.
2255
2409
  */
2256
- async reply(runId, input, handle, ask, reply) {
2410
+ async reply(runId, input, handle, ask, reply, spent) {
2257
2411
  const server = await this.host.server(input.actor);
2258
2412
  if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
2259
- return this.continueAfterRestart(runId, input, server, ask, reply);
2413
+ return this.continueAfterRestart(runId, input, server, ask, reply, spent);
2260
2414
  }
2261
- const live = await this.ensureLive(runId, input, handle, false);
2415
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2262
2416
  await live.turn.decide(ask, reply);
2263
2417
  return handle;
2264
2418
  }
2265
- async continueAfterRestart(runId, input, server, ask, reply) {
2419
+ async continueAfterRestart(runId, input, server, ask, reply, spent) {
2266
2420
  const approved = "approved" in reply && reply.approved === true;
2267
2421
  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.`;
2268
2422
  const thread = await this.store.getThread(input.threadId);
2269
2423
  const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
2270
2424
  await this.refreshInstructions(runId, input, server.client, handle.sessionId);
2271
2425
  this.drop(runId);
2272
- const live = await this.ensureLive(runId, input, handle, false);
2426
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2273
2427
  await live.turn.decide(ask, reply, {
2274
2428
  tellOpenCode: false
2275
2429
  });
@@ -2289,7 +2443,7 @@ ${body}
2289
2443
  sessionID: known.sessionId
2290
2444
  });
2291
2445
  }
2292
- async ensureLive(runId, input, handle, catchUp) {
2446
+ async ensureLive(runId, input, handle, catchUp, spent) {
2293
2447
  const current = this.live.get(runId);
2294
2448
  if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
2295
2449
  current?.stop();
@@ -2315,6 +2469,10 @@ ${body}
2315
2469
  } : {}
2316
2470
  }), "approvalFor"),
2317
2471
  modelLabel: input.model ?? "opencode",
2472
+ priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
2473
+ ...spent !== void 0 ? {
2474
+ carried: spent
2475
+ } : {},
2318
2476
  logger: this.logger,
2319
2477
  onGranted: /* @__PURE__ */ __name((action, id) => {
2320
2478
  const grants = this.grants.get(runId) ?? [];
@@ -2361,20 +2519,14 @@ ${body}
2361
2519
  }
2362
2520
  return milestone;
2363
2521
  }
2364
- /** What the run produced, as the host's settle hooks see it. */
2365
- async runResult(runId, input, outcome, durationMs) {
2522
+ /**
2523
+ * What the run produced, as the host's settle hooks see it. `usage` is what the run spent — the
2524
+ * runner's sum of its milestones' usage, which a resumed run reads back from its journal — else
2525
+ * what the turn this process follows saw.
2526
+ */
2527
+ async runResult(runId, input, outcome, durationMs, usage) {
2366
2528
  const thread = await this.store.getThread(input.threadId);
2367
2529
  const messages = (thread?.messages ?? []).filter((m) => m.role === "assistant" && m.runId === runId);
2368
- const usage = {
2369
- inputTokens: 0,
2370
- outputTokens: 0,
2371
- costUsd: 0
2372
- };
2373
- for (const m of messages) {
2374
- usage.inputTokens += m.usage?.inputTokens ?? 0;
2375
- usage.outputTokens += m.usage?.outputTokens ?? 0;
2376
- usage.costUsd += m.usage?.costUsd ?? 0;
2377
- }
2378
2530
  return {
2379
2531
  runId,
2380
2532
  input,
@@ -2419,15 +2571,31 @@ ${body}
2419
2571
  this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
2420
2572
  }
2421
2573
  }
2422
- /** End the run's stream and bookkeeping for how its execution ended. */
2423
- async settle(runId, input, outcome, durationMs) {
2574
+ /**
2575
+ * What the run spent: the runner's sum of its milestones, or what the turn this process follows
2576
+ * saw (which starts from that sum) when it saw more — a run that failed or was cancelled between
2577
+ * two milestones.
2578
+ */
2579
+ spentBy(runId, spent) {
2580
+ const seen = this.live.get(runId)?.turn.totalUsage();
2581
+ if (spent === void 0) return seen ?? emptyUsage();
2582
+ if (seen === void 0) return spent;
2583
+ const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
2584
+ return tokens(seen) > tokens(spent) ? seen : spent;
2585
+ }
2586
+ /**
2587
+ * End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
2588
+ * (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
2589
+ */
2590
+ async settle(runId, input, outcome, durationMs, spent) {
2424
2591
  if (outcome.status === "failed") {
2425
- await this.settleFailed(runId, input, outcome.error, durationMs);
2592
+ await this.settleFailed(runId, input, outcome.error, durationMs, spent);
2426
2593
  return;
2427
2594
  }
2595
+ const usage = this.spentBy(runId, spent);
2428
2596
  this.forget(runId);
2429
2597
  const writer = await this.sink.open(runId);
2430
- const result = await this.runResult(runId, input, outcome, durationMs);
2598
+ const result = await this.runResult(runId, input, outcome, durationMs, usage);
2431
2599
  await this.amend(writer, result);
2432
2600
  if (outcome.status === "interrupted") {
2433
2601
  this.logger.log(`agent run ${runId} cancelled`);
@@ -2455,13 +2623,14 @@ ${body}
2455
2623
  }
2456
2624
  await this.settled(result);
2457
2625
  }
2458
- async settleFailed(runId, input, error, durationMs = 0) {
2626
+ async settleFailed(runId, input, error, durationMs = 0, spent) {
2627
+ const usage = this.spentBy(runId, spent);
2459
2628
  this.forget(runId);
2460
2629
  const writer = await this.sink.open(runId);
2461
2630
  const result = await this.runResult(runId, input, {
2462
2631
  status: "failed",
2463
2632
  error
2464
- }, durationMs);
2633
+ }, durationMs, usage);
2465
2634
  const worded = (await this.amend(writer, result)).error;
2466
2635
  const message = worded ?? streamFailure(new Error(error)).message;
2467
2636
  this.logger.error(`agent run ${runId} failed: ${error}`);
@@ -2531,6 +2700,10 @@ OpenCodeTurns = _ts_decorate2([
2531
2700
  _ts_param2(9, Inject2(AGENT_CHAT_QUEUE)),
2532
2701
  _ts_param2(10, Optional()),
2533
2702
  _ts_param2(10, Inject2(OPENCODE_TOOLS_TOKENS)),
2703
+ _ts_param2(11, Optional()),
2704
+ _ts_param2(11, Inject2(AGENT_PRICING_STORE)),
2705
+ _ts_param2(12, Optional()),
2706
+ _ts_param2(12, Inject2(AGENT_OPTIONS)),
2534
2707
  _ts_metadata2("design:type", Function),
2535
2708
  _ts_metadata2("design:paramtypes", [
2536
2709
  typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
@@ -2543,6 +2716,8 @@ OpenCodeTurns = _ts_decorate2([
2543
2716
  typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
2544
2717
  typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
2545
2718
  typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
2719
+ Object,
2720
+ typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
2546
2721
  Object
2547
2722
  ])
2548
2723
  ], OpenCodeTurns);
@@ -2585,6 +2760,19 @@ function uiEmitter(scope, push) {
2585
2760
  };
2586
2761
  }
2587
2762
  __name(uiEmitter, "uiEmitter");
2763
+ function priceRef(model) {
2764
+ const aliases = [
2765
+ model.id
2766
+ ];
2767
+ const tail = model.id.slice(model.id.lastIndexOf("/") + 1);
2768
+ if (tail !== model.id && parseBedrockModelId(tail) !== void 0) aliases.push(tail);
2769
+ return {
2770
+ modelId: `${model.providerID}/${model.id}`,
2771
+ provider: model.providerID,
2772
+ aliases
2773
+ };
2774
+ }
2775
+ __name(priceRef, "priceRef");
2588
2776
 
2589
2777
  // src/runner.ts
2590
2778
  function _ts_decorate3(decorators, target, key, desc) {
@@ -2686,18 +2874,20 @@ var OpenCodeAgentRunner = class {
2686
2874
  async run(runId, input) {
2687
2875
  const started = Date.now();
2688
2876
  this.inputs.set(runId, input);
2877
+ let spent = emptyUsage();
2689
2878
  try {
2690
2879
  let handle = await this.turns.begin(runId, input);
2691
2880
  if (this.cancelled.has(runId)) throw new RunCancelledError();
2692
- await this.turns.prompt(runId, input, handle);
2881
+ await this.turns.prompt(runId, input, handle, spent);
2693
2882
  for (; ; ) {
2694
- const milestone = await this.turns.observe(runId, input, handle);
2883
+ const milestone = await this.turns.observe(runId, input, handle, spent);
2884
+ spent = addUsage(spent, milestone.usage);
2695
2885
  if (milestone.kind === "finished") {
2696
- await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
2886
+ await this.turns.settle(runId, input, milestone.outcome, Date.now() - started, spent);
2697
2887
  return;
2698
2888
  }
2699
2889
  const reply = await this.park(runId, milestone.ask.id, milestone.ask.kind === "approval" ? "approval" : "answers", milestone.timeoutMs);
2700
- handle = await this.turns.reply(runId, input, handle, milestone.ask, reply);
2890
+ handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
2701
2891
  }
2702
2892
  } catch (error) {
2703
2893
  if (error instanceof RunCancelledError) {
@@ -2707,7 +2897,7 @@ var OpenCodeAgentRunner = class {
2707
2897
  }, Date.now() - started);
2708
2898
  return;
2709
2899
  }
2710
- await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"));
2900
+ await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
2711
2901
  } finally {
2712
2902
  this.inputs.delete(runId);
2713
2903
  }
@@ -2879,6 +3069,7 @@ var OpenCodeRunWorkflow = class {
2879
3069
  this.runs = runs;
2880
3070
  }
2881
3071
  async run(ctx, input) {
3072
+ let spent = emptyUsage();
2882
3073
  try {
2883
3074
  const begun = await ctx.localStep("begin", async () => ({
2884
3075
  handle: await this.turns.begin(ctx.runId, input),
@@ -2886,15 +3077,18 @@ var OpenCodeRunWorkflow = class {
2886
3077
  }));
2887
3078
  let handle = begun.handle;
2888
3079
  await ctx.localStep("prompt", async () => {
2889
- await this.turns.prompt(ctx.runId, input, handle);
3080
+ await this.turns.prompt(ctx.runId, input, handle, spent);
2890
3081
  return true;
2891
3082
  });
2892
3083
  for (let n = 0; ; n += 1) {
2893
- const milestone = await ctx.localStep(`observe:${n}`, () => this.turns.observe(ctx.runId, input, handle));
3084
+ const before = spent;
3085
+ const milestone = await ctx.localStep(`observe:${n}`, () => this.turns.observe(ctx.runId, input, handle, before));
3086
+ spent = addUsage(spent, milestone.usage);
3087
+ const total = spent;
2894
3088
  if (milestone.kind === "finished") {
2895
3089
  await ctx.localStep("finish", async () => {
2896
3090
  if (await this.cancelled(ctx.runId)) this.turns.drop(ctx.runId);
2897
- else await this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt);
3091
+ else await this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt, total);
2898
3092
  return true;
2899
3093
  });
2900
3094
  return {
@@ -2914,12 +3108,12 @@ var OpenCodeRunWorkflow = class {
2914
3108
  };
2915
3109
  }
2916
3110
  const ask = milestone.ask;
2917
- handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply));
3111
+ handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply, total));
2918
3112
  }
2919
3113
  } catch (error) {
2920
3114
  if (isWorkflowControlFlowSignal(error)) throw error;
2921
3115
  await ctx.localStep("fail", async () => {
2922
- await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"));
3116
+ await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"), 0, spent);
2923
3117
  return true;
2924
3118
  });
2925
3119
  return {