@dudousxd/nestjs-agent-opencode 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -1
- package/dist/durable/index.cjs +344 -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 +345 -75
- package/dist/durable/index.js.map +1 -1
- package/dist/{engine-Bij6AdoF.d.cts → engine-0YkBLWZR.d.cts} +154 -19
- package/dist/{engine-Bij6AdoF.d.ts → engine-0YkBLWZR.d.ts} +154 -19
- package/dist/index.cjs +343 -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 +340 -71
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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,22 @@ var OpenCodeTurn = class {
|
|
|
314
349
|
hidden = /* @__PURE__ */ new Set();
|
|
315
350
|
milestones = [];
|
|
316
351
|
waiting;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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;
|
|
357
|
+
/** What the session had spent when this turn prompted it; what OpenCode reported since. */
|
|
358
|
+
baseline;
|
|
359
|
+
seen = {
|
|
360
|
+
cost: 0,
|
|
361
|
+
input: 0,
|
|
362
|
+
output: 0,
|
|
363
|
+
reasoning: 0,
|
|
364
|
+
read: 0,
|
|
365
|
+
write: 0
|
|
320
366
|
};
|
|
321
|
-
|
|
367
|
+
compacted = false;
|
|
322
368
|
stepOpen = false;
|
|
323
369
|
sawText = false;
|
|
324
370
|
separator = false;
|
|
@@ -328,6 +374,24 @@ var OpenCodeTurn = class {
|
|
|
328
374
|
stepError;
|
|
329
375
|
constructor(a) {
|
|
330
376
|
this.a = a;
|
|
377
|
+
this.model = modelOfLabel(a.input.model);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* What the session had spent before this turn prompted it (`session.get`). When the execution
|
|
381
|
+
* ends, what the session spent since that this turn's events did not report — the title OpenCode
|
|
382
|
+
* generates, a compaction: OpenCode records those calls on the session without streaming them — is
|
|
383
|
+
* recorded as a usage row of its own.
|
|
384
|
+
*/
|
|
385
|
+
setBaseline(info) {
|
|
386
|
+
if (info?.tokens === void 0) return;
|
|
387
|
+
this.baseline = {
|
|
388
|
+
cost: Number(info.cost) || 0,
|
|
389
|
+
tokens: info.tokens
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/** What the run spent so far: carried from before this turn object, plus what it saw. */
|
|
393
|
+
totalUsage() {
|
|
394
|
+
return addUsage(this.a.carried, this.spent);
|
|
331
395
|
}
|
|
332
396
|
get sessionId() {
|
|
333
397
|
return this.a.sessionId;
|
|
@@ -410,7 +474,15 @@ var OpenCodeTurn = class {
|
|
|
410
474
|
this.chain = this.chain.then(() => ask.kind === "approval" ? this.decided(ask, reply, tell) : this.answered(ask, reply, tell)).then(resolve, reject);
|
|
411
475
|
});
|
|
412
476
|
}
|
|
413
|
-
reach(
|
|
477
|
+
reach(reached) {
|
|
478
|
+
const usage = subUsage(this.spent, this.reported);
|
|
479
|
+
this.reported = {
|
|
480
|
+
...this.spent
|
|
481
|
+
};
|
|
482
|
+
const milestone = {
|
|
483
|
+
...reached,
|
|
484
|
+
usage
|
|
485
|
+
};
|
|
414
486
|
const waiting = this.waiting;
|
|
415
487
|
if (waiting !== void 0) {
|
|
416
488
|
this.waiting = void 0;
|
|
@@ -566,9 +638,26 @@ var OpenCodeTurn = class {
|
|
|
566
638
|
}
|
|
567
639
|
}
|
|
568
640
|
async onEvent(e) {
|
|
569
|
-
if (this.ended) return;
|
|
570
641
|
const d = e.data ?? {};
|
|
642
|
+
if (e.type === "session.usage.recorded") {
|
|
643
|
+
await this.usageRecorded(d);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (this.ended) return;
|
|
571
647
|
switch (e.type) {
|
|
648
|
+
case "session.compaction.started":
|
|
649
|
+
case "session.compaction.ended":
|
|
650
|
+
this.compacted = true;
|
|
651
|
+
return;
|
|
652
|
+
case "session.step.started":
|
|
653
|
+
case "session.model.selected": {
|
|
654
|
+
const model = d.model;
|
|
655
|
+
if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
|
|
656
|
+
providerID: model.providerID,
|
|
657
|
+
id: model.id
|
|
658
|
+
};
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
572
661
|
case "session.text.delta": {
|
|
573
662
|
const delta = typeof d.delta === "string" ? d.delta : "";
|
|
574
663
|
if (!delta) return;
|
|
@@ -722,42 +811,70 @@ ${delta}` : delta;
|
|
|
722
811
|
});
|
|
723
812
|
}
|
|
724
813
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
this.
|
|
738
|
-
this.
|
|
739
|
-
|
|
814
|
+
modelId() {
|
|
815
|
+
return this.model !== void 0 ? `${this.model.providerID}/${this.model.id}` : this.a.modelLabel;
|
|
816
|
+
}
|
|
817
|
+
/** One model call's usage and cost, into the ledger and the run's total. */
|
|
818
|
+
async spend(d, purpose) {
|
|
819
|
+
const usage = usageOf(d.tokens);
|
|
820
|
+
const reportedCostUsd = typeof d.cost === "number" && Number.isFinite(d.cost) && d.cost >= 0 ? d.cost : void 0;
|
|
821
|
+
const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
|
|
822
|
+
this.seen.cost += reportedCostUsd ?? 0;
|
|
823
|
+
this.seen.input += n(d.tokens?.input);
|
|
824
|
+
this.seen.output += n(d.tokens?.output);
|
|
825
|
+
this.seen.reasoning += n(d.tokens?.reasoning);
|
|
826
|
+
this.seen.read += n(d.tokens?.cache?.read);
|
|
827
|
+
this.seen.write += n(d.tokens?.cache?.write);
|
|
828
|
+
const fallback = reportedCostUsd !== void 0 ? {
|
|
829
|
+
costUsd: reportedCostUsd,
|
|
830
|
+
costSource: "provider"
|
|
831
|
+
} : {};
|
|
832
|
+
let cost = fallback;
|
|
833
|
+
if (this.a.priceStep !== void 0) {
|
|
834
|
+
cost = await this.a.priceStep({
|
|
835
|
+
model: this.model,
|
|
836
|
+
usage,
|
|
837
|
+
reportedCostUsd
|
|
838
|
+
}).catch(() => fallback);
|
|
839
|
+
}
|
|
840
|
+
this.spent = addUsage(this.spent, {
|
|
841
|
+
inputTokens: usage.inputTokens,
|
|
842
|
+
outputTokens: usage.outputTokens,
|
|
843
|
+
cacheReadTokens: usage.cacheReadTokens ?? 0,
|
|
844
|
+
cacheWriteTokens: usage.cacheWriteTokens ?? 0,
|
|
845
|
+
reasoningTokens: usage.reasoningTokens ?? 0,
|
|
846
|
+
costUsd: cost.costUsd ?? 0
|
|
847
|
+
});
|
|
848
|
+
if (cost.costUsd !== void 0) this.priced = true;
|
|
740
849
|
await this.a.store.recordUsage({
|
|
741
850
|
threadId: this.a.input.threadId,
|
|
742
851
|
actorRef: this.a.input.actor.id,
|
|
743
|
-
modelId: this.
|
|
744
|
-
purpose
|
|
852
|
+
modelId: this.modelId(),
|
|
853
|
+
purpose,
|
|
745
854
|
usage,
|
|
746
|
-
...
|
|
747
|
-
costUsd,
|
|
748
|
-
costSource: "provider"
|
|
749
|
-
} : {}
|
|
855
|
+
...cost
|
|
750
856
|
});
|
|
857
|
+
return {
|
|
858
|
+
usage,
|
|
859
|
+
cost
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
/** `session.usage.recorded`: a call OpenCode made besides the steps (the title, a compaction). */
|
|
863
|
+
async usageRecorded(d) {
|
|
864
|
+
await this.spend(d, d.source === "title" ? "title" : "history_summary");
|
|
865
|
+
}
|
|
866
|
+
async stepEnded(d) {
|
|
867
|
+
const { usage, cost } = await this.spend(d, "chat");
|
|
751
868
|
if (!this.stepOpen) return;
|
|
752
869
|
this.stepOpen = false;
|
|
753
870
|
await this.write({
|
|
754
871
|
kind: "step-finish",
|
|
755
872
|
usage,
|
|
756
|
-
costUsd: costUsd ?? null,
|
|
873
|
+
costUsd: cost.costUsd ?? null,
|
|
757
874
|
...this.segment.reasoningMs > 0 ? {
|
|
758
875
|
reasoningMs: this.segment.reasoningMs
|
|
759
876
|
} : {},
|
|
760
|
-
model: this.
|
|
877
|
+
model: this.modelId()
|
|
761
878
|
});
|
|
762
879
|
}
|
|
763
880
|
/**
|
|
@@ -776,12 +893,7 @@ ${delta}` : delta;
|
|
|
776
893
|
content: s.text,
|
|
777
894
|
runId: this.a.runId,
|
|
778
895
|
...final ? {
|
|
779
|
-
usage:
|
|
780
|
-
...this.usage,
|
|
781
|
-
...this.costUsd !== void 0 ? {
|
|
782
|
-
costUsd: this.costUsd
|
|
783
|
-
} : {}
|
|
784
|
-
}
|
|
896
|
+
usage: this.messageUsage()
|
|
785
897
|
} : {},
|
|
786
898
|
...this.a.input.agentName !== void 0 ? {
|
|
787
899
|
agentName: this.a.input.agentName
|
|
@@ -809,6 +921,26 @@ ${delta}` : delta;
|
|
|
809
921
|
]);
|
|
810
922
|
return message.id;
|
|
811
923
|
}
|
|
924
|
+
/** The run's usage as its last message carries it: the whole run, cache tokens included. */
|
|
925
|
+
messageUsage() {
|
|
926
|
+
const total = this.totalUsage();
|
|
927
|
+
return {
|
|
928
|
+
inputTokens: total.inputTokens,
|
|
929
|
+
outputTokens: total.outputTokens,
|
|
930
|
+
...total.cacheReadTokens > 0 ? {
|
|
931
|
+
cacheReadTokens: total.cacheReadTokens
|
|
932
|
+
} : {},
|
|
933
|
+
...total.cacheWriteTokens > 0 ? {
|
|
934
|
+
cacheWriteTokens: total.cacheWriteTokens
|
|
935
|
+
} : {},
|
|
936
|
+
...total.reasoningTokens > 0 ? {
|
|
937
|
+
reasoningTokens: total.reasoningTokens
|
|
938
|
+
} : {},
|
|
939
|
+
...this.priced || (this.a.carried?.costUsd ?? 0) > 0 ? {
|
|
940
|
+
costUsd: total.costUsd
|
|
941
|
+
} : {}
|
|
942
|
+
};
|
|
943
|
+
}
|
|
812
944
|
/** A request recorded by an earlier process (a turn resumed after a restart). */
|
|
813
945
|
async alreadyRecorded(id) {
|
|
814
946
|
return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
|
|
@@ -1160,9 +1292,39 @@ ${delta}` : delta;
|
|
|
1160
1292
|
this.messageResults.set(messageId, results);
|
|
1161
1293
|
await this.a.store.setMessageToolResults(messageId, results);
|
|
1162
1294
|
}
|
|
1295
|
+
/** What the session spent since the baseline that no event of this turn reported. */
|
|
1296
|
+
async unreported() {
|
|
1297
|
+
const baseline = this.baseline;
|
|
1298
|
+
if (baseline === void 0) return;
|
|
1299
|
+
this.baseline = void 0;
|
|
1300
|
+
const info = await this.a.client.session.get?.({
|
|
1301
|
+
sessionID: this.a.sessionId
|
|
1302
|
+
}).catch(() => void 0);
|
|
1303
|
+
const after = info?.tokens;
|
|
1304
|
+
if (after === void 0) return;
|
|
1305
|
+
const before = baseline.tokens;
|
|
1306
|
+
const left = /* @__PURE__ */ __name((now, then, seen) => Math.max(0, (Number(now) || 0) - (Number(then) || 0) - seen), "left");
|
|
1307
|
+
const tokens = {
|
|
1308
|
+
input: left(after.input, before.input, this.seen.input),
|
|
1309
|
+
output: left(after.output, before.output, this.seen.output),
|
|
1310
|
+
reasoning: left(after.reasoning, before.reasoning, this.seen.reasoning),
|
|
1311
|
+
cache: {
|
|
1312
|
+
read: left(after.cache?.read, before.cache?.read, this.seen.read),
|
|
1313
|
+
write: left(after.cache?.write, before.cache?.write, this.seen.write)
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
const total = tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write;
|
|
1317
|
+
if (total === 0) return;
|
|
1318
|
+
const cost = left(info?.cost, baseline.cost, this.seen.cost);
|
|
1319
|
+
await this.spend({
|
|
1320
|
+
tokens,
|
|
1321
|
+
cost: cost < 1e-9 ? 0 : cost
|
|
1322
|
+
}, this.compacted ? "history_summary" : "title");
|
|
1323
|
+
}
|
|
1163
1324
|
async end(outcome) {
|
|
1164
1325
|
if (this.ended) return;
|
|
1165
1326
|
this.ended = true;
|
|
1327
|
+
await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
|
|
1166
1328
|
if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
|
|
1167
1329
|
await this.flush(true);
|
|
1168
1330
|
}
|
|
@@ -1178,6 +1340,35 @@ ${delta}` : delta;
|
|
|
1178
1340
|
});
|
|
1179
1341
|
}
|
|
1180
1342
|
};
|
|
1343
|
+
function modelOfLabel(label) {
|
|
1344
|
+
const slash = label?.indexOf("/") ?? -1;
|
|
1345
|
+
if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
|
|
1346
|
+
return {
|
|
1347
|
+
providerID: label.slice(0, slash),
|
|
1348
|
+
id: label.slice(slash + 1)
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
__name(modelOfLabel, "modelOfLabel");
|
|
1352
|
+
function usageOf(tokens) {
|
|
1353
|
+
const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
|
|
1354
|
+
const read = n(tokens?.cache?.read);
|
|
1355
|
+
const write = n(tokens?.cache?.write);
|
|
1356
|
+
const reasoning = n(tokens?.reasoning);
|
|
1357
|
+
return {
|
|
1358
|
+
inputTokens: n(tokens?.input) + read + write,
|
|
1359
|
+
outputTokens: n(tokens?.output),
|
|
1360
|
+
...read > 0 ? {
|
|
1361
|
+
cacheReadTokens: read
|
|
1362
|
+
} : {},
|
|
1363
|
+
...write > 0 ? {
|
|
1364
|
+
cacheWriteTokens: write
|
|
1365
|
+
} : {},
|
|
1366
|
+
...reasoning > 0 ? {
|
|
1367
|
+
reasoningTokens: reasoning
|
|
1368
|
+
} : {}
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
__name(usageOf, "usageOf");
|
|
1181
1372
|
|
|
1182
1373
|
// src/mcp.ts
|
|
1183
1374
|
var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -1609,7 +1800,7 @@ var OpenCodeTurns = class {
|
|
|
1609
1800
|
spent = /* @__PURE__ */ new Set();
|
|
1610
1801
|
/** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
|
|
1611
1802
|
toolsIssued = /* @__PURE__ */ new Map();
|
|
1612
|
-
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2) {
|
|
1803
|
+
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
|
|
1613
1804
|
this.host = host;
|
|
1614
1805
|
this.sessions = sessions;
|
|
1615
1806
|
this.settings = settings;
|
|
@@ -1621,6 +1812,30 @@ var OpenCodeTurns = class {
|
|
|
1621
1812
|
this.memory = memory;
|
|
1622
1813
|
this.queue = queue;
|
|
1623
1814
|
this.toolsTokens = toolsTokens2;
|
|
1815
|
+
this.prices = new import_nestjs_agent_core4.ModelPriceResolver({
|
|
1816
|
+
pricingStore,
|
|
1817
|
+
catalog: agentOptions?.priceCatalog,
|
|
1818
|
+
log: {
|
|
1819
|
+
info: /* @__PURE__ */ __name((message) => this.logger.log(message), "info"),
|
|
1820
|
+
warn: /* @__PURE__ */ __name((message) => this.logger.warn(message), "warn")
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
prices;
|
|
1825
|
+
/**
|
|
1826
|
+
* What a model call OpenCode made cost, by the same rule the library's loop records its own steps
|
|
1827
|
+
* at: a reported figure wins, else an estimate off the model's price — see
|
|
1828
|
+
* {@link OpenCodeEngineSettings.cost}.
|
|
1829
|
+
*/
|
|
1830
|
+
async priceStep(step) {
|
|
1831
|
+
const { model, usage, reportedCostUsd } = step;
|
|
1832
|
+
const tokens = usage.inputTokens + usage.outputTokens;
|
|
1833
|
+
const reported = reportedCostUsd !== void 0 && (reportedCostUsd > 0 || tokens === 0);
|
|
1834
|
+
if (reported && this.settings.cost !== "estimate") {
|
|
1835
|
+
return (0, import_nestjs_agent_core4.resolveUsageCost)(usage, reportedCostUsd, void 0);
|
|
1836
|
+
}
|
|
1837
|
+
const price = model !== void 0 && tokens > 0 ? await this.prices.priceFor(priceRef(model)) : void 0;
|
|
1838
|
+
return (0, import_nestjs_agent_core4.resolveUsageCost)(usage, price === void 0 ? reportedCostUsd : void 0, price);
|
|
1624
1839
|
}
|
|
1625
1840
|
/** The tools server's name in OpenCode. */
|
|
1626
1841
|
get toolsServer() {
|
|
@@ -2210,8 +2425,8 @@ ${body}
|
|
|
2210
2425
|
}
|
|
2211
2426
|
// ---- prompt / observe / reply --------------------------------------------------------------
|
|
2212
2427
|
/** 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);
|
|
2428
|
+
async prompt(runId, input, handle, spent) {
|
|
2429
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2215
2430
|
try {
|
|
2216
2431
|
const prompt = await this.host.promptFor?.({
|
|
2217
2432
|
input,
|
|
@@ -2226,6 +2441,7 @@ ${body}
|
|
|
2226
2441
|
})
|
|
2227
2442
|
} : {}
|
|
2228
2443
|
};
|
|
2444
|
+
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2229
2445
|
await live.client.session.prompt({
|
|
2230
2446
|
sessionID: handle.sessionId,
|
|
2231
2447
|
text: prompt.text,
|
|
@@ -2241,9 +2457,12 @@ ${body}
|
|
|
2241
2457
|
* Wait for the turn's next milestone. In a process that was not following the turn (a resumed
|
|
2242
2458
|
* durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
|
|
2243
2459
|
* `session.wait` is the safety net for a terminal event that was missed.
|
|
2460
|
+
*
|
|
2461
|
+
* `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
|
|
2462
|
+
* rebuilt here starts from it, so the answer it persists carries the whole run's usage.
|
|
2244
2463
|
*/
|
|
2245
|
-
async observe(runId, input, handle) {
|
|
2246
|
-
const live = await this.ensureLive(runId, input, handle, true);
|
|
2464
|
+
async observe(runId, input, handle, spent) {
|
|
2465
|
+
const live = await this.ensureLive(runId, input, handle, true, spent);
|
|
2247
2466
|
const { turn, client } = live;
|
|
2248
2467
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2249
2468
|
let done = false;
|
|
@@ -2282,32 +2501,43 @@ ${body}
|
|
|
2282
2501
|
* request is gone with it: a new session is opened, told what the person decided, and prompted
|
|
2283
2502
|
* to go on.
|
|
2284
2503
|
*/
|
|
2285
|
-
async reply(runId, input, handle, ask, reply) {
|
|
2504
|
+
async reply(runId, input, handle, ask, reply, spent) {
|
|
2286
2505
|
const server = await this.host.server(input.actor);
|
|
2287
2506
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2288
|
-
return this.continueAfterRestart(runId, input, server, ask, reply);
|
|
2507
|
+
return this.continueAfterRestart(runId, input, server, ask, reply, spent);
|
|
2289
2508
|
}
|
|
2290
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2509
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2291
2510
|
await live.turn.decide(ask, reply);
|
|
2292
2511
|
return handle;
|
|
2293
2512
|
}
|
|
2294
|
-
async continueAfterRestart(runId, input, server, ask, reply) {
|
|
2513
|
+
async continueAfterRestart(runId, input, server, ask, reply, spent) {
|
|
2295
2514
|
const approved = "approved" in reply && reply.approved === true;
|
|
2296
2515
|
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
2516
|
const thread = await this.store.getThread(input.threadId);
|
|
2298
2517
|
const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
|
|
2299
2518
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2300
2519
|
this.drop(runId);
|
|
2301
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2520
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2302
2521
|
await live.turn.decide(ask, reply, {
|
|
2303
2522
|
tellOpenCode: false
|
|
2304
2523
|
});
|
|
2524
|
+
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2305
2525
|
await live.client.session.prompt({
|
|
2306
2526
|
sessionID: handle.sessionId,
|
|
2307
2527
|
text: note
|
|
2308
2528
|
});
|
|
2309
2529
|
return handle;
|
|
2310
2530
|
}
|
|
2531
|
+
/** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
|
|
2532
|
+
async sessionUsage(client, sessionId) {
|
|
2533
|
+
try {
|
|
2534
|
+
return await client.session.get?.({
|
|
2535
|
+
sessionID: sessionId
|
|
2536
|
+
});
|
|
2537
|
+
} catch {
|
|
2538
|
+
return void 0;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2311
2541
|
/** Interrupt the thread's session (cancel). */
|
|
2312
2542
|
async interrupt(input) {
|
|
2313
2543
|
const known = await this.sessions.get(input.threadId);
|
|
@@ -2318,7 +2548,7 @@ ${body}
|
|
|
2318
2548
|
sessionID: known.sessionId
|
|
2319
2549
|
});
|
|
2320
2550
|
}
|
|
2321
|
-
async ensureLive(runId, input, handle, catchUp) {
|
|
2551
|
+
async ensureLive(runId, input, handle, catchUp, spent) {
|
|
2322
2552
|
const current = this.live.get(runId);
|
|
2323
2553
|
if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
|
|
2324
2554
|
current?.stop();
|
|
@@ -2344,6 +2574,10 @@ ${body}
|
|
|
2344
2574
|
} : {}
|
|
2345
2575
|
}), "approvalFor"),
|
|
2346
2576
|
modelLabel: input.model ?? "opencode",
|
|
2577
|
+
priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
|
|
2578
|
+
...spent !== void 0 ? {
|
|
2579
|
+
carried: spent
|
|
2580
|
+
} : {},
|
|
2347
2581
|
logger: this.logger,
|
|
2348
2582
|
onGranted: /* @__PURE__ */ __name((action, id) => {
|
|
2349
2583
|
const grants = this.grants.get(runId) ?? [];
|
|
@@ -2390,20 +2624,14 @@ ${body}
|
|
|
2390
2624
|
}
|
|
2391
2625
|
return milestone;
|
|
2392
2626
|
}
|
|
2393
|
-
/**
|
|
2394
|
-
|
|
2627
|
+
/**
|
|
2628
|
+
* What the run produced, as the host's settle hooks see it. `usage` is what the run spent — the
|
|
2629
|
+
* runner's sum of its milestones' usage, which a resumed run reads back from its journal — else
|
|
2630
|
+
* what the turn this process follows saw.
|
|
2631
|
+
*/
|
|
2632
|
+
async runResult(runId, input, outcome, durationMs, usage) {
|
|
2395
2633
|
const thread = await this.store.getThread(input.threadId);
|
|
2396
2634
|
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
2635
|
return {
|
|
2408
2636
|
runId,
|
|
2409
2637
|
input,
|
|
@@ -2448,15 +2676,31 @@ ${body}
|
|
|
2448
2676
|
this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
|
|
2449
2677
|
}
|
|
2450
2678
|
}
|
|
2451
|
-
/**
|
|
2452
|
-
|
|
2679
|
+
/**
|
|
2680
|
+
* What the run spent: the runner's sum of its milestones, or what the turn this process follows
|
|
2681
|
+
* saw (which starts from that sum) when it saw more — a run that failed or was cancelled between
|
|
2682
|
+
* two milestones.
|
|
2683
|
+
*/
|
|
2684
|
+
spentBy(runId, spent) {
|
|
2685
|
+
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
2686
|
+
if (spent === void 0) return seen ?? emptyUsage();
|
|
2687
|
+
if (seen === void 0) return spent;
|
|
2688
|
+
const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
|
|
2689
|
+
return tokens(seen) > tokens(spent) ? seen : spent;
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
|
|
2693
|
+
* (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
|
|
2694
|
+
*/
|
|
2695
|
+
async settle(runId, input, outcome, durationMs, spent) {
|
|
2453
2696
|
if (outcome.status === "failed") {
|
|
2454
|
-
await this.settleFailed(runId, input, outcome.error, durationMs);
|
|
2697
|
+
await this.settleFailed(runId, input, outcome.error, durationMs, spent);
|
|
2455
2698
|
return;
|
|
2456
2699
|
}
|
|
2700
|
+
const usage = this.spentBy(runId, spent);
|
|
2457
2701
|
this.forget(runId);
|
|
2458
2702
|
const writer = await this.sink.open(runId);
|
|
2459
|
-
const result = await this.runResult(runId, input, outcome, durationMs);
|
|
2703
|
+
const result = await this.runResult(runId, input, outcome, durationMs, usage);
|
|
2460
2704
|
await this.amend(writer, result);
|
|
2461
2705
|
if (outcome.status === "interrupted") {
|
|
2462
2706
|
this.logger.log(`agent run ${runId} cancelled`);
|
|
@@ -2484,13 +2728,14 @@ ${body}
|
|
|
2484
2728
|
}
|
|
2485
2729
|
await this.settled(result);
|
|
2486
2730
|
}
|
|
2487
|
-
async settleFailed(runId, input, error, durationMs = 0) {
|
|
2731
|
+
async settleFailed(runId, input, error, durationMs = 0, spent) {
|
|
2732
|
+
const usage = this.spentBy(runId, spent);
|
|
2488
2733
|
this.forget(runId);
|
|
2489
2734
|
const writer = await this.sink.open(runId);
|
|
2490
2735
|
const result = await this.runResult(runId, input, {
|
|
2491
2736
|
status: "failed",
|
|
2492
2737
|
error
|
|
2493
|
-
}, durationMs);
|
|
2738
|
+
}, durationMs, usage);
|
|
2494
2739
|
const worded = (await this.amend(writer, result)).error;
|
|
2495
2740
|
const message = worded ?? (0, import_nestjs_agent_core4.streamFailure)(new Error(error)).message;
|
|
2496
2741
|
this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
@@ -2560,6 +2805,10 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
2560
2805
|
_ts_param2(9, (0, import_common4.Inject)(import_nestjs_agent.AGENT_CHAT_QUEUE)),
|
|
2561
2806
|
_ts_param2(10, (0, import_common4.Optional)()),
|
|
2562
2807
|
_ts_param2(10, (0, import_common4.Inject)(OPENCODE_TOOLS_TOKENS)),
|
|
2808
|
+
_ts_param2(11, (0, import_common4.Optional)()),
|
|
2809
|
+
_ts_param2(11, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_PRICING_STORE)),
|
|
2810
|
+
_ts_param2(12, (0, import_common4.Optional)()),
|
|
2811
|
+
_ts_param2(12, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_OPTIONS)),
|
|
2563
2812
|
_ts_metadata2("design:type", Function),
|
|
2564
2813
|
_ts_metadata2("design:paramtypes", [
|
|
2565
2814
|
typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
|
|
@@ -2572,6 +2821,8 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
2572
2821
|
typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
|
|
2573
2822
|
typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
|
|
2574
2823
|
typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
|
|
2824
|
+
Object,
|
|
2825
|
+
typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
|
|
2575
2826
|
Object
|
|
2576
2827
|
])
|
|
2577
2828
|
], OpenCodeTurns);
|
|
@@ -2614,6 +2865,19 @@ function uiEmitter(scope, push) {
|
|
|
2614
2865
|
};
|
|
2615
2866
|
}
|
|
2616
2867
|
__name(uiEmitter, "uiEmitter");
|
|
2868
|
+
function priceRef(model) {
|
|
2869
|
+
const aliases = [
|
|
2870
|
+
model.id
|
|
2871
|
+
];
|
|
2872
|
+
const tail = model.id.slice(model.id.lastIndexOf("/") + 1);
|
|
2873
|
+
if (tail !== model.id && (0, import_nestjs_agent_core4.parseBedrockModelId)(tail) !== void 0) aliases.push(tail);
|
|
2874
|
+
return {
|
|
2875
|
+
modelId: `${model.providerID}/${model.id}`,
|
|
2876
|
+
provider: model.providerID,
|
|
2877
|
+
aliases
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
__name(priceRef, "priceRef");
|
|
2617
2881
|
|
|
2618
2882
|
// src/runner.ts
|
|
2619
2883
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
@@ -2715,18 +2979,20 @@ var OpenCodeAgentRunner = class {
|
|
|
2715
2979
|
async run(runId, input) {
|
|
2716
2980
|
const started = Date.now();
|
|
2717
2981
|
this.inputs.set(runId, input);
|
|
2982
|
+
let spent = emptyUsage();
|
|
2718
2983
|
try {
|
|
2719
2984
|
let handle = await this.turns.begin(runId, input);
|
|
2720
2985
|
if (this.cancelled.has(runId)) throw new import_nestjs_agent_core5.RunCancelledError();
|
|
2721
|
-
await this.turns.prompt(runId, input, handle);
|
|
2986
|
+
await this.turns.prompt(runId, input, handle, spent);
|
|
2722
2987
|
for (; ; ) {
|
|
2723
|
-
const milestone = await this.turns.observe(runId, input, handle);
|
|
2988
|
+
const milestone = await this.turns.observe(runId, input, handle, spent);
|
|
2989
|
+
spent = addUsage(spent, milestone.usage);
|
|
2724
2990
|
if (milestone.kind === "finished") {
|
|
2725
|
-
await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
|
|
2991
|
+
await this.turns.settle(runId, input, milestone.outcome, Date.now() - started, spent);
|
|
2726
2992
|
return;
|
|
2727
2993
|
}
|
|
2728
2994
|
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);
|
|
2995
|
+
handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
|
|
2730
2996
|
}
|
|
2731
2997
|
} catch (error) {
|
|
2732
2998
|
if (error instanceof import_nestjs_agent_core5.RunCancelledError) {
|
|
@@ -2736,7 +3002,7 @@ var OpenCodeAgentRunner = class {
|
|
|
2736
3002
|
}, Date.now() - started);
|
|
2737
3003
|
return;
|
|
2738
3004
|
}
|
|
2739
|
-
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"));
|
|
3005
|
+
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
|
|
2740
3006
|
} finally {
|
|
2741
3007
|
this.inputs.delete(runId);
|
|
2742
3008
|
}
|
|
@@ -2908,6 +3174,7 @@ var OpenCodeRunWorkflow = class {
|
|
|
2908
3174
|
this.runs = runs;
|
|
2909
3175
|
}
|
|
2910
3176
|
async run(ctx, input) {
|
|
3177
|
+
let spent = emptyUsage();
|
|
2911
3178
|
try {
|
|
2912
3179
|
const begun = await ctx.localStep("begin", async () => ({
|
|
2913
3180
|
handle: await this.turns.begin(ctx.runId, input),
|
|
@@ -2915,15 +3182,18 @@ var OpenCodeRunWorkflow = class {
|
|
|
2915
3182
|
}));
|
|
2916
3183
|
let handle = begun.handle;
|
|
2917
3184
|
await ctx.localStep("prompt", async () => {
|
|
2918
|
-
await this.turns.prompt(ctx.runId, input, handle);
|
|
3185
|
+
await this.turns.prompt(ctx.runId, input, handle, spent);
|
|
2919
3186
|
return true;
|
|
2920
3187
|
});
|
|
2921
3188
|
for (let n = 0; ; n += 1) {
|
|
2922
|
-
const
|
|
3189
|
+
const before = spent;
|
|
3190
|
+
const milestone = await ctx.localStep(`observe:${n}`, () => this.turns.observe(ctx.runId, input, handle, before));
|
|
3191
|
+
spent = addUsage(spent, milestone.usage);
|
|
3192
|
+
const total = spent;
|
|
2923
3193
|
if (milestone.kind === "finished") {
|
|
2924
3194
|
await ctx.localStep("finish", async () => {
|
|
2925
3195
|
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);
|
|
3196
|
+
else await this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt, total);
|
|
2927
3197
|
return true;
|
|
2928
3198
|
});
|
|
2929
3199
|
return {
|
|
@@ -2943,12 +3213,12 @@ var OpenCodeRunWorkflow = class {
|
|
|
2943
3213
|
};
|
|
2944
3214
|
}
|
|
2945
3215
|
const ask = milestone.ask;
|
|
2946
|
-
handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply));
|
|
3216
|
+
handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply, total));
|
|
2947
3217
|
}
|
|
2948
3218
|
} catch (error) {
|
|
2949
3219
|
if ((0, import_nestjs_durable_core.isWorkflowControlFlowSignal)(error)) throw error;
|
|
2950
3220
|
await ctx.localStep("fail", async () => {
|
|
2951
|
-
await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"));
|
|
3221
|
+
await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"), 0, spent);
|
|
2952
3222
|
return true;
|
|
2953
3223
|
});
|
|
2954
3224
|
return {
|