@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.
- package/README.md +29 -1
- package/dist/durable/index.cjs +268 -74
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +1 -1
- package/dist/durable/index.d.ts +1 -1
- package/dist/durable/index.js +269 -75
- package/dist/durable/index.js.map +1 -1
- package/dist/{engine-Bij6AdoF.d.cts → engine-JnBbcRxh.d.cts} +123 -19
- package/dist/{engine-Bij6AdoF.d.ts → engine-JnBbcRxh.d.ts} +123 -19
- package/dist/index.cjs +267 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +264 -71
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -53,7 +53,8 @@ export class AppModule {}
|
|
|
53
53
|
| OpenCode | The library's protocol and store |
|
|
54
54
|
| --- | --- |
|
|
55
55
|
| `session.text.delta` / `session.reasoning.delta` | `text` / `reasoning` |
|
|
56
|
-
| a step (`session.step.ended`) | `step-start` … `step-finish` with usage; usage
|
|
56
|
+
| a step (`session.step.ended`) | `step-start` … `step-finish` with usage and cost; a `chat` usage row per step (see [Cost and usage](#cost-and-usage)) |
|
|
57
|
+
| `session.usage.recorded` (the title, a compaction) | a `title` / `history_summary` usage row |
|
|
57
58
|
| tools (`session.tool.*`) | `tool-input-*` / `tool-output*`, code-mode inner calls nested by `parentId` |
|
|
58
59
|
| `permission.asked` | an `action` call + `approval-requested`, recorded `pending_approval`; `approve` / `reject` → `permission.reply` |
|
|
59
60
|
| `form.created` | `elicitation`; `answer` / `skip` → `session.form.reply` / `cancel` |
|
|
@@ -64,6 +65,33 @@ export class AppModule {}
|
|
|
64
65
|
Sessions: one per thread, kept by an `OpenCodeSessionStore` (in memory by default; persist it for
|
|
65
66
|
several replicas) and recreated when the server's `bootId` changes, told the conversation so far.
|
|
66
67
|
|
|
68
|
+
## Cost and usage
|
|
69
|
+
|
|
70
|
+
Every model call OpenCode makes for a turn — each step, the title it gives the session, a
|
|
71
|
+
compaction — goes to the same token-usage ledger the library's own loop writes (`recordUsage`:
|
|
72
|
+
`agent_token_usage` with `cost_usd` and `cost_source`), so the ledger quota (`quota: { limits }`),
|
|
73
|
+
the dashboard and `GET /quota` see an OpenCode turn like any other.
|
|
74
|
+
|
|
75
|
+
- **Tokens.** OpenCode reports the uncached input in `tokens.input` and the cache beside it; the row
|
|
76
|
+
counts the whole input side in `inputTokens`, with `cacheReadTokens` / `cacheWriteTokens` as
|
|
77
|
+
subsets (the library's `MessageUsage`). The row's model is the one OpenCode names for the step
|
|
78
|
+
(`<providerID>/<model id>`, e.g. `amazon-bedrock/us-gov.anthropic.claude-sonnet-4-5-20250929-v1:0`).
|
|
79
|
+
- **Cost.** OpenCode prices each call itself, off its model catalog. A call it priced (`cost > 0`) is
|
|
80
|
+
recorded at that figure, `cost_source: 'provider'`. A call it priced at 0 — no price for the model
|
|
81
|
+
in its catalog — is estimated from the library's price for the model, `cost_source: 'estimate'`:
|
|
82
|
+
the pricing store's row, else (seeded into the store once, as at boot) `priceCatalog.prices`, the
|
|
83
|
+
built-in GovCloud Bedrock table (`priceCatalog.region` / `AWS_REGION`, or a `us-gov.` id) and
|
|
84
|
+
models.dev. A model the library cannot price keeps OpenCode's 0.
|
|
85
|
+
- `openCode({ cost: 'estimate' })` prefers the library's price whenever it has one — for models
|
|
86
|
+
OpenCode would price at a list price that is not yours (models.dev lists commercial Bedrock prices,
|
|
87
|
+
so Bedrock in GovCloud is priced too low unless OpenCode's config declares the model's `cost`).
|
|
88
|
+
- **The run's total** (`OpenCodeRunResult.usage` for `beforeSettle` / `onSettled`, and the usage on
|
|
89
|
+
the turn's last message) is added up from the turn's own calls, not read back from the store: each
|
|
90
|
+
milestone carries what was spent since the last one, and a durable run journals it with the
|
|
91
|
+
milestone, so a run resumed in another process (an approval answered after a restart) still reports
|
|
92
|
+
the whole run, cache tokens included. A process that dies while OpenCode works loses the calls it
|
|
93
|
+
saw from that total (their ledger rows are already written).
|
|
94
|
+
|
|
67
95
|
## Durable turns
|
|
68
96
|
|
|
69
97
|
```ts
|
package/dist/durable/index.cjs
CHANGED
|
@@ -249,6 +249,41 @@ function toFormAnswer(form, answers = {}) {
|
|
|
249
249
|
__name(toFormAnswer, "toFormAnswer");
|
|
250
250
|
|
|
251
251
|
// src/turn.ts
|
|
252
|
+
function emptyUsage() {
|
|
253
|
+
return {
|
|
254
|
+
inputTokens: 0,
|
|
255
|
+
outputTokens: 0,
|
|
256
|
+
cacheReadTokens: 0,
|
|
257
|
+
cacheWriteTokens: 0,
|
|
258
|
+
reasoningTokens: 0,
|
|
259
|
+
costUsd: 0
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
__name(emptyUsage, "emptyUsage");
|
|
263
|
+
function addUsage(a, b) {
|
|
264
|
+
const x = a ?? emptyUsage();
|
|
265
|
+
const y = b ?? emptyUsage();
|
|
266
|
+
return {
|
|
267
|
+
inputTokens: x.inputTokens + (y.inputTokens ?? 0),
|
|
268
|
+
outputTokens: x.outputTokens + (y.outputTokens ?? 0),
|
|
269
|
+
cacheReadTokens: (x.cacheReadTokens ?? 0) + (y.cacheReadTokens ?? 0),
|
|
270
|
+
cacheWriteTokens: (x.cacheWriteTokens ?? 0) + (y.cacheWriteTokens ?? 0),
|
|
271
|
+
reasoningTokens: (x.reasoningTokens ?? 0) + (y.reasoningTokens ?? 0),
|
|
272
|
+
costUsd: x.costUsd + (y.costUsd ?? 0)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
__name(addUsage, "addUsage");
|
|
276
|
+
function subUsage(a, b) {
|
|
277
|
+
return {
|
|
278
|
+
inputTokens: a.inputTokens - b.inputTokens,
|
|
279
|
+
outputTokens: a.outputTokens - b.outputTokens,
|
|
280
|
+
cacheReadTokens: a.cacheReadTokens - b.cacheReadTokens,
|
|
281
|
+
cacheWriteTokens: a.cacheWriteTokens - b.cacheWriteTokens,
|
|
282
|
+
reasoningTokens: a.reasoningTokens - b.reasoningTokens,
|
|
283
|
+
costUsd: a.costUsd - b.costUsd
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
__name(subUsage, "subUsage");
|
|
252
287
|
var ASK_TOOL_NAME = "ask";
|
|
253
288
|
var FAILED_STATUSES = /* @__PURE__ */ new Set([
|
|
254
289
|
"error",
|
|
@@ -314,11 +349,11 @@ var OpenCodeTurn = class {
|
|
|
314
349
|
hidden = /* @__PURE__ */ new Set();
|
|
315
350
|
milestones = [];
|
|
316
351
|
waiting;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
352
|
+
/** What this turn object saw spent, and how much of it a milestone already reported. */
|
|
353
|
+
spent = emptyUsage();
|
|
354
|
+
reported = emptyUsage();
|
|
355
|
+
priced = false;
|
|
356
|
+
model;
|
|
322
357
|
stepOpen = false;
|
|
323
358
|
sawText = false;
|
|
324
359
|
separator = false;
|
|
@@ -328,6 +363,11 @@ var OpenCodeTurn = class {
|
|
|
328
363
|
stepError;
|
|
329
364
|
constructor(a) {
|
|
330
365
|
this.a = a;
|
|
366
|
+
this.model = modelOfLabel(a.input.model);
|
|
367
|
+
}
|
|
368
|
+
/** What the run spent so far: carried from before this turn object, plus what it saw. */
|
|
369
|
+
totalUsage() {
|
|
370
|
+
return addUsage(this.a.carried, this.spent);
|
|
331
371
|
}
|
|
332
372
|
get sessionId() {
|
|
333
373
|
return this.a.sessionId;
|
|
@@ -410,7 +450,15 @@ var OpenCodeTurn = class {
|
|
|
410
450
|
this.chain = this.chain.then(() => ask.kind === "approval" ? this.decided(ask, reply, tell) : this.answered(ask, reply, tell)).then(resolve, reject);
|
|
411
451
|
});
|
|
412
452
|
}
|
|
413
|
-
reach(
|
|
453
|
+
reach(reached) {
|
|
454
|
+
const usage = subUsage(this.spent, this.reported);
|
|
455
|
+
this.reported = {
|
|
456
|
+
...this.spent
|
|
457
|
+
};
|
|
458
|
+
const milestone = {
|
|
459
|
+
...reached,
|
|
460
|
+
usage
|
|
461
|
+
};
|
|
414
462
|
const waiting = this.waiting;
|
|
415
463
|
if (waiting !== void 0) {
|
|
416
464
|
this.waiting = void 0;
|
|
@@ -566,9 +614,22 @@ var OpenCodeTurn = class {
|
|
|
566
614
|
}
|
|
567
615
|
}
|
|
568
616
|
async onEvent(e) {
|
|
569
|
-
if (this.ended) return;
|
|
570
617
|
const d = e.data ?? {};
|
|
618
|
+
if (e.type === "session.usage.recorded") {
|
|
619
|
+
await this.usageRecorded(d);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (this.ended) return;
|
|
571
623
|
switch (e.type) {
|
|
624
|
+
case "session.step.started":
|
|
625
|
+
case "session.model.selected": {
|
|
626
|
+
const model = d.model;
|
|
627
|
+
if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
|
|
628
|
+
providerID: model.providerID,
|
|
629
|
+
id: model.id
|
|
630
|
+
};
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
572
633
|
case "session.text.delta": {
|
|
573
634
|
const delta = typeof d.delta === "string" ? d.delta : "";
|
|
574
635
|
if (!delta) return;
|
|
@@ -722,41 +783,63 @@ ${delta}` : delta;
|
|
|
722
783
|
});
|
|
723
784
|
}
|
|
724
785
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
};
|
|
736
|
-
|
|
737
|
-
this.
|
|
738
|
-
|
|
739
|
-
|
|
786
|
+
modelId() {
|
|
787
|
+
return this.model !== void 0 ? `${this.model.providerID}/${this.model.id}` : this.a.modelLabel;
|
|
788
|
+
}
|
|
789
|
+
/** One model call's usage and cost, into the ledger and the run's total. */
|
|
790
|
+
async spend(d, purpose) {
|
|
791
|
+
const usage = usageOf(d.tokens);
|
|
792
|
+
const reportedCostUsd = typeof d.cost === "number" && Number.isFinite(d.cost) && d.cost >= 0 ? d.cost : void 0;
|
|
793
|
+
const fallback = reportedCostUsd !== void 0 ? {
|
|
794
|
+
costUsd: reportedCostUsd,
|
|
795
|
+
costSource: "provider"
|
|
796
|
+
} : {};
|
|
797
|
+
let cost = fallback;
|
|
798
|
+
if (this.a.priceStep !== void 0) {
|
|
799
|
+
cost = await this.a.priceStep({
|
|
800
|
+
model: this.model,
|
|
801
|
+
usage,
|
|
802
|
+
reportedCostUsd
|
|
803
|
+
}).catch(() => fallback);
|
|
804
|
+
}
|
|
805
|
+
this.spent = addUsage(this.spent, {
|
|
806
|
+
inputTokens: usage.inputTokens,
|
|
807
|
+
outputTokens: usage.outputTokens,
|
|
808
|
+
cacheReadTokens: usage.cacheReadTokens ?? 0,
|
|
809
|
+
cacheWriteTokens: usage.cacheWriteTokens ?? 0,
|
|
810
|
+
reasoningTokens: usage.reasoningTokens ?? 0,
|
|
811
|
+
costUsd: cost.costUsd ?? 0
|
|
812
|
+
});
|
|
813
|
+
if (cost.costUsd !== void 0) this.priced = true;
|
|
740
814
|
await this.a.store.recordUsage({
|
|
741
815
|
threadId: this.a.input.threadId,
|
|
742
816
|
actorRef: this.a.input.actor.id,
|
|
743
|
-
modelId: this.
|
|
744
|
-
purpose
|
|
817
|
+
modelId: this.modelId(),
|
|
818
|
+
purpose,
|
|
745
819
|
usage,
|
|
746
|
-
...
|
|
747
|
-
costUsd
|
|
748
|
-
} : {}
|
|
820
|
+
...cost
|
|
749
821
|
});
|
|
822
|
+
return {
|
|
823
|
+
usage,
|
|
824
|
+
cost
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
/** `session.usage.recorded`: a call OpenCode made besides the steps (the title, a compaction). */
|
|
828
|
+
async usageRecorded(d) {
|
|
829
|
+
await this.spend(d, d.source === "title" ? "title" : "history_summary");
|
|
830
|
+
}
|
|
831
|
+
async stepEnded(d) {
|
|
832
|
+
const { usage, cost } = await this.spend(d, "chat");
|
|
750
833
|
if (!this.stepOpen) return;
|
|
751
834
|
this.stepOpen = false;
|
|
752
835
|
await this.write({
|
|
753
836
|
kind: "step-finish",
|
|
754
837
|
usage,
|
|
755
|
-
costUsd: costUsd ?? null,
|
|
838
|
+
costUsd: cost.costUsd ?? null,
|
|
756
839
|
...this.segment.reasoningMs > 0 ? {
|
|
757
840
|
reasoningMs: this.segment.reasoningMs
|
|
758
841
|
} : {},
|
|
759
|
-
model: this.
|
|
842
|
+
model: this.modelId()
|
|
760
843
|
});
|
|
761
844
|
}
|
|
762
845
|
/**
|
|
@@ -775,12 +858,7 @@ ${delta}` : delta;
|
|
|
775
858
|
content: s.text,
|
|
776
859
|
runId: this.a.runId,
|
|
777
860
|
...final ? {
|
|
778
|
-
usage:
|
|
779
|
-
...this.usage,
|
|
780
|
-
...this.costUsd !== void 0 ? {
|
|
781
|
-
costUsd: this.costUsd
|
|
782
|
-
} : {}
|
|
783
|
-
}
|
|
861
|
+
usage: this.messageUsage()
|
|
784
862
|
} : {},
|
|
785
863
|
...this.a.input.agentName !== void 0 ? {
|
|
786
864
|
agentName: this.a.input.agentName
|
|
@@ -808,6 +886,26 @@ ${delta}` : delta;
|
|
|
808
886
|
]);
|
|
809
887
|
return message.id;
|
|
810
888
|
}
|
|
889
|
+
/** The run's usage as its last message carries it: the whole run, cache tokens included. */
|
|
890
|
+
messageUsage() {
|
|
891
|
+
const total = this.totalUsage();
|
|
892
|
+
return {
|
|
893
|
+
inputTokens: total.inputTokens,
|
|
894
|
+
outputTokens: total.outputTokens,
|
|
895
|
+
...total.cacheReadTokens > 0 ? {
|
|
896
|
+
cacheReadTokens: total.cacheReadTokens
|
|
897
|
+
} : {},
|
|
898
|
+
...total.cacheWriteTokens > 0 ? {
|
|
899
|
+
cacheWriteTokens: total.cacheWriteTokens
|
|
900
|
+
} : {},
|
|
901
|
+
...total.reasoningTokens > 0 ? {
|
|
902
|
+
reasoningTokens: total.reasoningTokens
|
|
903
|
+
} : {},
|
|
904
|
+
...this.priced || (this.a.carried?.costUsd ?? 0) > 0 ? {
|
|
905
|
+
costUsd: total.costUsd
|
|
906
|
+
} : {}
|
|
907
|
+
};
|
|
908
|
+
}
|
|
811
909
|
/** A request recorded by an earlier process (a turn resumed after a restart). */
|
|
812
910
|
async alreadyRecorded(id) {
|
|
813
911
|
return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
|
|
@@ -1177,6 +1275,35 @@ ${delta}` : delta;
|
|
|
1177
1275
|
});
|
|
1178
1276
|
}
|
|
1179
1277
|
};
|
|
1278
|
+
function modelOfLabel(label) {
|
|
1279
|
+
const slash = label?.indexOf("/") ?? -1;
|
|
1280
|
+
if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
|
|
1281
|
+
return {
|
|
1282
|
+
providerID: label.slice(0, slash),
|
|
1283
|
+
id: label.slice(slash + 1)
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
__name(modelOfLabel, "modelOfLabel");
|
|
1287
|
+
function usageOf(tokens) {
|
|
1288
|
+
const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
|
|
1289
|
+
const read = n(tokens?.cache?.read);
|
|
1290
|
+
const write = n(tokens?.cache?.write);
|
|
1291
|
+
const reasoning = n(tokens?.reasoning);
|
|
1292
|
+
return {
|
|
1293
|
+
inputTokens: n(tokens?.input) + read + write,
|
|
1294
|
+
outputTokens: n(tokens?.output),
|
|
1295
|
+
...read > 0 ? {
|
|
1296
|
+
cacheReadTokens: read
|
|
1297
|
+
} : {},
|
|
1298
|
+
...write > 0 ? {
|
|
1299
|
+
cacheWriteTokens: write
|
|
1300
|
+
} : {},
|
|
1301
|
+
...reasoning > 0 ? {
|
|
1302
|
+
reasoningTokens: reasoning
|
|
1303
|
+
} : {}
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
__name(usageOf, "usageOf");
|
|
1180
1307
|
|
|
1181
1308
|
// src/mcp.ts
|
|
1182
1309
|
var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -1608,7 +1735,7 @@ var OpenCodeTurns = class {
|
|
|
1608
1735
|
spent = /* @__PURE__ */ new Set();
|
|
1609
1736
|
/** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
|
|
1610
1737
|
toolsIssued = /* @__PURE__ */ new Map();
|
|
1611
|
-
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2) {
|
|
1738
|
+
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
|
|
1612
1739
|
this.host = host;
|
|
1613
1740
|
this.sessions = sessions;
|
|
1614
1741
|
this.settings = settings;
|
|
@@ -1620,6 +1747,30 @@ var OpenCodeTurns = class {
|
|
|
1620
1747
|
this.memory = memory;
|
|
1621
1748
|
this.queue = queue;
|
|
1622
1749
|
this.toolsTokens = toolsTokens2;
|
|
1750
|
+
this.prices = new import_nestjs_agent_core4.ModelPriceResolver({
|
|
1751
|
+
pricingStore,
|
|
1752
|
+
catalog: agentOptions?.priceCatalog,
|
|
1753
|
+
log: {
|
|
1754
|
+
info: /* @__PURE__ */ __name((message) => this.logger.log(message), "info"),
|
|
1755
|
+
warn: /* @__PURE__ */ __name((message) => this.logger.warn(message), "warn")
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
prices;
|
|
1760
|
+
/**
|
|
1761
|
+
* What a model call OpenCode made cost, by the same rule the library's loop records its own steps
|
|
1762
|
+
* at: a reported figure wins, else an estimate off the model's price — see
|
|
1763
|
+
* {@link OpenCodeEngineSettings.cost}.
|
|
1764
|
+
*/
|
|
1765
|
+
async priceStep(step) {
|
|
1766
|
+
const { model, usage, reportedCostUsd } = step;
|
|
1767
|
+
const tokens = usage.inputTokens + usage.outputTokens;
|
|
1768
|
+
const reported = reportedCostUsd !== void 0 && (reportedCostUsd > 0 || tokens === 0);
|
|
1769
|
+
if (reported && this.settings.cost !== "estimate") {
|
|
1770
|
+
return (0, import_nestjs_agent_core4.resolveUsageCost)(usage, reportedCostUsd, void 0);
|
|
1771
|
+
}
|
|
1772
|
+
const price = model !== void 0 && tokens > 0 ? await this.prices.priceFor(priceRef(model)) : void 0;
|
|
1773
|
+
return (0, import_nestjs_agent_core4.resolveUsageCost)(usage, price === void 0 ? reportedCostUsd : void 0, price);
|
|
1623
1774
|
}
|
|
1624
1775
|
/** The tools server's name in OpenCode. */
|
|
1625
1776
|
get toolsServer() {
|
|
@@ -2209,8 +2360,8 @@ ${body}
|
|
|
2209
2360
|
}
|
|
2210
2361
|
// ---- prompt / observe / reply --------------------------------------------------------------
|
|
2211
2362
|
/** Start listening to the session (so nothing it says is missed), then send the user message. */
|
|
2212
|
-
async prompt(runId, input, handle) {
|
|
2213
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2363
|
+
async prompt(runId, input, handle, spent) {
|
|
2364
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2214
2365
|
try {
|
|
2215
2366
|
const prompt = await this.host.promptFor?.({
|
|
2216
2367
|
input,
|
|
@@ -2240,9 +2391,12 @@ ${body}
|
|
|
2240
2391
|
* Wait for the turn's next milestone. In a process that was not following the turn (a resumed
|
|
2241
2392
|
* durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
|
|
2242
2393
|
* `session.wait` is the safety net for a terminal event that was missed.
|
|
2394
|
+
*
|
|
2395
|
+
* `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
|
|
2396
|
+
* rebuilt here starts from it, so the answer it persists carries the whole run's usage.
|
|
2243
2397
|
*/
|
|
2244
|
-
async observe(runId, input, handle) {
|
|
2245
|
-
const live = await this.ensureLive(runId, input, handle, true);
|
|
2398
|
+
async observe(runId, input, handle, spent) {
|
|
2399
|
+
const live = await this.ensureLive(runId, input, handle, true, spent);
|
|
2246
2400
|
const { turn, client } = live;
|
|
2247
2401
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2248
2402
|
let done = false;
|
|
@@ -2281,23 +2435,23 @@ ${body}
|
|
|
2281
2435
|
* request is gone with it: a new session is opened, told what the person decided, and prompted
|
|
2282
2436
|
* to go on.
|
|
2283
2437
|
*/
|
|
2284
|
-
async reply(runId, input, handle, ask, reply) {
|
|
2438
|
+
async reply(runId, input, handle, ask, reply, spent) {
|
|
2285
2439
|
const server = await this.host.server(input.actor);
|
|
2286
2440
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2287
|
-
return this.continueAfterRestart(runId, input, server, ask, reply);
|
|
2441
|
+
return this.continueAfterRestart(runId, input, server, ask, reply, spent);
|
|
2288
2442
|
}
|
|
2289
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2443
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2290
2444
|
await live.turn.decide(ask, reply);
|
|
2291
2445
|
return handle;
|
|
2292
2446
|
}
|
|
2293
|
-
async continueAfterRestart(runId, input, server, ask, reply) {
|
|
2447
|
+
async continueAfterRestart(runId, input, server, ask, reply, spent) {
|
|
2294
2448
|
const approved = "approved" in reply && reply.approved === true;
|
|
2295
2449
|
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.`;
|
|
2296
2450
|
const thread = await this.store.getThread(input.threadId);
|
|
2297
2451
|
const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
|
|
2298
2452
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2299
2453
|
this.drop(runId);
|
|
2300
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2454
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2301
2455
|
await live.turn.decide(ask, reply, {
|
|
2302
2456
|
tellOpenCode: false
|
|
2303
2457
|
});
|
|
@@ -2317,7 +2471,7 @@ ${body}
|
|
|
2317
2471
|
sessionID: known.sessionId
|
|
2318
2472
|
});
|
|
2319
2473
|
}
|
|
2320
|
-
async ensureLive(runId, input, handle, catchUp) {
|
|
2474
|
+
async ensureLive(runId, input, handle, catchUp, spent) {
|
|
2321
2475
|
const current = this.live.get(runId);
|
|
2322
2476
|
if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
|
|
2323
2477
|
current?.stop();
|
|
@@ -2343,6 +2497,10 @@ ${body}
|
|
|
2343
2497
|
} : {}
|
|
2344
2498
|
}), "approvalFor"),
|
|
2345
2499
|
modelLabel: input.model ?? "opencode",
|
|
2500
|
+
priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
|
|
2501
|
+
...spent !== void 0 ? {
|
|
2502
|
+
carried: spent
|
|
2503
|
+
} : {},
|
|
2346
2504
|
logger: this.logger,
|
|
2347
2505
|
onGranted: /* @__PURE__ */ __name((action, id) => {
|
|
2348
2506
|
const grants = this.grants.get(runId) ?? [];
|
|
@@ -2389,20 +2547,14 @@ ${body}
|
|
|
2389
2547
|
}
|
|
2390
2548
|
return milestone;
|
|
2391
2549
|
}
|
|
2392
|
-
/**
|
|
2393
|
-
|
|
2550
|
+
/**
|
|
2551
|
+
* What the run produced, as the host's settle hooks see it. `usage` is what the run spent — the
|
|
2552
|
+
* runner's sum of its milestones' usage, which a resumed run reads back from its journal — else
|
|
2553
|
+
* what the turn this process follows saw.
|
|
2554
|
+
*/
|
|
2555
|
+
async runResult(runId, input, outcome, durationMs, usage) {
|
|
2394
2556
|
const thread = await this.store.getThread(input.threadId);
|
|
2395
2557
|
const messages = (thread?.messages ?? []).filter((m) => m.role === "assistant" && m.runId === runId);
|
|
2396
|
-
const usage = {
|
|
2397
|
-
inputTokens: 0,
|
|
2398
|
-
outputTokens: 0,
|
|
2399
|
-
costUsd: 0
|
|
2400
|
-
};
|
|
2401
|
-
for (const m of messages) {
|
|
2402
|
-
usage.inputTokens += m.usage?.inputTokens ?? 0;
|
|
2403
|
-
usage.outputTokens += m.usage?.outputTokens ?? 0;
|
|
2404
|
-
usage.costUsd += m.usage?.costUsd ?? 0;
|
|
2405
|
-
}
|
|
2406
2558
|
return {
|
|
2407
2559
|
runId,
|
|
2408
2560
|
input,
|
|
@@ -2447,15 +2599,31 @@ ${body}
|
|
|
2447
2599
|
this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
|
|
2448
2600
|
}
|
|
2449
2601
|
}
|
|
2450
|
-
/**
|
|
2451
|
-
|
|
2602
|
+
/**
|
|
2603
|
+
* What the run spent: the runner's sum of its milestones, or what the turn this process follows
|
|
2604
|
+
* saw (which starts from that sum) when it saw more — a run that failed or was cancelled between
|
|
2605
|
+
* two milestones.
|
|
2606
|
+
*/
|
|
2607
|
+
spentBy(runId, spent) {
|
|
2608
|
+
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
2609
|
+
if (spent === void 0) return seen ?? emptyUsage();
|
|
2610
|
+
if (seen === void 0) return spent;
|
|
2611
|
+
const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
|
|
2612
|
+
return tokens(seen) > tokens(spent) ? seen : spent;
|
|
2613
|
+
}
|
|
2614
|
+
/**
|
|
2615
|
+
* End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
|
|
2616
|
+
* (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
|
|
2617
|
+
*/
|
|
2618
|
+
async settle(runId, input, outcome, durationMs, spent) {
|
|
2452
2619
|
if (outcome.status === "failed") {
|
|
2453
|
-
await this.settleFailed(runId, input, outcome.error, durationMs);
|
|
2620
|
+
await this.settleFailed(runId, input, outcome.error, durationMs, spent);
|
|
2454
2621
|
return;
|
|
2455
2622
|
}
|
|
2623
|
+
const usage = this.spentBy(runId, spent);
|
|
2456
2624
|
this.forget(runId);
|
|
2457
2625
|
const writer = await this.sink.open(runId);
|
|
2458
|
-
const result = await this.runResult(runId, input, outcome, durationMs);
|
|
2626
|
+
const result = await this.runResult(runId, input, outcome, durationMs, usage);
|
|
2459
2627
|
await this.amend(writer, result);
|
|
2460
2628
|
if (outcome.status === "interrupted") {
|
|
2461
2629
|
this.logger.log(`agent run ${runId} cancelled`);
|
|
@@ -2483,13 +2651,14 @@ ${body}
|
|
|
2483
2651
|
}
|
|
2484
2652
|
await this.settled(result);
|
|
2485
2653
|
}
|
|
2486
|
-
async settleFailed(runId, input, error, durationMs = 0) {
|
|
2654
|
+
async settleFailed(runId, input, error, durationMs = 0, spent) {
|
|
2655
|
+
const usage = this.spentBy(runId, spent);
|
|
2487
2656
|
this.forget(runId);
|
|
2488
2657
|
const writer = await this.sink.open(runId);
|
|
2489
2658
|
const result = await this.runResult(runId, input, {
|
|
2490
2659
|
status: "failed",
|
|
2491
2660
|
error
|
|
2492
|
-
}, durationMs);
|
|
2661
|
+
}, durationMs, usage);
|
|
2493
2662
|
const worded = (await this.amend(writer, result)).error;
|
|
2494
2663
|
const message = worded ?? (0, import_nestjs_agent_core4.streamFailure)(new Error(error)).message;
|
|
2495
2664
|
this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
@@ -2559,6 +2728,10 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
2559
2728
|
_ts_param2(9, (0, import_common4.Inject)(import_nestjs_agent.AGENT_CHAT_QUEUE)),
|
|
2560
2729
|
_ts_param2(10, (0, import_common4.Optional)()),
|
|
2561
2730
|
_ts_param2(10, (0, import_common4.Inject)(OPENCODE_TOOLS_TOKENS)),
|
|
2731
|
+
_ts_param2(11, (0, import_common4.Optional)()),
|
|
2732
|
+
_ts_param2(11, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_PRICING_STORE)),
|
|
2733
|
+
_ts_param2(12, (0, import_common4.Optional)()),
|
|
2734
|
+
_ts_param2(12, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_OPTIONS)),
|
|
2562
2735
|
_ts_metadata2("design:type", Function),
|
|
2563
2736
|
_ts_metadata2("design:paramtypes", [
|
|
2564
2737
|
typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
|
|
@@ -2571,6 +2744,8 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
2571
2744
|
typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
|
|
2572
2745
|
typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
|
|
2573
2746
|
typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
|
|
2747
|
+
Object,
|
|
2748
|
+
typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
|
|
2574
2749
|
Object
|
|
2575
2750
|
])
|
|
2576
2751
|
], OpenCodeTurns);
|
|
@@ -2613,6 +2788,19 @@ function uiEmitter(scope, push) {
|
|
|
2613
2788
|
};
|
|
2614
2789
|
}
|
|
2615
2790
|
__name(uiEmitter, "uiEmitter");
|
|
2791
|
+
function priceRef(model) {
|
|
2792
|
+
const aliases = [
|
|
2793
|
+
model.id
|
|
2794
|
+
];
|
|
2795
|
+
const tail = model.id.slice(model.id.lastIndexOf("/") + 1);
|
|
2796
|
+
if (tail !== model.id && (0, import_nestjs_agent_core4.parseBedrockModelId)(tail) !== void 0) aliases.push(tail);
|
|
2797
|
+
return {
|
|
2798
|
+
modelId: `${model.providerID}/${model.id}`,
|
|
2799
|
+
provider: model.providerID,
|
|
2800
|
+
aliases
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
__name(priceRef, "priceRef");
|
|
2616
2804
|
|
|
2617
2805
|
// src/runner.ts
|
|
2618
2806
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
@@ -2714,18 +2902,20 @@ var OpenCodeAgentRunner = class {
|
|
|
2714
2902
|
async run(runId, input) {
|
|
2715
2903
|
const started = Date.now();
|
|
2716
2904
|
this.inputs.set(runId, input);
|
|
2905
|
+
let spent = emptyUsage();
|
|
2717
2906
|
try {
|
|
2718
2907
|
let handle = await this.turns.begin(runId, input);
|
|
2719
2908
|
if (this.cancelled.has(runId)) throw new import_nestjs_agent_core5.RunCancelledError();
|
|
2720
|
-
await this.turns.prompt(runId, input, handle);
|
|
2909
|
+
await this.turns.prompt(runId, input, handle, spent);
|
|
2721
2910
|
for (; ; ) {
|
|
2722
|
-
const milestone = await this.turns.observe(runId, input, handle);
|
|
2911
|
+
const milestone = await this.turns.observe(runId, input, handle, spent);
|
|
2912
|
+
spent = addUsage(spent, milestone.usage);
|
|
2723
2913
|
if (milestone.kind === "finished") {
|
|
2724
|
-
await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
|
|
2914
|
+
await this.turns.settle(runId, input, milestone.outcome, Date.now() - started, spent);
|
|
2725
2915
|
return;
|
|
2726
2916
|
}
|
|
2727
2917
|
const reply = await this.park(runId, milestone.ask.id, milestone.ask.kind === "approval" ? "approval" : "answers", milestone.timeoutMs);
|
|
2728
|
-
handle = await this.turns.reply(runId, input, handle, milestone.ask, reply);
|
|
2918
|
+
handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
|
|
2729
2919
|
}
|
|
2730
2920
|
} catch (error) {
|
|
2731
2921
|
if (error instanceof import_nestjs_agent_core5.RunCancelledError) {
|
|
@@ -2735,7 +2925,7 @@ var OpenCodeAgentRunner = class {
|
|
|
2735
2925
|
}, Date.now() - started);
|
|
2736
2926
|
return;
|
|
2737
2927
|
}
|
|
2738
|
-
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"));
|
|
2928
|
+
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
|
|
2739
2929
|
} finally {
|
|
2740
2930
|
this.inputs.delete(runId);
|
|
2741
2931
|
}
|
|
@@ -2907,6 +3097,7 @@ var OpenCodeRunWorkflow = class {
|
|
|
2907
3097
|
this.runs = runs;
|
|
2908
3098
|
}
|
|
2909
3099
|
async run(ctx, input) {
|
|
3100
|
+
let spent = emptyUsage();
|
|
2910
3101
|
try {
|
|
2911
3102
|
const begun = await ctx.localStep("begin", async () => ({
|
|
2912
3103
|
handle: await this.turns.begin(ctx.runId, input),
|
|
@@ -2914,15 +3105,18 @@ var OpenCodeRunWorkflow = class {
|
|
|
2914
3105
|
}));
|
|
2915
3106
|
let handle = begun.handle;
|
|
2916
3107
|
await ctx.localStep("prompt", async () => {
|
|
2917
|
-
await this.turns.prompt(ctx.runId, input, handle);
|
|
3108
|
+
await this.turns.prompt(ctx.runId, input, handle, spent);
|
|
2918
3109
|
return true;
|
|
2919
3110
|
});
|
|
2920
3111
|
for (let n = 0; ; n += 1) {
|
|
2921
|
-
const
|
|
3112
|
+
const before = spent;
|
|
3113
|
+
const milestone = await ctx.localStep(`observe:${n}`, () => this.turns.observe(ctx.runId, input, handle, before));
|
|
3114
|
+
spent = addUsage(spent, milestone.usage);
|
|
3115
|
+
const total = spent;
|
|
2922
3116
|
if (milestone.kind === "finished") {
|
|
2923
3117
|
await ctx.localStep("finish", async () => {
|
|
2924
3118
|
if (await this.cancelled(ctx.runId)) this.turns.drop(ctx.runId);
|
|
2925
|
-
else await this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt);
|
|
3119
|
+
else await this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt, total);
|
|
2926
3120
|
return true;
|
|
2927
3121
|
});
|
|
2928
3122
|
return {
|
|
@@ -2942,12 +3136,12 @@ var OpenCodeRunWorkflow = class {
|
|
|
2942
3136
|
};
|
|
2943
3137
|
}
|
|
2944
3138
|
const ask = milestone.ask;
|
|
2945
|
-
handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply));
|
|
3139
|
+
handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply, total));
|
|
2946
3140
|
}
|
|
2947
3141
|
} catch (error) {
|
|
2948
3142
|
if ((0, import_nestjs_durable_core.isWorkflowControlFlowSignal)(error)) throw error;
|
|
2949
3143
|
await ctx.localStep("fail", async () => {
|
|
2950
|
-
await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"));
|
|
3144
|
+
await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"), 0, spent);
|
|
2951
3145
|
return true;
|
|
2952
3146
|
});
|
|
2953
3147
|
return {
|