@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/README.md +29 -1
- package/dist/durable/index.cjs +268 -75
- 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 -76
- 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 -72
- 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 -72
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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,42 +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
|
-
costSource: "provider"
|
|
749
|
-
} : {}
|
|
820
|
+
...cost
|
|
750
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");
|
|
751
833
|
if (!this.stepOpen) return;
|
|
752
834
|
this.stepOpen = false;
|
|
753
835
|
await this.write({
|
|
754
836
|
kind: "step-finish",
|
|
755
837
|
usage,
|
|
756
|
-
costUsd: costUsd ?? null,
|
|
838
|
+
costUsd: cost.costUsd ?? null,
|
|
757
839
|
...this.segment.reasoningMs > 0 ? {
|
|
758
840
|
reasoningMs: this.segment.reasoningMs
|
|
759
841
|
} : {},
|
|
760
|
-
model: this.
|
|
842
|
+
model: this.modelId()
|
|
761
843
|
});
|
|
762
844
|
}
|
|
763
845
|
/**
|
|
@@ -776,12 +858,7 @@ ${delta}` : delta;
|
|
|
776
858
|
content: s.text,
|
|
777
859
|
runId: this.a.runId,
|
|
778
860
|
...final ? {
|
|
779
|
-
usage:
|
|
780
|
-
...this.usage,
|
|
781
|
-
...this.costUsd !== void 0 ? {
|
|
782
|
-
costUsd: this.costUsd
|
|
783
|
-
} : {}
|
|
784
|
-
}
|
|
861
|
+
usage: this.messageUsage()
|
|
785
862
|
} : {},
|
|
786
863
|
...this.a.input.agentName !== void 0 ? {
|
|
787
864
|
agentName: this.a.input.agentName
|
|
@@ -809,6 +886,26 @@ ${delta}` : delta;
|
|
|
809
886
|
]);
|
|
810
887
|
return message.id;
|
|
811
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
|
+
}
|
|
812
909
|
/** A request recorded by an earlier process (a turn resumed after a restart). */
|
|
813
910
|
async alreadyRecorded(id) {
|
|
814
911
|
return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
|
|
@@ -1178,6 +1275,35 @@ ${delta}` : delta;
|
|
|
1178
1275
|
});
|
|
1179
1276
|
}
|
|
1180
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");
|
|
1181
1307
|
|
|
1182
1308
|
// src/mcp.ts
|
|
1183
1309
|
var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -1609,7 +1735,7 @@ var OpenCodeTurns = class {
|
|
|
1609
1735
|
spent = /* @__PURE__ */ new Set();
|
|
1610
1736
|
/** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
|
|
1611
1737
|
toolsIssued = /* @__PURE__ */ new Map();
|
|
1612
|
-
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) {
|
|
1613
1739
|
this.host = host;
|
|
1614
1740
|
this.sessions = sessions;
|
|
1615
1741
|
this.settings = settings;
|
|
@@ -1621,6 +1747,30 @@ var OpenCodeTurns = class {
|
|
|
1621
1747
|
this.memory = memory;
|
|
1622
1748
|
this.queue = queue;
|
|
1623
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);
|
|
1624
1774
|
}
|
|
1625
1775
|
/** The tools server's name in OpenCode. */
|
|
1626
1776
|
get toolsServer() {
|
|
@@ -2210,8 +2360,8 @@ ${body}
|
|
|
2210
2360
|
}
|
|
2211
2361
|
// ---- prompt / observe / reply --------------------------------------------------------------
|
|
2212
2362
|
/** Start listening to the session (so nothing it says is missed), then send the user message. */
|
|
2213
|
-
async prompt(runId, input, handle) {
|
|
2214
|
-
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);
|
|
2215
2365
|
try {
|
|
2216
2366
|
const prompt = await this.host.promptFor?.({
|
|
2217
2367
|
input,
|
|
@@ -2241,9 +2391,12 @@ ${body}
|
|
|
2241
2391
|
* Wait for the turn's next milestone. In a process that was not following the turn (a resumed
|
|
2242
2392
|
* durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
|
|
2243
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.
|
|
2244
2397
|
*/
|
|
2245
|
-
async observe(runId, input, handle) {
|
|
2246
|
-
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);
|
|
2247
2400
|
const { turn, client } = live;
|
|
2248
2401
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2249
2402
|
let done = false;
|
|
@@ -2282,23 +2435,23 @@ ${body}
|
|
|
2282
2435
|
* request is gone with it: a new session is opened, told what the person decided, and prompted
|
|
2283
2436
|
* to go on.
|
|
2284
2437
|
*/
|
|
2285
|
-
async reply(runId, input, handle, ask, reply) {
|
|
2438
|
+
async reply(runId, input, handle, ask, reply, spent) {
|
|
2286
2439
|
const server = await this.host.server(input.actor);
|
|
2287
2440
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2288
|
-
return this.continueAfterRestart(runId, input, server, ask, reply);
|
|
2441
|
+
return this.continueAfterRestart(runId, input, server, ask, reply, spent);
|
|
2289
2442
|
}
|
|
2290
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2443
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2291
2444
|
await live.turn.decide(ask, reply);
|
|
2292
2445
|
return handle;
|
|
2293
2446
|
}
|
|
2294
|
-
async continueAfterRestart(runId, input, server, ask, reply) {
|
|
2447
|
+
async continueAfterRestart(runId, input, server, ask, reply, spent) {
|
|
2295
2448
|
const approved = "approved" in reply && reply.approved === true;
|
|
2296
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.`;
|
|
2297
2450
|
const thread = await this.store.getThread(input.threadId);
|
|
2298
2451
|
const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
|
|
2299
2452
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2300
2453
|
this.drop(runId);
|
|
2301
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2454
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2302
2455
|
await live.turn.decide(ask, reply, {
|
|
2303
2456
|
tellOpenCode: false
|
|
2304
2457
|
});
|
|
@@ -2318,7 +2471,7 @@ ${body}
|
|
|
2318
2471
|
sessionID: known.sessionId
|
|
2319
2472
|
});
|
|
2320
2473
|
}
|
|
2321
|
-
async ensureLive(runId, input, handle, catchUp) {
|
|
2474
|
+
async ensureLive(runId, input, handle, catchUp, spent) {
|
|
2322
2475
|
const current = this.live.get(runId);
|
|
2323
2476
|
if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
|
|
2324
2477
|
current?.stop();
|
|
@@ -2344,6 +2497,10 @@ ${body}
|
|
|
2344
2497
|
} : {}
|
|
2345
2498
|
}), "approvalFor"),
|
|
2346
2499
|
modelLabel: input.model ?? "opencode",
|
|
2500
|
+
priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
|
|
2501
|
+
...spent !== void 0 ? {
|
|
2502
|
+
carried: spent
|
|
2503
|
+
} : {},
|
|
2347
2504
|
logger: this.logger,
|
|
2348
2505
|
onGranted: /* @__PURE__ */ __name((action, id) => {
|
|
2349
2506
|
const grants = this.grants.get(runId) ?? [];
|
|
@@ -2390,20 +2547,14 @@ ${body}
|
|
|
2390
2547
|
}
|
|
2391
2548
|
return milestone;
|
|
2392
2549
|
}
|
|
2393
|
-
/**
|
|
2394
|
-
|
|
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) {
|
|
2395
2556
|
const thread = await this.store.getThread(input.threadId);
|
|
2396
2557
|
const messages = (thread?.messages ?? []).filter((m) => m.role === "assistant" && m.runId === runId);
|
|
2397
|
-
const usage = {
|
|
2398
|
-
inputTokens: 0,
|
|
2399
|
-
outputTokens: 0,
|
|
2400
|
-
costUsd: 0
|
|
2401
|
-
};
|
|
2402
|
-
for (const m of messages) {
|
|
2403
|
-
usage.inputTokens += m.usage?.inputTokens ?? 0;
|
|
2404
|
-
usage.outputTokens += m.usage?.outputTokens ?? 0;
|
|
2405
|
-
usage.costUsd += m.usage?.costUsd ?? 0;
|
|
2406
|
-
}
|
|
2407
2558
|
return {
|
|
2408
2559
|
runId,
|
|
2409
2560
|
input,
|
|
@@ -2448,15 +2599,31 @@ ${body}
|
|
|
2448
2599
|
this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
|
|
2449
2600
|
}
|
|
2450
2601
|
}
|
|
2451
|
-
/**
|
|
2452
|
-
|
|
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) {
|
|
2453
2619
|
if (outcome.status === "failed") {
|
|
2454
|
-
await this.settleFailed(runId, input, outcome.error, durationMs);
|
|
2620
|
+
await this.settleFailed(runId, input, outcome.error, durationMs, spent);
|
|
2455
2621
|
return;
|
|
2456
2622
|
}
|
|
2623
|
+
const usage = this.spentBy(runId, spent);
|
|
2457
2624
|
this.forget(runId);
|
|
2458
2625
|
const writer = await this.sink.open(runId);
|
|
2459
|
-
const result = await this.runResult(runId, input, outcome, durationMs);
|
|
2626
|
+
const result = await this.runResult(runId, input, outcome, durationMs, usage);
|
|
2460
2627
|
await this.amend(writer, result);
|
|
2461
2628
|
if (outcome.status === "interrupted") {
|
|
2462
2629
|
this.logger.log(`agent run ${runId} cancelled`);
|
|
@@ -2484,13 +2651,14 @@ ${body}
|
|
|
2484
2651
|
}
|
|
2485
2652
|
await this.settled(result);
|
|
2486
2653
|
}
|
|
2487
|
-
async settleFailed(runId, input, error, durationMs = 0) {
|
|
2654
|
+
async settleFailed(runId, input, error, durationMs = 0, spent) {
|
|
2655
|
+
const usage = this.spentBy(runId, spent);
|
|
2488
2656
|
this.forget(runId);
|
|
2489
2657
|
const writer = await this.sink.open(runId);
|
|
2490
2658
|
const result = await this.runResult(runId, input, {
|
|
2491
2659
|
status: "failed",
|
|
2492
2660
|
error
|
|
2493
|
-
}, durationMs);
|
|
2661
|
+
}, durationMs, usage);
|
|
2494
2662
|
const worded = (await this.amend(writer, result)).error;
|
|
2495
2663
|
const message = worded ?? (0, import_nestjs_agent_core4.streamFailure)(new Error(error)).message;
|
|
2496
2664
|
this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
@@ -2560,6 +2728,10 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
2560
2728
|
_ts_param2(9, (0, import_common4.Inject)(import_nestjs_agent.AGENT_CHAT_QUEUE)),
|
|
2561
2729
|
_ts_param2(10, (0, import_common4.Optional)()),
|
|
2562
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)),
|
|
2563
2735
|
_ts_metadata2("design:type", Function),
|
|
2564
2736
|
_ts_metadata2("design:paramtypes", [
|
|
2565
2737
|
typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
|
|
@@ -2572,6 +2744,8 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
2572
2744
|
typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
|
|
2573
2745
|
typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
|
|
2574
2746
|
typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
|
|
2747
|
+
Object,
|
|
2748
|
+
typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
|
|
2575
2749
|
Object
|
|
2576
2750
|
])
|
|
2577
2751
|
], OpenCodeTurns);
|
|
@@ -2614,6 +2788,19 @@ function uiEmitter(scope, push) {
|
|
|
2614
2788
|
};
|
|
2615
2789
|
}
|
|
2616
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");
|
|
2617
2804
|
|
|
2618
2805
|
// src/runner.ts
|
|
2619
2806
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
@@ -2715,18 +2902,20 @@ var OpenCodeAgentRunner = class {
|
|
|
2715
2902
|
async run(runId, input) {
|
|
2716
2903
|
const started = Date.now();
|
|
2717
2904
|
this.inputs.set(runId, input);
|
|
2905
|
+
let spent = emptyUsage();
|
|
2718
2906
|
try {
|
|
2719
2907
|
let handle = await this.turns.begin(runId, input);
|
|
2720
2908
|
if (this.cancelled.has(runId)) throw new import_nestjs_agent_core5.RunCancelledError();
|
|
2721
|
-
await this.turns.prompt(runId, input, handle);
|
|
2909
|
+
await this.turns.prompt(runId, input, handle, spent);
|
|
2722
2910
|
for (; ; ) {
|
|
2723
|
-
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);
|
|
2724
2913
|
if (milestone.kind === "finished") {
|
|
2725
|
-
await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
|
|
2914
|
+
await this.turns.settle(runId, input, milestone.outcome, Date.now() - started, spent);
|
|
2726
2915
|
return;
|
|
2727
2916
|
}
|
|
2728
2917
|
const reply = await this.park(runId, milestone.ask.id, milestone.ask.kind === "approval" ? "approval" : "answers", milestone.timeoutMs);
|
|
2729
|
-
handle = await this.turns.reply(runId, input, handle, milestone.ask, reply);
|
|
2918
|
+
handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
|
|
2730
2919
|
}
|
|
2731
2920
|
} catch (error) {
|
|
2732
2921
|
if (error instanceof import_nestjs_agent_core5.RunCancelledError) {
|
|
@@ -2736,7 +2925,7 @@ var OpenCodeAgentRunner = class {
|
|
|
2736
2925
|
}, Date.now() - started);
|
|
2737
2926
|
return;
|
|
2738
2927
|
}
|
|
2739
|
-
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);
|
|
2740
2929
|
} finally {
|
|
2741
2930
|
this.inputs.delete(runId);
|
|
2742
2931
|
}
|
|
@@ -2908,6 +3097,7 @@ var OpenCodeRunWorkflow = class {
|
|
|
2908
3097
|
this.runs = runs;
|
|
2909
3098
|
}
|
|
2910
3099
|
async run(ctx, input) {
|
|
3100
|
+
let spent = emptyUsage();
|
|
2911
3101
|
try {
|
|
2912
3102
|
const begun = await ctx.localStep("begin", async () => ({
|
|
2913
3103
|
handle: await this.turns.begin(ctx.runId, input),
|
|
@@ -2915,15 +3105,18 @@ var OpenCodeRunWorkflow = class {
|
|
|
2915
3105
|
}));
|
|
2916
3106
|
let handle = begun.handle;
|
|
2917
3107
|
await ctx.localStep("prompt", async () => {
|
|
2918
|
-
await this.turns.prompt(ctx.runId, input, handle);
|
|
3108
|
+
await this.turns.prompt(ctx.runId, input, handle, spent);
|
|
2919
3109
|
return true;
|
|
2920
3110
|
});
|
|
2921
3111
|
for (let n = 0; ; n += 1) {
|
|
2922
|
-
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;
|
|
2923
3116
|
if (milestone.kind === "finished") {
|
|
2924
3117
|
await ctx.localStep("finish", async () => {
|
|
2925
3118
|
if (await this.cancelled(ctx.runId)) this.turns.drop(ctx.runId);
|
|
2926
|
-
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);
|
|
2927
3120
|
return true;
|
|
2928
3121
|
});
|
|
2929
3122
|
return {
|
|
@@ -2943,12 +3136,12 @@ var OpenCodeRunWorkflow = class {
|
|
|
2943
3136
|
};
|
|
2944
3137
|
}
|
|
2945
3138
|
const ask = milestone.ask;
|
|
2946
|
-
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));
|
|
2947
3140
|
}
|
|
2948
3141
|
} catch (error) {
|
|
2949
3142
|
if ((0, import_nestjs_durable_core.isWorkflowControlFlowSignal)(error)) throw error;
|
|
2950
3143
|
await ctx.localStep("fail", async () => {
|
|
2951
|
-
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);
|
|
2952
3145
|
return true;
|
|
2953
3146
|
});
|
|
2954
3147
|
return {
|