@dudousxd/nestjs-agent-opencode 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/durable/index.cjs +107 -0
- 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 +108 -1
- package/dist/durable/index.js.map +1 -1
- package/dist/{engine-JnBbcRxh.d.cts → engine-Cp5uysaN.d.cts} +46 -1
- package/dist/{engine-JnBbcRxh.d.ts → engine-Cp5uysaN.d.ts} +46 -1
- package/dist/index.cjs +107 -0
- 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 +108 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ export class AppModule {}
|
|
|
54
54
|
| --- | --- |
|
|
55
55
|
| `session.text.delta` / `session.reasoning.delta` | `text` / `reasoning` |
|
|
56
56
|
| a step (`session.step.ended`) | `step-start` … `step-finish` with usage and cost; a `chat` usage row per step (see [Cost and usage](#cost-and-usage)) |
|
|
57
|
-
|
|
|
57
|
+
| the title OpenCode generates, a compaction | a `title` / `history_summary` usage row (see [Cost and usage](#cost-and-usage)) |
|
|
58
58
|
| tools (`session.tool.*`) | `tool-input-*` / `tool-output*`, code-mode inner calls nested by `parentId` |
|
|
59
59
|
| `permission.asked` | an `action` call + `approval-requested`, recorded `pending_approval`; `approve` / `reject` → `permission.reply` |
|
|
60
60
|
| `form.created` | `elicitation`; `answer` / `skip` → `session.form.reply` / `cancel` |
|
|
@@ -72,6 +72,11 @@ compaction — goes to the same token-usage ledger the library's own loop writes
|
|
|
72
72
|
`agent_token_usage` with `cost_usd` and `cost_source`), so the ledger quota (`quota: { limits }`),
|
|
73
73
|
the dashboard and `GET /quota` see an OpenCode turn like any other.
|
|
74
74
|
|
|
75
|
+
- **The title and compactions.** OpenCode records those calls on the session without streaming
|
|
76
|
+
them. The turn reads what the session had spent (`session.get`) before it prompts, and when the
|
|
77
|
+
execution ends records what the session spent since that its steps did not report, as a `title`
|
|
78
|
+
row (`history_summary` when a compaction ran). A `session.usage.recorded` event, from an OpenCode
|
|
79
|
+
that streams it, is recorded as it arrives and not counted twice.
|
|
75
80
|
- **Tokens.** OpenCode reports the uncached input in `tokens.input` and the cache beside it; the row
|
|
76
81
|
counts the whole input side in `inputTokens`, with `cacheReadTokens` / `cacheWriteTokens` as
|
|
77
82
|
subsets (the library's `MessageUsage`). The row's model is the one OpenCode names for the step
|
package/dist/durable/index.cjs
CHANGED
|
@@ -354,6 +354,17 @@ var OpenCodeTurn = class {
|
|
|
354
354
|
reported = emptyUsage();
|
|
355
355
|
priced = false;
|
|
356
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
|
|
366
|
+
};
|
|
367
|
+
compacted = false;
|
|
357
368
|
stepOpen = false;
|
|
358
369
|
sawText = false;
|
|
359
370
|
separator = false;
|
|
@@ -365,6 +376,19 @@ var OpenCodeTurn = class {
|
|
|
365
376
|
this.a = a;
|
|
366
377
|
this.model = modelOfLabel(a.input.model);
|
|
367
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
|
+
}
|
|
368
392
|
/** What the run spent so far: carried from before this turn object, plus what it saw. */
|
|
369
393
|
totalUsage() {
|
|
370
394
|
return addUsage(this.a.carried, this.spent);
|
|
@@ -621,6 +645,10 @@ var OpenCodeTurn = class {
|
|
|
621
645
|
}
|
|
622
646
|
if (this.ended) return;
|
|
623
647
|
switch (e.type) {
|
|
648
|
+
case "session.compaction.started":
|
|
649
|
+
case "session.compaction.ended":
|
|
650
|
+
this.compacted = true;
|
|
651
|
+
return;
|
|
624
652
|
case "session.step.started":
|
|
625
653
|
case "session.model.selected": {
|
|
626
654
|
const model = d.model;
|
|
@@ -790,6 +818,13 @@ ${delta}` : delta;
|
|
|
790
818
|
async spend(d, purpose) {
|
|
791
819
|
const usage = usageOf(d.tokens);
|
|
792
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);
|
|
793
828
|
const fallback = reportedCostUsd !== void 0 ? {
|
|
794
829
|
costUsd: reportedCostUsd,
|
|
795
830
|
costSource: "provider"
|
|
@@ -995,6 +1030,8 @@ ${delta}` : delta;
|
|
|
995
1030
|
}
|
|
996
1031
|
const ttlMs = requirement.ttlMs !== void 0 && Number.isFinite(requirement.ttlMs) && requirement.ttlMs > 0 ? Math.floor(requirement.ttlMs) : void 0;
|
|
997
1032
|
const expiresAt = ttlMs !== void 0 ? new Date(Date.now() + ttlMs).toISOString() : void 0;
|
|
1033
|
+
const tool = this.a.approvalPresentation?.(action);
|
|
1034
|
+
const confirmation = tool !== void 0 ? (0, import_nestjs_agent_core2.resolveToolConfirmation)(tool.name, args, tool.presentation) : void 0;
|
|
998
1035
|
this.segment.calls.push({
|
|
999
1036
|
id: req.id,
|
|
1000
1037
|
name: action,
|
|
@@ -1009,6 +1046,9 @@ ${delta}` : delta;
|
|
|
1009
1046
|
toolType: "action",
|
|
1010
1047
|
input: args,
|
|
1011
1048
|
status: "pending_approval",
|
|
1049
|
+
...confirmation !== void 0 ? {
|
|
1050
|
+
confirmation
|
|
1051
|
+
} : {},
|
|
1012
1052
|
runId: this.a.runId,
|
|
1013
1053
|
approver: requirement.approver,
|
|
1014
1054
|
...expiresAt !== void 0 ? {
|
|
@@ -1026,6 +1066,9 @@ ${delta}` : delta;
|
|
|
1026
1066
|
kind: "approval-requested",
|
|
1027
1067
|
id: req.id,
|
|
1028
1068
|
approver: requirement.approver,
|
|
1069
|
+
...confirmation !== void 0 ? {
|
|
1070
|
+
confirmation
|
|
1071
|
+
} : {},
|
|
1029
1072
|
...expiresAt !== void 0 ? {
|
|
1030
1073
|
expiresAt
|
|
1031
1074
|
} : {}
|
|
@@ -1257,9 +1300,39 @@ ${delta}` : delta;
|
|
|
1257
1300
|
this.messageResults.set(messageId, results);
|
|
1258
1301
|
await this.a.store.setMessageToolResults(messageId, results);
|
|
1259
1302
|
}
|
|
1303
|
+
/** What the session spent since the baseline that no event of this turn reported. */
|
|
1304
|
+
async unreported() {
|
|
1305
|
+
const baseline = this.baseline;
|
|
1306
|
+
if (baseline === void 0) return;
|
|
1307
|
+
this.baseline = void 0;
|
|
1308
|
+
const info = await this.a.client.session.get?.({
|
|
1309
|
+
sessionID: this.a.sessionId
|
|
1310
|
+
}).catch(() => void 0);
|
|
1311
|
+
const after = info?.tokens;
|
|
1312
|
+
if (after === void 0) return;
|
|
1313
|
+
const before = baseline.tokens;
|
|
1314
|
+
const left = /* @__PURE__ */ __name((now, then, seen) => Math.max(0, (Number(now) || 0) - (Number(then) || 0) - seen), "left");
|
|
1315
|
+
const tokens = {
|
|
1316
|
+
input: left(after.input, before.input, this.seen.input),
|
|
1317
|
+
output: left(after.output, before.output, this.seen.output),
|
|
1318
|
+
reasoning: left(after.reasoning, before.reasoning, this.seen.reasoning),
|
|
1319
|
+
cache: {
|
|
1320
|
+
read: left(after.cache?.read, before.cache?.read, this.seen.read),
|
|
1321
|
+
write: left(after.cache?.write, before.cache?.write, this.seen.write)
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
const total = tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write;
|
|
1325
|
+
if (total === 0) return;
|
|
1326
|
+
const cost = left(info?.cost, baseline.cost, this.seen.cost);
|
|
1327
|
+
await this.spend({
|
|
1328
|
+
tokens,
|
|
1329
|
+
cost: cost < 1e-9 ? 0 : cost
|
|
1330
|
+
}, this.compacted ? "history_summary" : "title");
|
|
1331
|
+
}
|
|
1260
1332
|
async end(outcome) {
|
|
1261
1333
|
if (this.ended) return;
|
|
1262
1334
|
this.ended = true;
|
|
1335
|
+
await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
|
|
1263
1336
|
if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
|
|
1264
1337
|
await this.flush(true);
|
|
1265
1338
|
}
|
|
@@ -1776,6 +1849,25 @@ var OpenCodeTurns = class {
|
|
|
1776
1849
|
get toolsServer() {
|
|
1777
1850
|
return this.settings.tools?.server ?? "aviary";
|
|
1778
1851
|
}
|
|
1852
|
+
/** The app tool an OpenCode permission `action` names, and its presentation — `undefined` for a built-in. */
|
|
1853
|
+
presentationOf(action) {
|
|
1854
|
+
if (this.settings.tools === void 0) return void 0;
|
|
1855
|
+
const server = this.toolsServer;
|
|
1856
|
+
for (const prefix of [
|
|
1857
|
+
`${server}.`,
|
|
1858
|
+
`${server}_`
|
|
1859
|
+
]) {
|
|
1860
|
+
if (!action.startsWith(prefix)) continue;
|
|
1861
|
+
const spec = this.registry.spec(action.slice(prefix.length));
|
|
1862
|
+
if (spec !== void 0) return {
|
|
1863
|
+
name: spec.name,
|
|
1864
|
+
...spec.presentation !== void 0 ? {
|
|
1865
|
+
presentation: spec.presentation
|
|
1866
|
+
} : {}
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
return void 0;
|
|
1870
|
+
}
|
|
1779
1871
|
/** The roles policy a turn's tools are gated by (the agent's own). */
|
|
1780
1872
|
rolesPolicyFor(input) {
|
|
1781
1873
|
return this.deps.forAgent(input.agentName).rolesPolicy;
|
|
@@ -2376,6 +2468,7 @@ ${body}
|
|
|
2376
2468
|
})
|
|
2377
2469
|
} : {}
|
|
2378
2470
|
};
|
|
2471
|
+
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2379
2472
|
await live.client.session.prompt({
|
|
2380
2473
|
sessionID: handle.sessionId,
|
|
2381
2474
|
text: prompt.text,
|
|
@@ -2455,12 +2548,23 @@ ${body}
|
|
|
2455
2548
|
await live.turn.decide(ask, reply, {
|
|
2456
2549
|
tellOpenCode: false
|
|
2457
2550
|
});
|
|
2551
|
+
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2458
2552
|
await live.client.session.prompt({
|
|
2459
2553
|
sessionID: handle.sessionId,
|
|
2460
2554
|
text: note
|
|
2461
2555
|
});
|
|
2462
2556
|
return handle;
|
|
2463
2557
|
}
|
|
2558
|
+
/** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
|
|
2559
|
+
async sessionUsage(client, sessionId) {
|
|
2560
|
+
try {
|
|
2561
|
+
return await client.session.get?.({
|
|
2562
|
+
sessionID: sessionId
|
|
2563
|
+
});
|
|
2564
|
+
} catch {
|
|
2565
|
+
return void 0;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2464
2568
|
/** Interrupt the thread's session (cancel). */
|
|
2465
2569
|
async interrupt(input) {
|
|
2466
2570
|
const known = await this.sessions.get(input.threadId);
|
|
@@ -2496,6 +2600,9 @@ ${body}
|
|
|
2496
2600
|
agentName: input.agentName
|
|
2497
2601
|
} : {}
|
|
2498
2602
|
}), "approvalFor"),
|
|
2603
|
+
// An app tool reaches OpenCode as `<server>.<name>` / `<server>_<name>`: its own presentation
|
|
2604
|
+
// words the approval card. OpenCode's built-ins (`bash`, `edit`) have none.
|
|
2605
|
+
approvalPresentation: /* @__PURE__ */ __name((action) => this.presentationOf(action), "approvalPresentation"),
|
|
2499
2606
|
modelLabel: input.model ?? "opencode",
|
|
2500
2607
|
priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
|
|
2501
2608
|
...spent !== void 0 ? {
|