@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/index.js
CHANGED
|
@@ -239,6 +239,41 @@ function toFormAnswer(form, answers = {}) {
|
|
|
239
239
|
__name(toFormAnswer, "toFormAnswer");
|
|
240
240
|
|
|
241
241
|
// src/turn.ts
|
|
242
|
+
function emptyUsage() {
|
|
243
|
+
return {
|
|
244
|
+
inputTokens: 0,
|
|
245
|
+
outputTokens: 0,
|
|
246
|
+
cacheReadTokens: 0,
|
|
247
|
+
cacheWriteTokens: 0,
|
|
248
|
+
reasoningTokens: 0,
|
|
249
|
+
costUsd: 0
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
__name(emptyUsage, "emptyUsage");
|
|
253
|
+
function addUsage(a, b) {
|
|
254
|
+
const x = a ?? emptyUsage();
|
|
255
|
+
const y = b ?? emptyUsage();
|
|
256
|
+
return {
|
|
257
|
+
inputTokens: x.inputTokens + (y.inputTokens ?? 0),
|
|
258
|
+
outputTokens: x.outputTokens + (y.outputTokens ?? 0),
|
|
259
|
+
cacheReadTokens: (x.cacheReadTokens ?? 0) + (y.cacheReadTokens ?? 0),
|
|
260
|
+
cacheWriteTokens: (x.cacheWriteTokens ?? 0) + (y.cacheWriteTokens ?? 0),
|
|
261
|
+
reasoningTokens: (x.reasoningTokens ?? 0) + (y.reasoningTokens ?? 0),
|
|
262
|
+
costUsd: x.costUsd + (y.costUsd ?? 0)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
__name(addUsage, "addUsage");
|
|
266
|
+
function subUsage(a, b) {
|
|
267
|
+
return {
|
|
268
|
+
inputTokens: a.inputTokens - b.inputTokens,
|
|
269
|
+
outputTokens: a.outputTokens - b.outputTokens,
|
|
270
|
+
cacheReadTokens: a.cacheReadTokens - b.cacheReadTokens,
|
|
271
|
+
cacheWriteTokens: a.cacheWriteTokens - b.cacheWriteTokens,
|
|
272
|
+
reasoningTokens: a.reasoningTokens - b.reasoningTokens,
|
|
273
|
+
costUsd: a.costUsd - b.costUsd
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
__name(subUsage, "subUsage");
|
|
242
277
|
var ASK_TOOL_NAME = "ask";
|
|
243
278
|
var FAILED_STATUSES = /* @__PURE__ */ new Set([
|
|
244
279
|
"error",
|
|
@@ -304,11 +339,22 @@ var OpenCodeTurn = class {
|
|
|
304
339
|
hidden = /* @__PURE__ */ new Set();
|
|
305
340
|
milestones = [];
|
|
306
341
|
waiting;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
342
|
+
/** What this turn object saw spent, and how much of it a milestone already reported. */
|
|
343
|
+
spent = emptyUsage();
|
|
344
|
+
reported = emptyUsage();
|
|
345
|
+
priced = false;
|
|
346
|
+
model;
|
|
347
|
+
/** What the session had spent when this turn prompted it; what OpenCode reported since. */
|
|
348
|
+
baseline;
|
|
349
|
+
seen = {
|
|
350
|
+
cost: 0,
|
|
351
|
+
input: 0,
|
|
352
|
+
output: 0,
|
|
353
|
+
reasoning: 0,
|
|
354
|
+
read: 0,
|
|
355
|
+
write: 0
|
|
310
356
|
};
|
|
311
|
-
|
|
357
|
+
compacted = false;
|
|
312
358
|
stepOpen = false;
|
|
313
359
|
sawText = false;
|
|
314
360
|
separator = false;
|
|
@@ -318,6 +364,24 @@ var OpenCodeTurn = class {
|
|
|
318
364
|
stepError;
|
|
319
365
|
constructor(a) {
|
|
320
366
|
this.a = a;
|
|
367
|
+
this.model = modelOfLabel(a.input.model);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* What the session had spent before this turn prompted it (`session.get`). When the execution
|
|
371
|
+
* ends, what the session spent since that this turn's events did not report — the title OpenCode
|
|
372
|
+
* generates, a compaction: OpenCode records those calls on the session without streaming them — is
|
|
373
|
+
* recorded as a usage row of its own.
|
|
374
|
+
*/
|
|
375
|
+
setBaseline(info) {
|
|
376
|
+
if (info?.tokens === void 0) return;
|
|
377
|
+
this.baseline = {
|
|
378
|
+
cost: Number(info.cost) || 0,
|
|
379
|
+
tokens: info.tokens
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
/** What the run spent so far: carried from before this turn object, plus what it saw. */
|
|
383
|
+
totalUsage() {
|
|
384
|
+
return addUsage(this.a.carried, this.spent);
|
|
321
385
|
}
|
|
322
386
|
get sessionId() {
|
|
323
387
|
return this.a.sessionId;
|
|
@@ -400,7 +464,15 @@ var OpenCodeTurn = class {
|
|
|
400
464
|
this.chain = this.chain.then(() => ask.kind === "approval" ? this.decided(ask, reply, tell) : this.answered(ask, reply, tell)).then(resolve, reject);
|
|
401
465
|
});
|
|
402
466
|
}
|
|
403
|
-
reach(
|
|
467
|
+
reach(reached) {
|
|
468
|
+
const usage = subUsage(this.spent, this.reported);
|
|
469
|
+
this.reported = {
|
|
470
|
+
...this.spent
|
|
471
|
+
};
|
|
472
|
+
const milestone = {
|
|
473
|
+
...reached,
|
|
474
|
+
usage
|
|
475
|
+
};
|
|
404
476
|
const waiting = this.waiting;
|
|
405
477
|
if (waiting !== void 0) {
|
|
406
478
|
this.waiting = void 0;
|
|
@@ -556,9 +628,26 @@ var OpenCodeTurn = class {
|
|
|
556
628
|
}
|
|
557
629
|
}
|
|
558
630
|
async onEvent(e) {
|
|
559
|
-
if (this.ended) return;
|
|
560
631
|
const d = e.data ?? {};
|
|
632
|
+
if (e.type === "session.usage.recorded") {
|
|
633
|
+
await this.usageRecorded(d);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (this.ended) return;
|
|
561
637
|
switch (e.type) {
|
|
638
|
+
case "session.compaction.started":
|
|
639
|
+
case "session.compaction.ended":
|
|
640
|
+
this.compacted = true;
|
|
641
|
+
return;
|
|
642
|
+
case "session.step.started":
|
|
643
|
+
case "session.model.selected": {
|
|
644
|
+
const model = d.model;
|
|
645
|
+
if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
|
|
646
|
+
providerID: model.providerID,
|
|
647
|
+
id: model.id
|
|
648
|
+
};
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
562
651
|
case "session.text.delta": {
|
|
563
652
|
const delta = typeof d.delta === "string" ? d.delta : "";
|
|
564
653
|
if (!delta) return;
|
|
@@ -712,42 +801,70 @@ ${delta}` : delta;
|
|
|
712
801
|
});
|
|
713
802
|
}
|
|
714
803
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
this.
|
|
728
|
-
this.
|
|
729
|
-
|
|
804
|
+
modelId() {
|
|
805
|
+
return this.model !== void 0 ? `${this.model.providerID}/${this.model.id}` : this.a.modelLabel;
|
|
806
|
+
}
|
|
807
|
+
/** One model call's usage and cost, into the ledger and the run's total. */
|
|
808
|
+
async spend(d, purpose) {
|
|
809
|
+
const usage = usageOf(d.tokens);
|
|
810
|
+
const reportedCostUsd = typeof d.cost === "number" && Number.isFinite(d.cost) && d.cost >= 0 ? d.cost : void 0;
|
|
811
|
+
const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
|
|
812
|
+
this.seen.cost += reportedCostUsd ?? 0;
|
|
813
|
+
this.seen.input += n(d.tokens?.input);
|
|
814
|
+
this.seen.output += n(d.tokens?.output);
|
|
815
|
+
this.seen.reasoning += n(d.tokens?.reasoning);
|
|
816
|
+
this.seen.read += n(d.tokens?.cache?.read);
|
|
817
|
+
this.seen.write += n(d.tokens?.cache?.write);
|
|
818
|
+
const fallback = reportedCostUsd !== void 0 ? {
|
|
819
|
+
costUsd: reportedCostUsd,
|
|
820
|
+
costSource: "provider"
|
|
821
|
+
} : {};
|
|
822
|
+
let cost = fallback;
|
|
823
|
+
if (this.a.priceStep !== void 0) {
|
|
824
|
+
cost = await this.a.priceStep({
|
|
825
|
+
model: this.model,
|
|
826
|
+
usage,
|
|
827
|
+
reportedCostUsd
|
|
828
|
+
}).catch(() => fallback);
|
|
829
|
+
}
|
|
830
|
+
this.spent = addUsage(this.spent, {
|
|
831
|
+
inputTokens: usage.inputTokens,
|
|
832
|
+
outputTokens: usage.outputTokens,
|
|
833
|
+
cacheReadTokens: usage.cacheReadTokens ?? 0,
|
|
834
|
+
cacheWriteTokens: usage.cacheWriteTokens ?? 0,
|
|
835
|
+
reasoningTokens: usage.reasoningTokens ?? 0,
|
|
836
|
+
costUsd: cost.costUsd ?? 0
|
|
837
|
+
});
|
|
838
|
+
if (cost.costUsd !== void 0) this.priced = true;
|
|
730
839
|
await this.a.store.recordUsage({
|
|
731
840
|
threadId: this.a.input.threadId,
|
|
732
841
|
actorRef: this.a.input.actor.id,
|
|
733
|
-
modelId: this.
|
|
734
|
-
purpose
|
|
842
|
+
modelId: this.modelId(),
|
|
843
|
+
purpose,
|
|
735
844
|
usage,
|
|
736
|
-
...
|
|
737
|
-
costUsd,
|
|
738
|
-
costSource: "provider"
|
|
739
|
-
} : {}
|
|
845
|
+
...cost
|
|
740
846
|
});
|
|
847
|
+
return {
|
|
848
|
+
usage,
|
|
849
|
+
cost
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
/** `session.usage.recorded`: a call OpenCode made besides the steps (the title, a compaction). */
|
|
853
|
+
async usageRecorded(d) {
|
|
854
|
+
await this.spend(d, d.source === "title" ? "title" : "history_summary");
|
|
855
|
+
}
|
|
856
|
+
async stepEnded(d) {
|
|
857
|
+
const { usage, cost } = await this.spend(d, "chat");
|
|
741
858
|
if (!this.stepOpen) return;
|
|
742
859
|
this.stepOpen = false;
|
|
743
860
|
await this.write({
|
|
744
861
|
kind: "step-finish",
|
|
745
862
|
usage,
|
|
746
|
-
costUsd: costUsd ?? null,
|
|
863
|
+
costUsd: cost.costUsd ?? null,
|
|
747
864
|
...this.segment.reasoningMs > 0 ? {
|
|
748
865
|
reasoningMs: this.segment.reasoningMs
|
|
749
866
|
} : {},
|
|
750
|
-
model: this.
|
|
867
|
+
model: this.modelId()
|
|
751
868
|
});
|
|
752
869
|
}
|
|
753
870
|
/**
|
|
@@ -766,12 +883,7 @@ ${delta}` : delta;
|
|
|
766
883
|
content: s.text,
|
|
767
884
|
runId: this.a.runId,
|
|
768
885
|
...final ? {
|
|
769
|
-
usage:
|
|
770
|
-
...this.usage,
|
|
771
|
-
...this.costUsd !== void 0 ? {
|
|
772
|
-
costUsd: this.costUsd
|
|
773
|
-
} : {}
|
|
774
|
-
}
|
|
886
|
+
usage: this.messageUsage()
|
|
775
887
|
} : {},
|
|
776
888
|
...this.a.input.agentName !== void 0 ? {
|
|
777
889
|
agentName: this.a.input.agentName
|
|
@@ -799,6 +911,26 @@ ${delta}` : delta;
|
|
|
799
911
|
]);
|
|
800
912
|
return message.id;
|
|
801
913
|
}
|
|
914
|
+
/** The run's usage as its last message carries it: the whole run, cache tokens included. */
|
|
915
|
+
messageUsage() {
|
|
916
|
+
const total = this.totalUsage();
|
|
917
|
+
return {
|
|
918
|
+
inputTokens: total.inputTokens,
|
|
919
|
+
outputTokens: total.outputTokens,
|
|
920
|
+
...total.cacheReadTokens > 0 ? {
|
|
921
|
+
cacheReadTokens: total.cacheReadTokens
|
|
922
|
+
} : {},
|
|
923
|
+
...total.cacheWriteTokens > 0 ? {
|
|
924
|
+
cacheWriteTokens: total.cacheWriteTokens
|
|
925
|
+
} : {},
|
|
926
|
+
...total.reasoningTokens > 0 ? {
|
|
927
|
+
reasoningTokens: total.reasoningTokens
|
|
928
|
+
} : {},
|
|
929
|
+
...this.priced || (this.a.carried?.costUsd ?? 0) > 0 ? {
|
|
930
|
+
costUsd: total.costUsd
|
|
931
|
+
} : {}
|
|
932
|
+
};
|
|
933
|
+
}
|
|
802
934
|
/** A request recorded by an earlier process (a turn resumed after a restart). */
|
|
803
935
|
async alreadyRecorded(id) {
|
|
804
936
|
return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
|
|
@@ -1150,9 +1282,39 @@ ${delta}` : delta;
|
|
|
1150
1282
|
this.messageResults.set(messageId, results);
|
|
1151
1283
|
await this.a.store.setMessageToolResults(messageId, results);
|
|
1152
1284
|
}
|
|
1285
|
+
/** What the session spent since the baseline that no event of this turn reported. */
|
|
1286
|
+
async unreported() {
|
|
1287
|
+
const baseline = this.baseline;
|
|
1288
|
+
if (baseline === void 0) return;
|
|
1289
|
+
this.baseline = void 0;
|
|
1290
|
+
const info = await this.a.client.session.get?.({
|
|
1291
|
+
sessionID: this.a.sessionId
|
|
1292
|
+
}).catch(() => void 0);
|
|
1293
|
+
const after = info?.tokens;
|
|
1294
|
+
if (after === void 0) return;
|
|
1295
|
+
const before = baseline.tokens;
|
|
1296
|
+
const left = /* @__PURE__ */ __name((now, then, seen) => Math.max(0, (Number(now) || 0) - (Number(then) || 0) - seen), "left");
|
|
1297
|
+
const tokens = {
|
|
1298
|
+
input: left(after.input, before.input, this.seen.input),
|
|
1299
|
+
output: left(after.output, before.output, this.seen.output),
|
|
1300
|
+
reasoning: left(after.reasoning, before.reasoning, this.seen.reasoning),
|
|
1301
|
+
cache: {
|
|
1302
|
+
read: left(after.cache?.read, before.cache?.read, this.seen.read),
|
|
1303
|
+
write: left(after.cache?.write, before.cache?.write, this.seen.write)
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
const total = tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write;
|
|
1307
|
+
if (total === 0) return;
|
|
1308
|
+
const cost = left(info?.cost, baseline.cost, this.seen.cost);
|
|
1309
|
+
await this.spend({
|
|
1310
|
+
tokens,
|
|
1311
|
+
cost: cost < 1e-9 ? 0 : cost
|
|
1312
|
+
}, this.compacted ? "history_summary" : "title");
|
|
1313
|
+
}
|
|
1153
1314
|
async end(outcome) {
|
|
1154
1315
|
if (this.ended) return;
|
|
1155
1316
|
this.ended = true;
|
|
1317
|
+
await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
|
|
1156
1318
|
if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
|
|
1157
1319
|
await this.flush(true);
|
|
1158
1320
|
}
|
|
@@ -1168,6 +1330,35 @@ ${delta}` : delta;
|
|
|
1168
1330
|
});
|
|
1169
1331
|
}
|
|
1170
1332
|
};
|
|
1333
|
+
function modelOfLabel(label) {
|
|
1334
|
+
const slash = label?.indexOf("/") ?? -1;
|
|
1335
|
+
if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
|
|
1336
|
+
return {
|
|
1337
|
+
providerID: label.slice(0, slash),
|
|
1338
|
+
id: label.slice(slash + 1)
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
__name(modelOfLabel, "modelOfLabel");
|
|
1342
|
+
function usageOf(tokens) {
|
|
1343
|
+
const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
|
|
1344
|
+
const read = n(tokens?.cache?.read);
|
|
1345
|
+
const write = n(tokens?.cache?.write);
|
|
1346
|
+
const reasoning = n(tokens?.reasoning);
|
|
1347
|
+
return {
|
|
1348
|
+
inputTokens: n(tokens?.input) + read + write,
|
|
1349
|
+
outputTokens: n(tokens?.output),
|
|
1350
|
+
...read > 0 ? {
|
|
1351
|
+
cacheReadTokens: read
|
|
1352
|
+
} : {},
|
|
1353
|
+
...write > 0 ? {
|
|
1354
|
+
cacheWriteTokens: write
|
|
1355
|
+
} : {},
|
|
1356
|
+
...reasoning > 0 ? {
|
|
1357
|
+
reasoningTokens: reasoning
|
|
1358
|
+
} : {}
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
__name(usageOf, "usageOf");
|
|
1171
1362
|
|
|
1172
1363
|
// src/mcp.ts
|
|
1173
1364
|
var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -1432,7 +1623,7 @@ import { Inject as Inject3, Injectable as Injectable2 } from "@nestjs/common";
|
|
|
1432
1623
|
// src/turns.ts
|
|
1433
1624
|
import { randomUUID } from "crypto";
|
|
1434
1625
|
import { AGENT_CHAT_QUEUE } from "@dudousxd/nestjs-agent";
|
|
1435
|
-
import { AGENT_DEPS_FACTORY, AGENT_MEMORY, AGENT_SINK, AGENT_SKILLS, AGENT_STORE, AGENT_TOOL_REGISTRY, DEFAULT_MAX_FACT_CHARS, DefaultApprovalPolicy, RUN_ENDED_BEFORE_TOOL_CALL, actorScope, buildMemoryBlock, defaultScopeResolver, encodeStreamEvent as encodeStreamEvent2, memoryWriteVerdict, publishAgentRunFailed, releaseThreadRun, resolveMemoryDigest, resolveSkillCatalog, streamFailure } from "@dudousxd/nestjs-agent-core";
|
|
1626
|
+
import { AGENT_DEPS_FACTORY, AGENT_MEMORY, AGENT_OPTIONS, AGENT_PRICING_STORE, AGENT_SINK, AGENT_SKILLS, AGENT_STORE, AGENT_TOOL_REGISTRY, DEFAULT_MAX_FACT_CHARS, DefaultApprovalPolicy, ModelPriceResolver, RUN_ENDED_BEFORE_TOOL_CALL, actorScope, buildMemoryBlock, defaultScopeResolver, encodeStreamEvent as encodeStreamEvent2, memoryWriteVerdict, parseBedrockModelId, publishAgentRunFailed, releaseThreadRun, resolveMemoryDigest, resolveSkillCatalog, resolveUsageCost, streamFailure } from "@dudousxd/nestjs-agent-core";
|
|
1436
1627
|
import { Inject as Inject2, Injectable, Logger as Logger2, Optional } from "@nestjs/common";
|
|
1437
1628
|
|
|
1438
1629
|
// src/event-hub.ts
|
|
@@ -1599,7 +1790,7 @@ var OpenCodeTurns = class {
|
|
|
1599
1790
|
spent = /* @__PURE__ */ new Set();
|
|
1600
1791
|
/** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
|
|
1601
1792
|
toolsIssued = /* @__PURE__ */ new Map();
|
|
1602
|
-
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2) {
|
|
1793
|
+
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
|
|
1603
1794
|
this.host = host;
|
|
1604
1795
|
this.sessions = sessions;
|
|
1605
1796
|
this.settings = settings;
|
|
@@ -1611,6 +1802,30 @@ var OpenCodeTurns = class {
|
|
|
1611
1802
|
this.memory = memory;
|
|
1612
1803
|
this.queue = queue;
|
|
1613
1804
|
this.toolsTokens = toolsTokens2;
|
|
1805
|
+
this.prices = new ModelPriceResolver({
|
|
1806
|
+
pricingStore,
|
|
1807
|
+
catalog: agentOptions?.priceCatalog,
|
|
1808
|
+
log: {
|
|
1809
|
+
info: /* @__PURE__ */ __name((message) => this.logger.log(message), "info"),
|
|
1810
|
+
warn: /* @__PURE__ */ __name((message) => this.logger.warn(message), "warn")
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
prices;
|
|
1815
|
+
/**
|
|
1816
|
+
* What a model call OpenCode made cost, by the same rule the library's loop records its own steps
|
|
1817
|
+
* at: a reported figure wins, else an estimate off the model's price — see
|
|
1818
|
+
* {@link OpenCodeEngineSettings.cost}.
|
|
1819
|
+
*/
|
|
1820
|
+
async priceStep(step) {
|
|
1821
|
+
const { model, usage, reportedCostUsd } = step;
|
|
1822
|
+
const tokens = usage.inputTokens + usage.outputTokens;
|
|
1823
|
+
const reported = reportedCostUsd !== void 0 && (reportedCostUsd > 0 || tokens === 0);
|
|
1824
|
+
if (reported && this.settings.cost !== "estimate") {
|
|
1825
|
+
return resolveUsageCost(usage, reportedCostUsd, void 0);
|
|
1826
|
+
}
|
|
1827
|
+
const price = model !== void 0 && tokens > 0 ? await this.prices.priceFor(priceRef(model)) : void 0;
|
|
1828
|
+
return resolveUsageCost(usage, price === void 0 ? reportedCostUsd : void 0, price);
|
|
1614
1829
|
}
|
|
1615
1830
|
/** The tools server's name in OpenCode. */
|
|
1616
1831
|
get toolsServer() {
|
|
@@ -2200,8 +2415,8 @@ ${body}
|
|
|
2200
2415
|
}
|
|
2201
2416
|
// ---- prompt / observe / reply --------------------------------------------------------------
|
|
2202
2417
|
/** Start listening to the session (so nothing it says is missed), then send the user message. */
|
|
2203
|
-
async prompt(runId, input, handle) {
|
|
2204
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2418
|
+
async prompt(runId, input, handle, spent) {
|
|
2419
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2205
2420
|
try {
|
|
2206
2421
|
const prompt = await this.host.promptFor?.({
|
|
2207
2422
|
input,
|
|
@@ -2216,6 +2431,7 @@ ${body}
|
|
|
2216
2431
|
})
|
|
2217
2432
|
} : {}
|
|
2218
2433
|
};
|
|
2434
|
+
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2219
2435
|
await live.client.session.prompt({
|
|
2220
2436
|
sessionID: handle.sessionId,
|
|
2221
2437
|
text: prompt.text,
|
|
@@ -2231,9 +2447,12 @@ ${body}
|
|
|
2231
2447
|
* Wait for the turn's next milestone. In a process that was not following the turn (a resumed
|
|
2232
2448
|
* durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
|
|
2233
2449
|
* `session.wait` is the safety net for a terminal event that was missed.
|
|
2450
|
+
*
|
|
2451
|
+
* `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
|
|
2452
|
+
* rebuilt here starts from it, so the answer it persists carries the whole run's usage.
|
|
2234
2453
|
*/
|
|
2235
|
-
async observe(runId, input, handle) {
|
|
2236
|
-
const live = await this.ensureLive(runId, input, handle, true);
|
|
2454
|
+
async observe(runId, input, handle, spent) {
|
|
2455
|
+
const live = await this.ensureLive(runId, input, handle, true, spent);
|
|
2237
2456
|
const { turn, client } = live;
|
|
2238
2457
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2239
2458
|
let done = false;
|
|
@@ -2272,32 +2491,43 @@ ${body}
|
|
|
2272
2491
|
* request is gone with it: a new session is opened, told what the person decided, and prompted
|
|
2273
2492
|
* to go on.
|
|
2274
2493
|
*/
|
|
2275
|
-
async reply(runId, input, handle, ask, reply) {
|
|
2494
|
+
async reply(runId, input, handle, ask, reply, spent) {
|
|
2276
2495
|
const server = await this.host.server(input.actor);
|
|
2277
2496
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2278
|
-
return this.continueAfterRestart(runId, input, server, ask, reply);
|
|
2497
|
+
return this.continueAfterRestart(runId, input, server, ask, reply, spent);
|
|
2279
2498
|
}
|
|
2280
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2499
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2281
2500
|
await live.turn.decide(ask, reply);
|
|
2282
2501
|
return handle;
|
|
2283
2502
|
}
|
|
2284
|
-
async continueAfterRestart(runId, input, server, ask, reply) {
|
|
2503
|
+
async continueAfterRestart(runId, input, server, ask, reply, spent) {
|
|
2285
2504
|
const approved = "approved" in reply && reply.approved === true;
|
|
2286
2505
|
const note = ask.kind === "approval" ? approved ? `The user approved the action ${ask.action} you asked permission for. Continue the task.` : `The user did not approve the action ${ask.action}. Continue without it and explain.` : `The user answered your question: ${JSON.stringify("answers" in reply ? reply.answers : {})}. Continue.`;
|
|
2287
2506
|
const thread = await this.store.getThread(input.threadId);
|
|
2288
2507
|
const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
|
|
2289
2508
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2290
2509
|
this.drop(runId);
|
|
2291
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2510
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2292
2511
|
await live.turn.decide(ask, reply, {
|
|
2293
2512
|
tellOpenCode: false
|
|
2294
2513
|
});
|
|
2514
|
+
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2295
2515
|
await live.client.session.prompt({
|
|
2296
2516
|
sessionID: handle.sessionId,
|
|
2297
2517
|
text: note
|
|
2298
2518
|
});
|
|
2299
2519
|
return handle;
|
|
2300
2520
|
}
|
|
2521
|
+
/** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
|
|
2522
|
+
async sessionUsage(client, sessionId) {
|
|
2523
|
+
try {
|
|
2524
|
+
return await client.session.get?.({
|
|
2525
|
+
sessionID: sessionId
|
|
2526
|
+
});
|
|
2527
|
+
} catch {
|
|
2528
|
+
return void 0;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2301
2531
|
/** Interrupt the thread's session (cancel). */
|
|
2302
2532
|
async interrupt(input) {
|
|
2303
2533
|
const known = await this.sessions.get(input.threadId);
|
|
@@ -2308,7 +2538,7 @@ ${body}
|
|
|
2308
2538
|
sessionID: known.sessionId
|
|
2309
2539
|
});
|
|
2310
2540
|
}
|
|
2311
|
-
async ensureLive(runId, input, handle, catchUp) {
|
|
2541
|
+
async ensureLive(runId, input, handle, catchUp, spent) {
|
|
2312
2542
|
const current = this.live.get(runId);
|
|
2313
2543
|
if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
|
|
2314
2544
|
current?.stop();
|
|
@@ -2334,6 +2564,10 @@ ${body}
|
|
|
2334
2564
|
} : {}
|
|
2335
2565
|
}), "approvalFor"),
|
|
2336
2566
|
modelLabel: input.model ?? "opencode",
|
|
2567
|
+
priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
|
|
2568
|
+
...spent !== void 0 ? {
|
|
2569
|
+
carried: spent
|
|
2570
|
+
} : {},
|
|
2337
2571
|
logger: this.logger,
|
|
2338
2572
|
onGranted: /* @__PURE__ */ __name((action, id) => {
|
|
2339
2573
|
const grants = this.grants.get(runId) ?? [];
|
|
@@ -2380,20 +2614,14 @@ ${body}
|
|
|
2380
2614
|
}
|
|
2381
2615
|
return milestone;
|
|
2382
2616
|
}
|
|
2383
|
-
/**
|
|
2384
|
-
|
|
2617
|
+
/**
|
|
2618
|
+
* What the run produced, as the host's settle hooks see it. `usage` is what the run spent — the
|
|
2619
|
+
* runner's sum of its milestones' usage, which a resumed run reads back from its journal — else
|
|
2620
|
+
* what the turn this process follows saw.
|
|
2621
|
+
*/
|
|
2622
|
+
async runResult(runId, input, outcome, durationMs, usage) {
|
|
2385
2623
|
const thread = await this.store.getThread(input.threadId);
|
|
2386
2624
|
const messages = (thread?.messages ?? []).filter((m) => m.role === "assistant" && m.runId === runId);
|
|
2387
|
-
const usage = {
|
|
2388
|
-
inputTokens: 0,
|
|
2389
|
-
outputTokens: 0,
|
|
2390
|
-
costUsd: 0
|
|
2391
|
-
};
|
|
2392
|
-
for (const m of messages) {
|
|
2393
|
-
usage.inputTokens += m.usage?.inputTokens ?? 0;
|
|
2394
|
-
usage.outputTokens += m.usage?.outputTokens ?? 0;
|
|
2395
|
-
usage.costUsd += m.usage?.costUsd ?? 0;
|
|
2396
|
-
}
|
|
2397
2625
|
return {
|
|
2398
2626
|
runId,
|
|
2399
2627
|
input,
|
|
@@ -2438,15 +2666,31 @@ ${body}
|
|
|
2438
2666
|
this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
|
|
2439
2667
|
}
|
|
2440
2668
|
}
|
|
2441
|
-
/**
|
|
2442
|
-
|
|
2669
|
+
/**
|
|
2670
|
+
* What the run spent: the runner's sum of its milestones, or what the turn this process follows
|
|
2671
|
+
* saw (which starts from that sum) when it saw more — a run that failed or was cancelled between
|
|
2672
|
+
* two milestones.
|
|
2673
|
+
*/
|
|
2674
|
+
spentBy(runId, spent) {
|
|
2675
|
+
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
2676
|
+
if (spent === void 0) return seen ?? emptyUsage();
|
|
2677
|
+
if (seen === void 0) return spent;
|
|
2678
|
+
const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
|
|
2679
|
+
return tokens(seen) > tokens(spent) ? seen : spent;
|
|
2680
|
+
}
|
|
2681
|
+
/**
|
|
2682
|
+
* End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
|
|
2683
|
+
* (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
|
|
2684
|
+
*/
|
|
2685
|
+
async settle(runId, input, outcome, durationMs, spent) {
|
|
2443
2686
|
if (outcome.status === "failed") {
|
|
2444
|
-
await this.settleFailed(runId, input, outcome.error, durationMs);
|
|
2687
|
+
await this.settleFailed(runId, input, outcome.error, durationMs, spent);
|
|
2445
2688
|
return;
|
|
2446
2689
|
}
|
|
2690
|
+
const usage = this.spentBy(runId, spent);
|
|
2447
2691
|
this.forget(runId);
|
|
2448
2692
|
const writer = await this.sink.open(runId);
|
|
2449
|
-
const result = await this.runResult(runId, input, outcome, durationMs);
|
|
2693
|
+
const result = await this.runResult(runId, input, outcome, durationMs, usage);
|
|
2450
2694
|
await this.amend(writer, result);
|
|
2451
2695
|
if (outcome.status === "interrupted") {
|
|
2452
2696
|
this.logger.log(`agent run ${runId} cancelled`);
|
|
@@ -2474,13 +2718,14 @@ ${body}
|
|
|
2474
2718
|
}
|
|
2475
2719
|
await this.settled(result);
|
|
2476
2720
|
}
|
|
2477
|
-
async settleFailed(runId, input, error, durationMs = 0) {
|
|
2721
|
+
async settleFailed(runId, input, error, durationMs = 0, spent) {
|
|
2722
|
+
const usage = this.spentBy(runId, spent);
|
|
2478
2723
|
this.forget(runId);
|
|
2479
2724
|
const writer = await this.sink.open(runId);
|
|
2480
2725
|
const result = await this.runResult(runId, input, {
|
|
2481
2726
|
status: "failed",
|
|
2482
2727
|
error
|
|
2483
|
-
}, durationMs);
|
|
2728
|
+
}, durationMs, usage);
|
|
2484
2729
|
const worded = (await this.amend(writer, result)).error;
|
|
2485
2730
|
const message = worded ?? streamFailure(new Error(error)).message;
|
|
2486
2731
|
this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
@@ -2550,6 +2795,10 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
2550
2795
|
_ts_param2(9, Inject2(AGENT_CHAT_QUEUE)),
|
|
2551
2796
|
_ts_param2(10, Optional()),
|
|
2552
2797
|
_ts_param2(10, Inject2(OPENCODE_TOOLS_TOKENS)),
|
|
2798
|
+
_ts_param2(11, Optional()),
|
|
2799
|
+
_ts_param2(11, Inject2(AGENT_PRICING_STORE)),
|
|
2800
|
+
_ts_param2(12, Optional()),
|
|
2801
|
+
_ts_param2(12, Inject2(AGENT_OPTIONS)),
|
|
2553
2802
|
_ts_metadata2("design:type", Function),
|
|
2554
2803
|
_ts_metadata2("design:paramtypes", [
|
|
2555
2804
|
typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
|
|
@@ -2562,6 +2811,8 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
2562
2811
|
typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
|
|
2563
2812
|
typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
|
|
2564
2813
|
typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
|
|
2814
|
+
Object,
|
|
2815
|
+
typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
|
|
2565
2816
|
Object
|
|
2566
2817
|
])
|
|
2567
2818
|
], OpenCodeTurns);
|
|
@@ -2604,6 +2855,19 @@ function uiEmitter(scope, push) {
|
|
|
2604
2855
|
};
|
|
2605
2856
|
}
|
|
2606
2857
|
__name(uiEmitter, "uiEmitter");
|
|
2858
|
+
function priceRef(model) {
|
|
2859
|
+
const aliases = [
|
|
2860
|
+
model.id
|
|
2861
|
+
];
|
|
2862
|
+
const tail = model.id.slice(model.id.lastIndexOf("/") + 1);
|
|
2863
|
+
if (tail !== model.id && parseBedrockModelId(tail) !== void 0) aliases.push(tail);
|
|
2864
|
+
return {
|
|
2865
|
+
modelId: `${model.providerID}/${model.id}`,
|
|
2866
|
+
provider: model.providerID,
|
|
2867
|
+
aliases
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2870
|
+
__name(priceRef, "priceRef");
|
|
2607
2871
|
|
|
2608
2872
|
// src/runner.ts
|
|
2609
2873
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
@@ -2705,18 +2969,20 @@ var OpenCodeAgentRunner = class {
|
|
|
2705
2969
|
async run(runId, input) {
|
|
2706
2970
|
const started = Date.now();
|
|
2707
2971
|
this.inputs.set(runId, input);
|
|
2972
|
+
let spent = emptyUsage();
|
|
2708
2973
|
try {
|
|
2709
2974
|
let handle = await this.turns.begin(runId, input);
|
|
2710
2975
|
if (this.cancelled.has(runId)) throw new RunCancelledError();
|
|
2711
|
-
await this.turns.prompt(runId, input, handle);
|
|
2976
|
+
await this.turns.prompt(runId, input, handle, spent);
|
|
2712
2977
|
for (; ; ) {
|
|
2713
|
-
const milestone = await this.turns.observe(runId, input, handle);
|
|
2978
|
+
const milestone = await this.turns.observe(runId, input, handle, spent);
|
|
2979
|
+
spent = addUsage(spent, milestone.usage);
|
|
2714
2980
|
if (milestone.kind === "finished") {
|
|
2715
|
-
await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
|
|
2981
|
+
await this.turns.settle(runId, input, milestone.outcome, Date.now() - started, spent);
|
|
2716
2982
|
return;
|
|
2717
2983
|
}
|
|
2718
2984
|
const reply = await this.park(runId, milestone.ask.id, milestone.ask.kind === "approval" ? "approval" : "answers", milestone.timeoutMs);
|
|
2719
|
-
handle = await this.turns.reply(runId, input, handle, milestone.ask, reply);
|
|
2985
|
+
handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
|
|
2720
2986
|
}
|
|
2721
2987
|
} catch (error) {
|
|
2722
2988
|
if (error instanceof RunCancelledError) {
|
|
@@ -2726,7 +2992,7 @@ var OpenCodeAgentRunner = class {
|
|
|
2726
2992
|
}, Date.now() - started);
|
|
2727
2993
|
return;
|
|
2728
2994
|
}
|
|
2729
|
-
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"));
|
|
2995
|
+
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
|
|
2730
2996
|
} finally {
|
|
2731
2997
|
this.inputs.delete(runId);
|
|
2732
2998
|
}
|
|
@@ -2878,6 +3144,8 @@ export {
|
|
|
2878
3144
|
OpenCodeToolsTokens,
|
|
2879
3145
|
OpenCodeTurn,
|
|
2880
3146
|
OpenCodeTurns,
|
|
3147
|
+
addUsage,
|
|
3148
|
+
emptyUsage,
|
|
2881
3149
|
keyValueOpenCodeSessionStore,
|
|
2882
3150
|
openCode,
|
|
2883
3151
|
openCodeControllers,
|
|
@@ -2885,6 +3153,7 @@ export {
|
|
|
2885
3153
|
sessionOf,
|
|
2886
3154
|
toElicitation,
|
|
2887
3155
|
toFormAnswer,
|
|
2888
|
-
toQuestion
|
|
3156
|
+
toQuestion,
|
|
3157
|
+
usageOf
|
|
2889
3158
|
};
|
|
2890
3159
|
//# sourceMappingURL=index.js.map
|