@dudousxd/nestjs-agent-opencode 0.1.2 → 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.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as OpenCodeClient, e as OpenCodeEvent, f as OpenCodeForm, g as OpenCodeFormValue, h as OpenCodeFormField, i as OpenCodeMcpEndpoint, O as OpenCodeTurns, a as OpenCodeEngineSettings } from './engine-Bij6AdoF.cjs';
2
- export { I as InMemoryOpenCodeSessionStore, M as Milestone, j as OpenCodeAmendment, k as OpenCodeCallContext, c as OpenCodeEngineOptions, b as OpenCodeHost, l as OpenCodeKeyValue, m as OpenCodeModelRef, n as OpenCodePermissionRequest, o as OpenCodePermissionRule, p as OpenCodeReplyMismatchError, q as OpenCodeRunResult, r as OpenCodeServer, s as OpenCodeSessionCreate, t as OpenCodeSessionRef, u as OpenCodeSessionStore, v as OpenCodeToolCall, w as OpenCodeToolRefusedError, x as OpenCodeToolsClaims, y as OpenCodeToolsOptions, z as OpenCodeToolsTokens, A as OpenCodeTurn, B as OpenCodeTurnContext, P as PendingAsk, S as SessionHandle, T as TurnOutcome, C as keyValueOpenCodeSessionStore, D as openCode, E as openCodeControllers, F as openCodeProviders } from './engine-Bij6AdoF.cjs';
1
+ import { d as OpenCodeClient, e as OpenCodeEvent, f as OpenCodeForm, g as OpenCodeFormValue, h as OpenCodeFormField, i as OpenCodeMcpEndpoint, O as OpenCodeTurns, a as OpenCodeEngineSettings } from './engine-JnBbcRxh.cjs';
2
+ export { I as InMemoryOpenCodeSessionStore, M as Milestone, j as OpenCodeAmendment, k as OpenCodeCallContext, c as OpenCodeEngineOptions, b as OpenCodeHost, l as OpenCodeKeyValue, m as OpenCodeModelRef, n as OpenCodePermissionRequest, o as OpenCodePermissionRule, p as OpenCodeReplyMismatchError, q as OpenCodeRunResult, r as OpenCodeServer, s as OpenCodeSessionCreate, t as OpenCodeSessionRef, u as OpenCodeSessionStore, v as OpenCodeToolCall, w as OpenCodeToolRefusedError, x as OpenCodeToolsClaims, y as OpenCodeToolsOptions, z as OpenCodeToolsTokens, A as OpenCodeTurn, B as OpenCodeTurnContext, C as OpenCodeUsage, P as PendingAsk, S as SessionHandle, D as StepCost, E as StepModel, T as TurnOutcome, F as addUsage, G as emptyUsage, H as keyValueOpenCodeSessionStore, J as openCode, K as openCodeControllers, L as openCodeProviders, N as usageOf } from './engine-JnBbcRxh.cjs';
3
3
  import { ElicitationRequest, ElicitationQuestion, AgentRunner, AgentRunInput, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
4
4
  import { IncomingMessage, ServerResponse } from 'node:http';
5
5
  import '@dudousxd/nestjs-agent';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as OpenCodeClient, e as OpenCodeEvent, f as OpenCodeForm, g as OpenCodeFormValue, h as OpenCodeFormField, i as OpenCodeMcpEndpoint, O as OpenCodeTurns, a as OpenCodeEngineSettings } from './engine-Bij6AdoF.js';
2
- export { I as InMemoryOpenCodeSessionStore, M as Milestone, j as OpenCodeAmendment, k as OpenCodeCallContext, c as OpenCodeEngineOptions, b as OpenCodeHost, l as OpenCodeKeyValue, m as OpenCodeModelRef, n as OpenCodePermissionRequest, o as OpenCodePermissionRule, p as OpenCodeReplyMismatchError, q as OpenCodeRunResult, r as OpenCodeServer, s as OpenCodeSessionCreate, t as OpenCodeSessionRef, u as OpenCodeSessionStore, v as OpenCodeToolCall, w as OpenCodeToolRefusedError, x as OpenCodeToolsClaims, y as OpenCodeToolsOptions, z as OpenCodeToolsTokens, A as OpenCodeTurn, B as OpenCodeTurnContext, P as PendingAsk, S as SessionHandle, T as TurnOutcome, C as keyValueOpenCodeSessionStore, D as openCode, E as openCodeControllers, F as openCodeProviders } from './engine-Bij6AdoF.js';
1
+ import { d as OpenCodeClient, e as OpenCodeEvent, f as OpenCodeForm, g as OpenCodeFormValue, h as OpenCodeFormField, i as OpenCodeMcpEndpoint, O as OpenCodeTurns, a as OpenCodeEngineSettings } from './engine-JnBbcRxh.js';
2
+ export { I as InMemoryOpenCodeSessionStore, M as Milestone, j as OpenCodeAmendment, k as OpenCodeCallContext, c as OpenCodeEngineOptions, b as OpenCodeHost, l as OpenCodeKeyValue, m as OpenCodeModelRef, n as OpenCodePermissionRequest, o as OpenCodePermissionRule, p as OpenCodeReplyMismatchError, q as OpenCodeRunResult, r as OpenCodeServer, s as OpenCodeSessionCreate, t as OpenCodeSessionRef, u as OpenCodeSessionStore, v as OpenCodeToolCall, w as OpenCodeToolRefusedError, x as OpenCodeToolsClaims, y as OpenCodeToolsOptions, z as OpenCodeToolsTokens, A as OpenCodeTurn, B as OpenCodeTurnContext, C as OpenCodeUsage, P as PendingAsk, S as SessionHandle, D as StepCost, E as StepModel, T as TurnOutcome, F as addUsage, G as emptyUsage, H as keyValueOpenCodeSessionStore, J as openCode, K as openCodeControllers, L as openCodeProviders, N as usageOf } from './engine-JnBbcRxh.js';
3
3
  import { ElicitationRequest, ElicitationQuestion, AgentRunner, AgentRunInput, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
4
4
  import { IncomingMessage, ServerResponse } from 'node:http';
5
5
  import '@dudousxd/nestjs-agent';
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,11 @@ var OpenCodeTurn = class {
304
339
  hidden = /* @__PURE__ */ new Set();
305
340
  milestones = [];
306
341
  waiting;
307
- usage = {
308
- inputTokens: 0,
309
- outputTokens: 0
310
- };
311
- costUsd;
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;
312
347
  stepOpen = false;
313
348
  sawText = false;
314
349
  separator = false;
@@ -318,6 +353,11 @@ var OpenCodeTurn = class {
318
353
  stepError;
319
354
  constructor(a) {
320
355
  this.a = a;
356
+ this.model = modelOfLabel(a.input.model);
357
+ }
358
+ /** What the run spent so far: carried from before this turn object, plus what it saw. */
359
+ totalUsage() {
360
+ return addUsage(this.a.carried, this.spent);
321
361
  }
322
362
  get sessionId() {
323
363
  return this.a.sessionId;
@@ -400,7 +440,15 @@ var OpenCodeTurn = class {
400
440
  this.chain = this.chain.then(() => ask.kind === "approval" ? this.decided(ask, reply, tell) : this.answered(ask, reply, tell)).then(resolve, reject);
401
441
  });
402
442
  }
403
- reach(milestone) {
443
+ reach(reached) {
444
+ const usage = subUsage(this.spent, this.reported);
445
+ this.reported = {
446
+ ...this.spent
447
+ };
448
+ const milestone = {
449
+ ...reached,
450
+ usage
451
+ };
404
452
  const waiting = this.waiting;
405
453
  if (waiting !== void 0) {
406
454
  this.waiting = void 0;
@@ -556,9 +604,22 @@ var OpenCodeTurn = class {
556
604
  }
557
605
  }
558
606
  async onEvent(e) {
559
- if (this.ended) return;
560
607
  const d = e.data ?? {};
608
+ if (e.type === "session.usage.recorded") {
609
+ await this.usageRecorded(d);
610
+ return;
611
+ }
612
+ if (this.ended) return;
561
613
  switch (e.type) {
614
+ case "session.step.started":
615
+ case "session.model.selected": {
616
+ const model = d.model;
617
+ if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
618
+ providerID: model.providerID,
619
+ id: model.id
620
+ };
621
+ return;
622
+ }
562
623
  case "session.text.delta": {
563
624
  const delta = typeof d.delta === "string" ? d.delta : "";
564
625
  if (!delta) return;
@@ -712,42 +773,63 @@ ${delta}` : delta;
712
773
  });
713
774
  }
714
775
  }
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;
776
+ modelId() {
777
+ return this.model !== void 0 ? `${this.model.providerID}/${this.model.id}` : this.a.modelLabel;
778
+ }
779
+ /** One model call's usage and cost, into the ledger and the run's total. */
780
+ async spend(d, purpose) {
781
+ const usage = usageOf(d.tokens);
782
+ const reportedCostUsd = typeof d.cost === "number" && Number.isFinite(d.cost) && d.cost >= 0 ? d.cost : void 0;
783
+ const fallback = reportedCostUsd !== void 0 ? {
784
+ costUsd: reportedCostUsd,
785
+ costSource: "provider"
786
+ } : {};
787
+ let cost = fallback;
788
+ if (this.a.priceStep !== void 0) {
789
+ cost = await this.a.priceStep({
790
+ model: this.model,
791
+ usage,
792
+ reportedCostUsd
793
+ }).catch(() => fallback);
794
+ }
795
+ this.spent = addUsage(this.spent, {
796
+ inputTokens: usage.inputTokens,
797
+ outputTokens: usage.outputTokens,
798
+ cacheReadTokens: usage.cacheReadTokens ?? 0,
799
+ cacheWriteTokens: usage.cacheWriteTokens ?? 0,
800
+ reasoningTokens: usage.reasoningTokens ?? 0,
801
+ costUsd: cost.costUsd ?? 0
802
+ });
803
+ if (cost.costUsd !== void 0) this.priced = true;
730
804
  await this.a.store.recordUsage({
731
805
  threadId: this.a.input.threadId,
732
806
  actorRef: this.a.input.actor.id,
733
- modelId: this.a.modelLabel,
734
- purpose: "chat",
807
+ modelId: this.modelId(),
808
+ purpose,
735
809
  usage,
736
- ...costUsd !== void 0 ? {
737
- costUsd,
738
- costSource: "provider"
739
- } : {}
810
+ ...cost
740
811
  });
812
+ return {
813
+ usage,
814
+ cost
815
+ };
816
+ }
817
+ /** `session.usage.recorded`: a call OpenCode made besides the steps (the title, a compaction). */
818
+ async usageRecorded(d) {
819
+ await this.spend(d, d.source === "title" ? "title" : "history_summary");
820
+ }
821
+ async stepEnded(d) {
822
+ const { usage, cost } = await this.spend(d, "chat");
741
823
  if (!this.stepOpen) return;
742
824
  this.stepOpen = false;
743
825
  await this.write({
744
826
  kind: "step-finish",
745
827
  usage,
746
- costUsd: costUsd ?? null,
828
+ costUsd: cost.costUsd ?? null,
747
829
  ...this.segment.reasoningMs > 0 ? {
748
830
  reasoningMs: this.segment.reasoningMs
749
831
  } : {},
750
- model: this.a.modelLabel
832
+ model: this.modelId()
751
833
  });
752
834
  }
753
835
  /**
@@ -766,12 +848,7 @@ ${delta}` : delta;
766
848
  content: s.text,
767
849
  runId: this.a.runId,
768
850
  ...final ? {
769
- usage: {
770
- ...this.usage,
771
- ...this.costUsd !== void 0 ? {
772
- costUsd: this.costUsd
773
- } : {}
774
- }
851
+ usage: this.messageUsage()
775
852
  } : {},
776
853
  ...this.a.input.agentName !== void 0 ? {
777
854
  agentName: this.a.input.agentName
@@ -799,6 +876,26 @@ ${delta}` : delta;
799
876
  ]);
800
877
  return message.id;
801
878
  }
879
+ /** The run's usage as its last message carries it: the whole run, cache tokens included. */
880
+ messageUsage() {
881
+ const total = this.totalUsage();
882
+ return {
883
+ inputTokens: total.inputTokens,
884
+ outputTokens: total.outputTokens,
885
+ ...total.cacheReadTokens > 0 ? {
886
+ cacheReadTokens: total.cacheReadTokens
887
+ } : {},
888
+ ...total.cacheWriteTokens > 0 ? {
889
+ cacheWriteTokens: total.cacheWriteTokens
890
+ } : {},
891
+ ...total.reasoningTokens > 0 ? {
892
+ reasoningTokens: total.reasoningTokens
893
+ } : {},
894
+ ...this.priced || (this.a.carried?.costUsd ?? 0) > 0 ? {
895
+ costUsd: total.costUsd
896
+ } : {}
897
+ };
898
+ }
802
899
  /** A request recorded by an earlier process (a turn resumed after a restart). */
803
900
  async alreadyRecorded(id) {
804
901
  return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
@@ -1168,6 +1265,35 @@ ${delta}` : delta;
1168
1265
  });
1169
1266
  }
1170
1267
  };
1268
+ function modelOfLabel(label) {
1269
+ const slash = label?.indexOf("/") ?? -1;
1270
+ if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
1271
+ return {
1272
+ providerID: label.slice(0, slash),
1273
+ id: label.slice(slash + 1)
1274
+ };
1275
+ }
1276
+ __name(modelOfLabel, "modelOfLabel");
1277
+ function usageOf(tokens) {
1278
+ const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
1279
+ const read = n(tokens?.cache?.read);
1280
+ const write = n(tokens?.cache?.write);
1281
+ const reasoning = n(tokens?.reasoning);
1282
+ return {
1283
+ inputTokens: n(tokens?.input) + read + write,
1284
+ outputTokens: n(tokens?.output),
1285
+ ...read > 0 ? {
1286
+ cacheReadTokens: read
1287
+ } : {},
1288
+ ...write > 0 ? {
1289
+ cacheWriteTokens: write
1290
+ } : {},
1291
+ ...reasoning > 0 ? {
1292
+ reasoningTokens: reasoning
1293
+ } : {}
1294
+ };
1295
+ }
1296
+ __name(usageOf, "usageOf");
1171
1297
 
1172
1298
  // src/mcp.ts
1173
1299
  var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
@@ -1432,7 +1558,7 @@ import { Inject as Inject3, Injectable as Injectable2 } from "@nestjs/common";
1432
1558
  // src/turns.ts
1433
1559
  import { randomUUID } from "crypto";
1434
1560
  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";
1561
+ 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
1562
  import { Inject as Inject2, Injectable, Logger as Logger2, Optional } from "@nestjs/common";
1437
1563
 
1438
1564
  // src/event-hub.ts
@@ -1599,7 +1725,7 @@ var OpenCodeTurns = class {
1599
1725
  spent = /* @__PURE__ */ new Set();
1600
1726
  /** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
1601
1727
  toolsIssued = /* @__PURE__ */ new Map();
1602
- constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2) {
1728
+ constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
1603
1729
  this.host = host;
1604
1730
  this.sessions = sessions;
1605
1731
  this.settings = settings;
@@ -1611,6 +1737,30 @@ var OpenCodeTurns = class {
1611
1737
  this.memory = memory;
1612
1738
  this.queue = queue;
1613
1739
  this.toolsTokens = toolsTokens2;
1740
+ this.prices = new ModelPriceResolver({
1741
+ pricingStore,
1742
+ catalog: agentOptions?.priceCatalog,
1743
+ log: {
1744
+ info: /* @__PURE__ */ __name((message) => this.logger.log(message), "info"),
1745
+ warn: /* @__PURE__ */ __name((message) => this.logger.warn(message), "warn")
1746
+ }
1747
+ });
1748
+ }
1749
+ prices;
1750
+ /**
1751
+ * What a model call OpenCode made cost, by the same rule the library's loop records its own steps
1752
+ * at: a reported figure wins, else an estimate off the model's price — see
1753
+ * {@link OpenCodeEngineSettings.cost}.
1754
+ */
1755
+ async priceStep(step) {
1756
+ const { model, usage, reportedCostUsd } = step;
1757
+ const tokens = usage.inputTokens + usage.outputTokens;
1758
+ const reported = reportedCostUsd !== void 0 && (reportedCostUsd > 0 || tokens === 0);
1759
+ if (reported && this.settings.cost !== "estimate") {
1760
+ return resolveUsageCost(usage, reportedCostUsd, void 0);
1761
+ }
1762
+ const price = model !== void 0 && tokens > 0 ? await this.prices.priceFor(priceRef(model)) : void 0;
1763
+ return resolveUsageCost(usage, price === void 0 ? reportedCostUsd : void 0, price);
1614
1764
  }
1615
1765
  /** The tools server's name in OpenCode. */
1616
1766
  get toolsServer() {
@@ -2200,8 +2350,8 @@ ${body}
2200
2350
  }
2201
2351
  // ---- prompt / observe / reply --------------------------------------------------------------
2202
2352
  /** 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);
2353
+ async prompt(runId, input, handle, spent) {
2354
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2205
2355
  try {
2206
2356
  const prompt = await this.host.promptFor?.({
2207
2357
  input,
@@ -2231,9 +2381,12 @@ ${body}
2231
2381
  * Wait for the turn's next milestone. In a process that was not following the turn (a resumed
2232
2382
  * durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
2233
2383
  * `session.wait` is the safety net for a terminal event that was missed.
2384
+ *
2385
+ * `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
2386
+ * rebuilt here starts from it, so the answer it persists carries the whole run's usage.
2234
2387
  */
2235
- async observe(runId, input, handle) {
2236
- const live = await this.ensureLive(runId, input, handle, true);
2388
+ async observe(runId, input, handle, spent) {
2389
+ const live = await this.ensureLive(runId, input, handle, true, spent);
2237
2390
  const { turn, client } = live;
2238
2391
  if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
2239
2392
  let done = false;
@@ -2272,23 +2425,23 @@ ${body}
2272
2425
  * request is gone with it: a new session is opened, told what the person decided, and prompted
2273
2426
  * to go on.
2274
2427
  */
2275
- async reply(runId, input, handle, ask, reply) {
2428
+ async reply(runId, input, handle, ask, reply, spent) {
2276
2429
  const server = await this.host.server(input.actor);
2277
2430
  if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
2278
- return this.continueAfterRestart(runId, input, server, ask, reply);
2431
+ return this.continueAfterRestart(runId, input, server, ask, reply, spent);
2279
2432
  }
2280
- const live = await this.ensureLive(runId, input, handle, false);
2433
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2281
2434
  await live.turn.decide(ask, reply);
2282
2435
  return handle;
2283
2436
  }
2284
- async continueAfterRestart(runId, input, server, ask, reply) {
2437
+ async continueAfterRestart(runId, input, server, ask, reply, spent) {
2285
2438
  const approved = "approved" in reply && reply.approved === true;
2286
2439
  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
2440
  const thread = await this.store.getThread(input.threadId);
2288
2441
  const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
2289
2442
  await this.refreshInstructions(runId, input, server.client, handle.sessionId);
2290
2443
  this.drop(runId);
2291
- const live = await this.ensureLive(runId, input, handle, false);
2444
+ const live = await this.ensureLive(runId, input, handle, false, spent);
2292
2445
  await live.turn.decide(ask, reply, {
2293
2446
  tellOpenCode: false
2294
2447
  });
@@ -2308,7 +2461,7 @@ ${body}
2308
2461
  sessionID: known.sessionId
2309
2462
  });
2310
2463
  }
2311
- async ensureLive(runId, input, handle, catchUp) {
2464
+ async ensureLive(runId, input, handle, catchUp, spent) {
2312
2465
  const current = this.live.get(runId);
2313
2466
  if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
2314
2467
  current?.stop();
@@ -2334,6 +2487,10 @@ ${body}
2334
2487
  } : {}
2335
2488
  }), "approvalFor"),
2336
2489
  modelLabel: input.model ?? "opencode",
2490
+ priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
2491
+ ...spent !== void 0 ? {
2492
+ carried: spent
2493
+ } : {},
2337
2494
  logger: this.logger,
2338
2495
  onGranted: /* @__PURE__ */ __name((action, id) => {
2339
2496
  const grants = this.grants.get(runId) ?? [];
@@ -2380,20 +2537,14 @@ ${body}
2380
2537
  }
2381
2538
  return milestone;
2382
2539
  }
2383
- /** What the run produced, as the host's settle hooks see it. */
2384
- async runResult(runId, input, outcome, durationMs) {
2540
+ /**
2541
+ * What the run produced, as the host's settle hooks see it. `usage` is what the run spent — the
2542
+ * runner's sum of its milestones' usage, which a resumed run reads back from its journal — else
2543
+ * what the turn this process follows saw.
2544
+ */
2545
+ async runResult(runId, input, outcome, durationMs, usage) {
2385
2546
  const thread = await this.store.getThread(input.threadId);
2386
2547
  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
2548
  return {
2398
2549
  runId,
2399
2550
  input,
@@ -2438,15 +2589,31 @@ ${body}
2438
2589
  this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
2439
2590
  }
2440
2591
  }
2441
- /** End the run's stream and bookkeeping for how its execution ended. */
2442
- async settle(runId, input, outcome, durationMs) {
2592
+ /**
2593
+ * What the run spent: the runner's sum of its milestones, or what the turn this process follows
2594
+ * saw (which starts from that sum) when it saw more — a run that failed or was cancelled between
2595
+ * two milestones.
2596
+ */
2597
+ spentBy(runId, spent) {
2598
+ const seen = this.live.get(runId)?.turn.totalUsage();
2599
+ if (spent === void 0) return seen ?? emptyUsage();
2600
+ if (seen === void 0) return spent;
2601
+ const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
2602
+ return tokens(seen) > tokens(spent) ? seen : spent;
2603
+ }
2604
+ /**
2605
+ * End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
2606
+ * (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
2607
+ */
2608
+ async settle(runId, input, outcome, durationMs, spent) {
2443
2609
  if (outcome.status === "failed") {
2444
- await this.settleFailed(runId, input, outcome.error, durationMs);
2610
+ await this.settleFailed(runId, input, outcome.error, durationMs, spent);
2445
2611
  return;
2446
2612
  }
2613
+ const usage = this.spentBy(runId, spent);
2447
2614
  this.forget(runId);
2448
2615
  const writer = await this.sink.open(runId);
2449
- const result = await this.runResult(runId, input, outcome, durationMs);
2616
+ const result = await this.runResult(runId, input, outcome, durationMs, usage);
2450
2617
  await this.amend(writer, result);
2451
2618
  if (outcome.status === "interrupted") {
2452
2619
  this.logger.log(`agent run ${runId} cancelled`);
@@ -2474,13 +2641,14 @@ ${body}
2474
2641
  }
2475
2642
  await this.settled(result);
2476
2643
  }
2477
- async settleFailed(runId, input, error, durationMs = 0) {
2644
+ async settleFailed(runId, input, error, durationMs = 0, spent) {
2645
+ const usage = this.spentBy(runId, spent);
2478
2646
  this.forget(runId);
2479
2647
  const writer = await this.sink.open(runId);
2480
2648
  const result = await this.runResult(runId, input, {
2481
2649
  status: "failed",
2482
2650
  error
2483
- }, durationMs);
2651
+ }, durationMs, usage);
2484
2652
  const worded = (await this.amend(writer, result)).error;
2485
2653
  const message = worded ?? streamFailure(new Error(error)).message;
2486
2654
  this.logger.error(`agent run ${runId} failed: ${error}`);
@@ -2550,6 +2718,10 @@ OpenCodeTurns = _ts_decorate2([
2550
2718
  _ts_param2(9, Inject2(AGENT_CHAT_QUEUE)),
2551
2719
  _ts_param2(10, Optional()),
2552
2720
  _ts_param2(10, Inject2(OPENCODE_TOOLS_TOKENS)),
2721
+ _ts_param2(11, Optional()),
2722
+ _ts_param2(11, Inject2(AGENT_PRICING_STORE)),
2723
+ _ts_param2(12, Optional()),
2724
+ _ts_param2(12, Inject2(AGENT_OPTIONS)),
2553
2725
  _ts_metadata2("design:type", Function),
2554
2726
  _ts_metadata2("design:paramtypes", [
2555
2727
  typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
@@ -2562,6 +2734,8 @@ OpenCodeTurns = _ts_decorate2([
2562
2734
  typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
2563
2735
  typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
2564
2736
  typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
2737
+ Object,
2738
+ typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
2565
2739
  Object
2566
2740
  ])
2567
2741
  ], OpenCodeTurns);
@@ -2604,6 +2778,19 @@ function uiEmitter(scope, push) {
2604
2778
  };
2605
2779
  }
2606
2780
  __name(uiEmitter, "uiEmitter");
2781
+ function priceRef(model) {
2782
+ const aliases = [
2783
+ model.id
2784
+ ];
2785
+ const tail = model.id.slice(model.id.lastIndexOf("/") + 1);
2786
+ if (tail !== model.id && parseBedrockModelId(tail) !== void 0) aliases.push(tail);
2787
+ return {
2788
+ modelId: `${model.providerID}/${model.id}`,
2789
+ provider: model.providerID,
2790
+ aliases
2791
+ };
2792
+ }
2793
+ __name(priceRef, "priceRef");
2607
2794
 
2608
2795
  // src/runner.ts
2609
2796
  function _ts_decorate3(decorators, target, key, desc) {
@@ -2705,18 +2892,20 @@ var OpenCodeAgentRunner = class {
2705
2892
  async run(runId, input) {
2706
2893
  const started = Date.now();
2707
2894
  this.inputs.set(runId, input);
2895
+ let spent = emptyUsage();
2708
2896
  try {
2709
2897
  let handle = await this.turns.begin(runId, input);
2710
2898
  if (this.cancelled.has(runId)) throw new RunCancelledError();
2711
- await this.turns.prompt(runId, input, handle);
2899
+ await this.turns.prompt(runId, input, handle, spent);
2712
2900
  for (; ; ) {
2713
- const milestone = await this.turns.observe(runId, input, handle);
2901
+ const milestone = await this.turns.observe(runId, input, handle, spent);
2902
+ spent = addUsage(spent, milestone.usage);
2714
2903
  if (milestone.kind === "finished") {
2715
- await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
2904
+ await this.turns.settle(runId, input, milestone.outcome, Date.now() - started, spent);
2716
2905
  return;
2717
2906
  }
2718
2907
  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);
2908
+ handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
2720
2909
  }
2721
2910
  } catch (error) {
2722
2911
  if (error instanceof RunCancelledError) {
@@ -2726,7 +2915,7 @@ var OpenCodeAgentRunner = class {
2726
2915
  }, Date.now() - started);
2727
2916
  return;
2728
2917
  }
2729
- await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"));
2918
+ await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
2730
2919
  } finally {
2731
2920
  this.inputs.delete(runId);
2732
2921
  }
@@ -2878,6 +3067,8 @@ export {
2878
3067
  OpenCodeToolsTokens,
2879
3068
  OpenCodeTurn,
2880
3069
  OpenCodeTurns,
3070
+ addUsage,
3071
+ emptyUsage,
2881
3072
  keyValueOpenCodeSessionStore,
2882
3073
  openCode,
2883
3074
  openCodeControllers,
@@ -2885,6 +3076,7 @@ export {
2885
3076
  sessionOf,
2886
3077
  toElicitation,
2887
3078
  toFormAnswer,
2888
- toQuestion
3079
+ toQuestion,
3080
+ usageOf
2889
3081
  };
2890
3082
  //# sourceMappingURL=index.js.map