@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.js
CHANGED
|
@@ -221,6 +221,41 @@ function toFormAnswer(form, answers = {}) {
|
|
|
221
221
|
__name(toFormAnswer, "toFormAnswer");
|
|
222
222
|
|
|
223
223
|
// src/turn.ts
|
|
224
|
+
function emptyUsage() {
|
|
225
|
+
return {
|
|
226
|
+
inputTokens: 0,
|
|
227
|
+
outputTokens: 0,
|
|
228
|
+
cacheReadTokens: 0,
|
|
229
|
+
cacheWriteTokens: 0,
|
|
230
|
+
reasoningTokens: 0,
|
|
231
|
+
costUsd: 0
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
__name(emptyUsage, "emptyUsage");
|
|
235
|
+
function addUsage(a, b) {
|
|
236
|
+
const x = a ?? emptyUsage();
|
|
237
|
+
const y = b ?? emptyUsage();
|
|
238
|
+
return {
|
|
239
|
+
inputTokens: x.inputTokens + (y.inputTokens ?? 0),
|
|
240
|
+
outputTokens: x.outputTokens + (y.outputTokens ?? 0),
|
|
241
|
+
cacheReadTokens: (x.cacheReadTokens ?? 0) + (y.cacheReadTokens ?? 0),
|
|
242
|
+
cacheWriteTokens: (x.cacheWriteTokens ?? 0) + (y.cacheWriteTokens ?? 0),
|
|
243
|
+
reasoningTokens: (x.reasoningTokens ?? 0) + (y.reasoningTokens ?? 0),
|
|
244
|
+
costUsd: x.costUsd + (y.costUsd ?? 0)
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
__name(addUsage, "addUsage");
|
|
248
|
+
function subUsage(a, b) {
|
|
249
|
+
return {
|
|
250
|
+
inputTokens: a.inputTokens - b.inputTokens,
|
|
251
|
+
outputTokens: a.outputTokens - b.outputTokens,
|
|
252
|
+
cacheReadTokens: a.cacheReadTokens - b.cacheReadTokens,
|
|
253
|
+
cacheWriteTokens: a.cacheWriteTokens - b.cacheWriteTokens,
|
|
254
|
+
reasoningTokens: a.reasoningTokens - b.reasoningTokens,
|
|
255
|
+
costUsd: a.costUsd - b.costUsd
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
__name(subUsage, "subUsage");
|
|
224
259
|
var ASK_TOOL_NAME = "ask";
|
|
225
260
|
var FAILED_STATUSES = /* @__PURE__ */ new Set([
|
|
226
261
|
"error",
|
|
@@ -286,11 +321,22 @@ var OpenCodeTurn = class {
|
|
|
286
321
|
hidden = /* @__PURE__ */ new Set();
|
|
287
322
|
milestones = [];
|
|
288
323
|
waiting;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
324
|
+
/** What this turn object saw spent, and how much of it a milestone already reported. */
|
|
325
|
+
spent = emptyUsage();
|
|
326
|
+
reported = emptyUsage();
|
|
327
|
+
priced = false;
|
|
328
|
+
model;
|
|
329
|
+
/** What the session had spent when this turn prompted it; what OpenCode reported since. */
|
|
330
|
+
baseline;
|
|
331
|
+
seen = {
|
|
332
|
+
cost: 0,
|
|
333
|
+
input: 0,
|
|
334
|
+
output: 0,
|
|
335
|
+
reasoning: 0,
|
|
336
|
+
read: 0,
|
|
337
|
+
write: 0
|
|
292
338
|
};
|
|
293
|
-
|
|
339
|
+
compacted = false;
|
|
294
340
|
stepOpen = false;
|
|
295
341
|
sawText = false;
|
|
296
342
|
separator = false;
|
|
@@ -300,6 +346,24 @@ var OpenCodeTurn = class {
|
|
|
300
346
|
stepError;
|
|
301
347
|
constructor(a) {
|
|
302
348
|
this.a = a;
|
|
349
|
+
this.model = modelOfLabel(a.input.model);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* What the session had spent before this turn prompted it (`session.get`). When the execution
|
|
353
|
+
* ends, what the session spent since that this turn's events did not report — the title OpenCode
|
|
354
|
+
* generates, a compaction: OpenCode records those calls on the session without streaming them — is
|
|
355
|
+
* recorded as a usage row of its own.
|
|
356
|
+
*/
|
|
357
|
+
setBaseline(info) {
|
|
358
|
+
if (info?.tokens === void 0) return;
|
|
359
|
+
this.baseline = {
|
|
360
|
+
cost: Number(info.cost) || 0,
|
|
361
|
+
tokens: info.tokens
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/** What the run spent so far: carried from before this turn object, plus what it saw. */
|
|
365
|
+
totalUsage() {
|
|
366
|
+
return addUsage(this.a.carried, this.spent);
|
|
303
367
|
}
|
|
304
368
|
get sessionId() {
|
|
305
369
|
return this.a.sessionId;
|
|
@@ -382,7 +446,15 @@ var OpenCodeTurn = class {
|
|
|
382
446
|
this.chain = this.chain.then(() => ask.kind === "approval" ? this.decided(ask, reply, tell) : this.answered(ask, reply, tell)).then(resolve, reject);
|
|
383
447
|
});
|
|
384
448
|
}
|
|
385
|
-
reach(
|
|
449
|
+
reach(reached) {
|
|
450
|
+
const usage = subUsage(this.spent, this.reported);
|
|
451
|
+
this.reported = {
|
|
452
|
+
...this.spent
|
|
453
|
+
};
|
|
454
|
+
const milestone = {
|
|
455
|
+
...reached,
|
|
456
|
+
usage
|
|
457
|
+
};
|
|
386
458
|
const waiting = this.waiting;
|
|
387
459
|
if (waiting !== void 0) {
|
|
388
460
|
this.waiting = void 0;
|
|
@@ -538,9 +610,26 @@ var OpenCodeTurn = class {
|
|
|
538
610
|
}
|
|
539
611
|
}
|
|
540
612
|
async onEvent(e) {
|
|
541
|
-
if (this.ended) return;
|
|
542
613
|
const d = e.data ?? {};
|
|
614
|
+
if (e.type === "session.usage.recorded") {
|
|
615
|
+
await this.usageRecorded(d);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (this.ended) return;
|
|
543
619
|
switch (e.type) {
|
|
620
|
+
case "session.compaction.started":
|
|
621
|
+
case "session.compaction.ended":
|
|
622
|
+
this.compacted = true;
|
|
623
|
+
return;
|
|
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
|
+
}
|
|
544
633
|
case "session.text.delta": {
|
|
545
634
|
const delta = typeof d.delta === "string" ? d.delta : "";
|
|
546
635
|
if (!delta) return;
|
|
@@ -694,42 +783,70 @@ ${delta}` : delta;
|
|
|
694
783
|
});
|
|
695
784
|
}
|
|
696
785
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
this.
|
|
710
|
-
this.
|
|
711
|
-
|
|
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 n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
|
|
794
|
+
this.seen.cost += reportedCostUsd ?? 0;
|
|
795
|
+
this.seen.input += n(d.tokens?.input);
|
|
796
|
+
this.seen.output += n(d.tokens?.output);
|
|
797
|
+
this.seen.reasoning += n(d.tokens?.reasoning);
|
|
798
|
+
this.seen.read += n(d.tokens?.cache?.read);
|
|
799
|
+
this.seen.write += n(d.tokens?.cache?.write);
|
|
800
|
+
const fallback = reportedCostUsd !== void 0 ? {
|
|
801
|
+
costUsd: reportedCostUsd,
|
|
802
|
+
costSource: "provider"
|
|
803
|
+
} : {};
|
|
804
|
+
let cost = fallback;
|
|
805
|
+
if (this.a.priceStep !== void 0) {
|
|
806
|
+
cost = await this.a.priceStep({
|
|
807
|
+
model: this.model,
|
|
808
|
+
usage,
|
|
809
|
+
reportedCostUsd
|
|
810
|
+
}).catch(() => fallback);
|
|
811
|
+
}
|
|
812
|
+
this.spent = addUsage(this.spent, {
|
|
813
|
+
inputTokens: usage.inputTokens,
|
|
814
|
+
outputTokens: usage.outputTokens,
|
|
815
|
+
cacheReadTokens: usage.cacheReadTokens ?? 0,
|
|
816
|
+
cacheWriteTokens: usage.cacheWriteTokens ?? 0,
|
|
817
|
+
reasoningTokens: usage.reasoningTokens ?? 0,
|
|
818
|
+
costUsd: cost.costUsd ?? 0
|
|
819
|
+
});
|
|
820
|
+
if (cost.costUsd !== void 0) this.priced = true;
|
|
712
821
|
await this.a.store.recordUsage({
|
|
713
822
|
threadId: this.a.input.threadId,
|
|
714
823
|
actorRef: this.a.input.actor.id,
|
|
715
|
-
modelId: this.
|
|
716
|
-
purpose
|
|
824
|
+
modelId: this.modelId(),
|
|
825
|
+
purpose,
|
|
717
826
|
usage,
|
|
718
|
-
...
|
|
719
|
-
costUsd,
|
|
720
|
-
costSource: "provider"
|
|
721
|
-
} : {}
|
|
827
|
+
...cost
|
|
722
828
|
});
|
|
829
|
+
return {
|
|
830
|
+
usage,
|
|
831
|
+
cost
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
/** `session.usage.recorded`: a call OpenCode made besides the steps (the title, a compaction). */
|
|
835
|
+
async usageRecorded(d) {
|
|
836
|
+
await this.spend(d, d.source === "title" ? "title" : "history_summary");
|
|
837
|
+
}
|
|
838
|
+
async stepEnded(d) {
|
|
839
|
+
const { usage, cost } = await this.spend(d, "chat");
|
|
723
840
|
if (!this.stepOpen) return;
|
|
724
841
|
this.stepOpen = false;
|
|
725
842
|
await this.write({
|
|
726
843
|
kind: "step-finish",
|
|
727
844
|
usage,
|
|
728
|
-
costUsd: costUsd ?? null,
|
|
845
|
+
costUsd: cost.costUsd ?? null,
|
|
729
846
|
...this.segment.reasoningMs > 0 ? {
|
|
730
847
|
reasoningMs: this.segment.reasoningMs
|
|
731
848
|
} : {},
|
|
732
|
-
model: this.
|
|
849
|
+
model: this.modelId()
|
|
733
850
|
});
|
|
734
851
|
}
|
|
735
852
|
/**
|
|
@@ -748,12 +865,7 @@ ${delta}` : delta;
|
|
|
748
865
|
content: s.text,
|
|
749
866
|
runId: this.a.runId,
|
|
750
867
|
...final ? {
|
|
751
|
-
usage:
|
|
752
|
-
...this.usage,
|
|
753
|
-
...this.costUsd !== void 0 ? {
|
|
754
|
-
costUsd: this.costUsd
|
|
755
|
-
} : {}
|
|
756
|
-
}
|
|
868
|
+
usage: this.messageUsage()
|
|
757
869
|
} : {},
|
|
758
870
|
...this.a.input.agentName !== void 0 ? {
|
|
759
871
|
agentName: this.a.input.agentName
|
|
@@ -781,6 +893,26 @@ ${delta}` : delta;
|
|
|
781
893
|
]);
|
|
782
894
|
return message.id;
|
|
783
895
|
}
|
|
896
|
+
/** The run's usage as its last message carries it: the whole run, cache tokens included. */
|
|
897
|
+
messageUsage() {
|
|
898
|
+
const total = this.totalUsage();
|
|
899
|
+
return {
|
|
900
|
+
inputTokens: total.inputTokens,
|
|
901
|
+
outputTokens: total.outputTokens,
|
|
902
|
+
...total.cacheReadTokens > 0 ? {
|
|
903
|
+
cacheReadTokens: total.cacheReadTokens
|
|
904
|
+
} : {},
|
|
905
|
+
...total.cacheWriteTokens > 0 ? {
|
|
906
|
+
cacheWriteTokens: total.cacheWriteTokens
|
|
907
|
+
} : {},
|
|
908
|
+
...total.reasoningTokens > 0 ? {
|
|
909
|
+
reasoningTokens: total.reasoningTokens
|
|
910
|
+
} : {},
|
|
911
|
+
...this.priced || (this.a.carried?.costUsd ?? 0) > 0 ? {
|
|
912
|
+
costUsd: total.costUsd
|
|
913
|
+
} : {}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
784
916
|
/** A request recorded by an earlier process (a turn resumed after a restart). */
|
|
785
917
|
async alreadyRecorded(id) {
|
|
786
918
|
return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
|
|
@@ -1132,9 +1264,39 @@ ${delta}` : delta;
|
|
|
1132
1264
|
this.messageResults.set(messageId, results);
|
|
1133
1265
|
await this.a.store.setMessageToolResults(messageId, results);
|
|
1134
1266
|
}
|
|
1267
|
+
/** What the session spent since the baseline that no event of this turn reported. */
|
|
1268
|
+
async unreported() {
|
|
1269
|
+
const baseline = this.baseline;
|
|
1270
|
+
if (baseline === void 0) return;
|
|
1271
|
+
this.baseline = void 0;
|
|
1272
|
+
const info = await this.a.client.session.get?.({
|
|
1273
|
+
sessionID: this.a.sessionId
|
|
1274
|
+
}).catch(() => void 0);
|
|
1275
|
+
const after = info?.tokens;
|
|
1276
|
+
if (after === void 0) return;
|
|
1277
|
+
const before = baseline.tokens;
|
|
1278
|
+
const left = /* @__PURE__ */ __name((now, then, seen) => Math.max(0, (Number(now) || 0) - (Number(then) || 0) - seen), "left");
|
|
1279
|
+
const tokens = {
|
|
1280
|
+
input: left(after.input, before.input, this.seen.input),
|
|
1281
|
+
output: left(after.output, before.output, this.seen.output),
|
|
1282
|
+
reasoning: left(after.reasoning, before.reasoning, this.seen.reasoning),
|
|
1283
|
+
cache: {
|
|
1284
|
+
read: left(after.cache?.read, before.cache?.read, this.seen.read),
|
|
1285
|
+
write: left(after.cache?.write, before.cache?.write, this.seen.write)
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
const total = tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write;
|
|
1289
|
+
if (total === 0) return;
|
|
1290
|
+
const cost = left(info?.cost, baseline.cost, this.seen.cost);
|
|
1291
|
+
await this.spend({
|
|
1292
|
+
tokens,
|
|
1293
|
+
cost: cost < 1e-9 ? 0 : cost
|
|
1294
|
+
}, this.compacted ? "history_summary" : "title");
|
|
1295
|
+
}
|
|
1135
1296
|
async end(outcome) {
|
|
1136
1297
|
if (this.ended) return;
|
|
1137
1298
|
this.ended = true;
|
|
1299
|
+
await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
|
|
1138
1300
|
if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
|
|
1139
1301
|
await this.flush(true);
|
|
1140
1302
|
}
|
|
@@ -1150,6 +1312,35 @@ ${delta}` : delta;
|
|
|
1150
1312
|
});
|
|
1151
1313
|
}
|
|
1152
1314
|
};
|
|
1315
|
+
function modelOfLabel(label) {
|
|
1316
|
+
const slash = label?.indexOf("/") ?? -1;
|
|
1317
|
+
if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
|
|
1318
|
+
return {
|
|
1319
|
+
providerID: label.slice(0, slash),
|
|
1320
|
+
id: label.slice(slash + 1)
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
__name(modelOfLabel, "modelOfLabel");
|
|
1324
|
+
function usageOf(tokens) {
|
|
1325
|
+
const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
|
|
1326
|
+
const read = n(tokens?.cache?.read);
|
|
1327
|
+
const write = n(tokens?.cache?.write);
|
|
1328
|
+
const reasoning = n(tokens?.reasoning);
|
|
1329
|
+
return {
|
|
1330
|
+
inputTokens: n(tokens?.input) + read + write,
|
|
1331
|
+
outputTokens: n(tokens?.output),
|
|
1332
|
+
...read > 0 ? {
|
|
1333
|
+
cacheReadTokens: read
|
|
1334
|
+
} : {},
|
|
1335
|
+
...write > 0 ? {
|
|
1336
|
+
cacheWriteTokens: write
|
|
1337
|
+
} : {},
|
|
1338
|
+
...reasoning > 0 ? {
|
|
1339
|
+
reasoningTokens: reasoning
|
|
1340
|
+
} : {}
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
__name(usageOf, "usageOf");
|
|
1153
1344
|
|
|
1154
1345
|
// src/mcp.ts
|
|
1155
1346
|
var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -1414,7 +1605,7 @@ import { Inject as Inject3, Injectable as Injectable2 } from "@nestjs/common";
|
|
|
1414
1605
|
// src/turns.ts
|
|
1415
1606
|
import { randomUUID } from "crypto";
|
|
1416
1607
|
import { AGENT_CHAT_QUEUE } from "@dudousxd/nestjs-agent";
|
|
1417
|
-
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";
|
|
1608
|
+
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";
|
|
1418
1609
|
import { Inject as Inject2, Injectable, Logger as Logger2, Optional } from "@nestjs/common";
|
|
1419
1610
|
|
|
1420
1611
|
// src/event-hub.ts
|
|
@@ -1581,7 +1772,7 @@ var OpenCodeTurns = class {
|
|
|
1581
1772
|
spent = /* @__PURE__ */ new Set();
|
|
1582
1773
|
/** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
|
|
1583
1774
|
toolsIssued = /* @__PURE__ */ new Map();
|
|
1584
|
-
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2) {
|
|
1775
|
+
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
|
|
1585
1776
|
this.host = host;
|
|
1586
1777
|
this.sessions = sessions;
|
|
1587
1778
|
this.settings = settings;
|
|
@@ -1593,6 +1784,30 @@ var OpenCodeTurns = class {
|
|
|
1593
1784
|
this.memory = memory;
|
|
1594
1785
|
this.queue = queue;
|
|
1595
1786
|
this.toolsTokens = toolsTokens2;
|
|
1787
|
+
this.prices = new ModelPriceResolver({
|
|
1788
|
+
pricingStore,
|
|
1789
|
+
catalog: agentOptions?.priceCatalog,
|
|
1790
|
+
log: {
|
|
1791
|
+
info: /* @__PURE__ */ __name((message) => this.logger.log(message), "info"),
|
|
1792
|
+
warn: /* @__PURE__ */ __name((message) => this.logger.warn(message), "warn")
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
prices;
|
|
1797
|
+
/**
|
|
1798
|
+
* What a model call OpenCode made cost, by the same rule the library's loop records its own steps
|
|
1799
|
+
* at: a reported figure wins, else an estimate off the model's price — see
|
|
1800
|
+
* {@link OpenCodeEngineSettings.cost}.
|
|
1801
|
+
*/
|
|
1802
|
+
async priceStep(step) {
|
|
1803
|
+
const { model, usage, reportedCostUsd } = step;
|
|
1804
|
+
const tokens = usage.inputTokens + usage.outputTokens;
|
|
1805
|
+
const reported = reportedCostUsd !== void 0 && (reportedCostUsd > 0 || tokens === 0);
|
|
1806
|
+
if (reported && this.settings.cost !== "estimate") {
|
|
1807
|
+
return resolveUsageCost(usage, reportedCostUsd, void 0);
|
|
1808
|
+
}
|
|
1809
|
+
const price = model !== void 0 && tokens > 0 ? await this.prices.priceFor(priceRef(model)) : void 0;
|
|
1810
|
+
return resolveUsageCost(usage, price === void 0 ? reportedCostUsd : void 0, price);
|
|
1596
1811
|
}
|
|
1597
1812
|
/** The tools server's name in OpenCode. */
|
|
1598
1813
|
get toolsServer() {
|
|
@@ -2182,8 +2397,8 @@ ${body}
|
|
|
2182
2397
|
}
|
|
2183
2398
|
// ---- prompt / observe / reply --------------------------------------------------------------
|
|
2184
2399
|
/** Start listening to the session (so nothing it says is missed), then send the user message. */
|
|
2185
|
-
async prompt(runId, input, handle) {
|
|
2186
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2400
|
+
async prompt(runId, input, handle, spent) {
|
|
2401
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2187
2402
|
try {
|
|
2188
2403
|
const prompt = await this.host.promptFor?.({
|
|
2189
2404
|
input,
|
|
@@ -2198,6 +2413,7 @@ ${body}
|
|
|
2198
2413
|
})
|
|
2199
2414
|
} : {}
|
|
2200
2415
|
};
|
|
2416
|
+
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2201
2417
|
await live.client.session.prompt({
|
|
2202
2418
|
sessionID: handle.sessionId,
|
|
2203
2419
|
text: prompt.text,
|
|
@@ -2213,9 +2429,12 @@ ${body}
|
|
|
2213
2429
|
* Wait for the turn's next milestone. In a process that was not following the turn (a resumed
|
|
2214
2430
|
* durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
|
|
2215
2431
|
* `session.wait` is the safety net for a terminal event that was missed.
|
|
2432
|
+
*
|
|
2433
|
+
* `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
|
|
2434
|
+
* rebuilt here starts from it, so the answer it persists carries the whole run's usage.
|
|
2216
2435
|
*/
|
|
2217
|
-
async observe(runId, input, handle) {
|
|
2218
|
-
const live = await this.ensureLive(runId, input, handle, true);
|
|
2436
|
+
async observe(runId, input, handle, spent) {
|
|
2437
|
+
const live = await this.ensureLive(runId, input, handle, true, spent);
|
|
2219
2438
|
const { turn, client } = live;
|
|
2220
2439
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2221
2440
|
let done = false;
|
|
@@ -2254,32 +2473,43 @@ ${body}
|
|
|
2254
2473
|
* request is gone with it: a new session is opened, told what the person decided, and prompted
|
|
2255
2474
|
* to go on.
|
|
2256
2475
|
*/
|
|
2257
|
-
async reply(runId, input, handle, ask, reply) {
|
|
2476
|
+
async reply(runId, input, handle, ask, reply, spent) {
|
|
2258
2477
|
const server = await this.host.server(input.actor);
|
|
2259
2478
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2260
|
-
return this.continueAfterRestart(runId, input, server, ask, reply);
|
|
2479
|
+
return this.continueAfterRestart(runId, input, server, ask, reply, spent);
|
|
2261
2480
|
}
|
|
2262
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2481
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2263
2482
|
await live.turn.decide(ask, reply);
|
|
2264
2483
|
return handle;
|
|
2265
2484
|
}
|
|
2266
|
-
async continueAfterRestart(runId, input, server, ask, reply) {
|
|
2485
|
+
async continueAfterRestart(runId, input, server, ask, reply, spent) {
|
|
2267
2486
|
const approved = "approved" in reply && reply.approved === true;
|
|
2268
2487
|
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.`;
|
|
2269
2488
|
const thread = await this.store.getThread(input.threadId);
|
|
2270
2489
|
const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
|
|
2271
2490
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2272
2491
|
this.drop(runId);
|
|
2273
|
-
const live = await this.ensureLive(runId, input, handle, false);
|
|
2492
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2274
2493
|
await live.turn.decide(ask, reply, {
|
|
2275
2494
|
tellOpenCode: false
|
|
2276
2495
|
});
|
|
2496
|
+
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2277
2497
|
await live.client.session.prompt({
|
|
2278
2498
|
sessionID: handle.sessionId,
|
|
2279
2499
|
text: note
|
|
2280
2500
|
});
|
|
2281
2501
|
return handle;
|
|
2282
2502
|
}
|
|
2503
|
+
/** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
|
|
2504
|
+
async sessionUsage(client, sessionId) {
|
|
2505
|
+
try {
|
|
2506
|
+
return await client.session.get?.({
|
|
2507
|
+
sessionID: sessionId
|
|
2508
|
+
});
|
|
2509
|
+
} catch {
|
|
2510
|
+
return void 0;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2283
2513
|
/** Interrupt the thread's session (cancel). */
|
|
2284
2514
|
async interrupt(input) {
|
|
2285
2515
|
const known = await this.sessions.get(input.threadId);
|
|
@@ -2290,7 +2520,7 @@ ${body}
|
|
|
2290
2520
|
sessionID: known.sessionId
|
|
2291
2521
|
});
|
|
2292
2522
|
}
|
|
2293
|
-
async ensureLive(runId, input, handle, catchUp) {
|
|
2523
|
+
async ensureLive(runId, input, handle, catchUp, spent) {
|
|
2294
2524
|
const current = this.live.get(runId);
|
|
2295
2525
|
if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
|
|
2296
2526
|
current?.stop();
|
|
@@ -2316,6 +2546,10 @@ ${body}
|
|
|
2316
2546
|
} : {}
|
|
2317
2547
|
}), "approvalFor"),
|
|
2318
2548
|
modelLabel: input.model ?? "opencode",
|
|
2549
|
+
priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
|
|
2550
|
+
...spent !== void 0 ? {
|
|
2551
|
+
carried: spent
|
|
2552
|
+
} : {},
|
|
2319
2553
|
logger: this.logger,
|
|
2320
2554
|
onGranted: /* @__PURE__ */ __name((action, id) => {
|
|
2321
2555
|
const grants = this.grants.get(runId) ?? [];
|
|
@@ -2362,20 +2596,14 @@ ${body}
|
|
|
2362
2596
|
}
|
|
2363
2597
|
return milestone;
|
|
2364
2598
|
}
|
|
2365
|
-
/**
|
|
2366
|
-
|
|
2599
|
+
/**
|
|
2600
|
+
* What the run produced, as the host's settle hooks see it. `usage` is what the run spent — the
|
|
2601
|
+
* runner's sum of its milestones' usage, which a resumed run reads back from its journal — else
|
|
2602
|
+
* what the turn this process follows saw.
|
|
2603
|
+
*/
|
|
2604
|
+
async runResult(runId, input, outcome, durationMs, usage) {
|
|
2367
2605
|
const thread = await this.store.getThread(input.threadId);
|
|
2368
2606
|
const messages = (thread?.messages ?? []).filter((m) => m.role === "assistant" && m.runId === runId);
|
|
2369
|
-
const usage = {
|
|
2370
|
-
inputTokens: 0,
|
|
2371
|
-
outputTokens: 0,
|
|
2372
|
-
costUsd: 0
|
|
2373
|
-
};
|
|
2374
|
-
for (const m of messages) {
|
|
2375
|
-
usage.inputTokens += m.usage?.inputTokens ?? 0;
|
|
2376
|
-
usage.outputTokens += m.usage?.outputTokens ?? 0;
|
|
2377
|
-
usage.costUsd += m.usage?.costUsd ?? 0;
|
|
2378
|
-
}
|
|
2379
2607
|
return {
|
|
2380
2608
|
runId,
|
|
2381
2609
|
input,
|
|
@@ -2420,15 +2648,31 @@ ${body}
|
|
|
2420
2648
|
this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
|
|
2421
2649
|
}
|
|
2422
2650
|
}
|
|
2423
|
-
/**
|
|
2424
|
-
|
|
2651
|
+
/**
|
|
2652
|
+
* What the run spent: the runner's sum of its milestones, or what the turn this process follows
|
|
2653
|
+
* saw (which starts from that sum) when it saw more — a run that failed or was cancelled between
|
|
2654
|
+
* two milestones.
|
|
2655
|
+
*/
|
|
2656
|
+
spentBy(runId, spent) {
|
|
2657
|
+
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
2658
|
+
if (spent === void 0) return seen ?? emptyUsage();
|
|
2659
|
+
if (seen === void 0) return spent;
|
|
2660
|
+
const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
|
|
2661
|
+
return tokens(seen) > tokens(spent) ? seen : spent;
|
|
2662
|
+
}
|
|
2663
|
+
/**
|
|
2664
|
+
* End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
|
|
2665
|
+
* (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
|
|
2666
|
+
*/
|
|
2667
|
+
async settle(runId, input, outcome, durationMs, spent) {
|
|
2425
2668
|
if (outcome.status === "failed") {
|
|
2426
|
-
await this.settleFailed(runId, input, outcome.error, durationMs);
|
|
2669
|
+
await this.settleFailed(runId, input, outcome.error, durationMs, spent);
|
|
2427
2670
|
return;
|
|
2428
2671
|
}
|
|
2672
|
+
const usage = this.spentBy(runId, spent);
|
|
2429
2673
|
this.forget(runId);
|
|
2430
2674
|
const writer = await this.sink.open(runId);
|
|
2431
|
-
const result = await this.runResult(runId, input, outcome, durationMs);
|
|
2675
|
+
const result = await this.runResult(runId, input, outcome, durationMs, usage);
|
|
2432
2676
|
await this.amend(writer, result);
|
|
2433
2677
|
if (outcome.status === "interrupted") {
|
|
2434
2678
|
this.logger.log(`agent run ${runId} cancelled`);
|
|
@@ -2456,13 +2700,14 @@ ${body}
|
|
|
2456
2700
|
}
|
|
2457
2701
|
await this.settled(result);
|
|
2458
2702
|
}
|
|
2459
|
-
async settleFailed(runId, input, error, durationMs = 0) {
|
|
2703
|
+
async settleFailed(runId, input, error, durationMs = 0, spent) {
|
|
2704
|
+
const usage = this.spentBy(runId, spent);
|
|
2460
2705
|
this.forget(runId);
|
|
2461
2706
|
const writer = await this.sink.open(runId);
|
|
2462
2707
|
const result = await this.runResult(runId, input, {
|
|
2463
2708
|
status: "failed",
|
|
2464
2709
|
error
|
|
2465
|
-
}, durationMs);
|
|
2710
|
+
}, durationMs, usage);
|
|
2466
2711
|
const worded = (await this.amend(writer, result)).error;
|
|
2467
2712
|
const message = worded ?? streamFailure(new Error(error)).message;
|
|
2468
2713
|
this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
@@ -2532,6 +2777,10 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
2532
2777
|
_ts_param2(9, Inject2(AGENT_CHAT_QUEUE)),
|
|
2533
2778
|
_ts_param2(10, Optional()),
|
|
2534
2779
|
_ts_param2(10, Inject2(OPENCODE_TOOLS_TOKENS)),
|
|
2780
|
+
_ts_param2(11, Optional()),
|
|
2781
|
+
_ts_param2(11, Inject2(AGENT_PRICING_STORE)),
|
|
2782
|
+
_ts_param2(12, Optional()),
|
|
2783
|
+
_ts_param2(12, Inject2(AGENT_OPTIONS)),
|
|
2535
2784
|
_ts_metadata2("design:type", Function),
|
|
2536
2785
|
_ts_metadata2("design:paramtypes", [
|
|
2537
2786
|
typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
|
|
@@ -2544,6 +2793,8 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
2544
2793
|
typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
|
|
2545
2794
|
typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
|
|
2546
2795
|
typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
|
|
2796
|
+
Object,
|
|
2797
|
+
typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
|
|
2547
2798
|
Object
|
|
2548
2799
|
])
|
|
2549
2800
|
], OpenCodeTurns);
|
|
@@ -2586,6 +2837,19 @@ function uiEmitter(scope, push) {
|
|
|
2586
2837
|
};
|
|
2587
2838
|
}
|
|
2588
2839
|
__name(uiEmitter, "uiEmitter");
|
|
2840
|
+
function priceRef(model) {
|
|
2841
|
+
const aliases = [
|
|
2842
|
+
model.id
|
|
2843
|
+
];
|
|
2844
|
+
const tail = model.id.slice(model.id.lastIndexOf("/") + 1);
|
|
2845
|
+
if (tail !== model.id && parseBedrockModelId(tail) !== void 0) aliases.push(tail);
|
|
2846
|
+
return {
|
|
2847
|
+
modelId: `${model.providerID}/${model.id}`,
|
|
2848
|
+
provider: model.providerID,
|
|
2849
|
+
aliases
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
__name(priceRef, "priceRef");
|
|
2589
2853
|
|
|
2590
2854
|
// src/runner.ts
|
|
2591
2855
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
@@ -2687,18 +2951,20 @@ var OpenCodeAgentRunner = class {
|
|
|
2687
2951
|
async run(runId, input) {
|
|
2688
2952
|
const started = Date.now();
|
|
2689
2953
|
this.inputs.set(runId, input);
|
|
2954
|
+
let spent = emptyUsage();
|
|
2690
2955
|
try {
|
|
2691
2956
|
let handle = await this.turns.begin(runId, input);
|
|
2692
2957
|
if (this.cancelled.has(runId)) throw new RunCancelledError();
|
|
2693
|
-
await this.turns.prompt(runId, input, handle);
|
|
2958
|
+
await this.turns.prompt(runId, input, handle, spent);
|
|
2694
2959
|
for (; ; ) {
|
|
2695
|
-
const milestone = await this.turns.observe(runId, input, handle);
|
|
2960
|
+
const milestone = await this.turns.observe(runId, input, handle, spent);
|
|
2961
|
+
spent = addUsage(spent, milestone.usage);
|
|
2696
2962
|
if (milestone.kind === "finished") {
|
|
2697
|
-
await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
|
|
2963
|
+
await this.turns.settle(runId, input, milestone.outcome, Date.now() - started, spent);
|
|
2698
2964
|
return;
|
|
2699
2965
|
}
|
|
2700
2966
|
const reply = await this.park(runId, milestone.ask.id, milestone.ask.kind === "approval" ? "approval" : "answers", milestone.timeoutMs);
|
|
2701
|
-
handle = await this.turns.reply(runId, input, handle, milestone.ask, reply);
|
|
2967
|
+
handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
|
|
2702
2968
|
}
|
|
2703
2969
|
} catch (error) {
|
|
2704
2970
|
if (error instanceof RunCancelledError) {
|
|
@@ -2708,7 +2974,7 @@ var OpenCodeAgentRunner = class {
|
|
|
2708
2974
|
}, Date.now() - started);
|
|
2709
2975
|
return;
|
|
2710
2976
|
}
|
|
2711
|
-
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"));
|
|
2977
|
+
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
|
|
2712
2978
|
} finally {
|
|
2713
2979
|
this.inputs.delete(runId);
|
|
2714
2980
|
}
|
|
@@ -2880,6 +3146,7 @@ var OpenCodeRunWorkflow = class {
|
|
|
2880
3146
|
this.runs = runs;
|
|
2881
3147
|
}
|
|
2882
3148
|
async run(ctx, input) {
|
|
3149
|
+
let spent = emptyUsage();
|
|
2883
3150
|
try {
|
|
2884
3151
|
const begun = await ctx.localStep("begin", async () => ({
|
|
2885
3152
|
handle: await this.turns.begin(ctx.runId, input),
|
|
@@ -2887,15 +3154,18 @@ var OpenCodeRunWorkflow = class {
|
|
|
2887
3154
|
}));
|
|
2888
3155
|
let handle = begun.handle;
|
|
2889
3156
|
await ctx.localStep("prompt", async () => {
|
|
2890
|
-
await this.turns.prompt(ctx.runId, input, handle);
|
|
3157
|
+
await this.turns.prompt(ctx.runId, input, handle, spent);
|
|
2891
3158
|
return true;
|
|
2892
3159
|
});
|
|
2893
3160
|
for (let n = 0; ; n += 1) {
|
|
2894
|
-
const
|
|
3161
|
+
const before = spent;
|
|
3162
|
+
const milestone = await ctx.localStep(`observe:${n}`, () => this.turns.observe(ctx.runId, input, handle, before));
|
|
3163
|
+
spent = addUsage(spent, milestone.usage);
|
|
3164
|
+
const total = spent;
|
|
2895
3165
|
if (milestone.kind === "finished") {
|
|
2896
3166
|
await ctx.localStep("finish", async () => {
|
|
2897
3167
|
if (await this.cancelled(ctx.runId)) this.turns.drop(ctx.runId);
|
|
2898
|
-
else await this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt);
|
|
3168
|
+
else await this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt, total);
|
|
2899
3169
|
return true;
|
|
2900
3170
|
});
|
|
2901
3171
|
return {
|
|
@@ -2915,12 +3185,12 @@ var OpenCodeRunWorkflow = class {
|
|
|
2915
3185
|
};
|
|
2916
3186
|
}
|
|
2917
3187
|
const ask = milestone.ask;
|
|
2918
|
-
handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply));
|
|
3188
|
+
handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply, total));
|
|
2919
3189
|
}
|
|
2920
3190
|
} catch (error) {
|
|
2921
3191
|
if (isWorkflowControlFlowSignal(error)) throw error;
|
|
2922
3192
|
await ctx.localStep("fail", async () => {
|
|
2923
|
-
await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"));
|
|
3193
|
+
await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"), 0, spent);
|
|
2924
3194
|
return true;
|
|
2925
3195
|
});
|
|
2926
3196
|
return {
|