@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.
@@ -1,5 +1,5 @@
1
1
  import { AgentEngine } from '@dudousxd/nestjs-agent';
2
- import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-JnBbcRxh.cjs';
2
+ import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-Cp5uysaN.cjs';
3
3
  import { AgentRunner, AgentStore, AgentRunInput, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
4
4
  import { WorkflowService } from '@dudousxd/nestjs-durable';
5
5
  import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
@@ -1,5 +1,5 @@
1
1
  import { AgentEngine } from '@dudousxd/nestjs-agent';
2
- import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-JnBbcRxh.js';
2
+ import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-Cp5uysaN.js';
3
3
  import { AgentRunner, AgentStore, AgentRunInput, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
4
4
  import { WorkflowService } from '@dudousxd/nestjs-durable';
5
5
  import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
@@ -132,7 +132,7 @@ import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-sc
132
132
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
133
133
 
134
134
  // src/turn.ts
135
- import { encodeStreamEvent, settleElicitation } from "@dudousxd/nestjs-agent-core";
135
+ import { encodeStreamEvent, resolveToolConfirmation, settleElicitation } from "@dudousxd/nestjs-agent-core";
136
136
  import { ConflictException } from "@nestjs/common";
137
137
 
138
138
  // src/forms.ts
@@ -326,6 +326,17 @@ var OpenCodeTurn = class {
326
326
  reported = emptyUsage();
327
327
  priced = false;
328
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
338
+ };
339
+ compacted = false;
329
340
  stepOpen = false;
330
341
  sawText = false;
331
342
  separator = false;
@@ -337,6 +348,19 @@ var OpenCodeTurn = class {
337
348
  this.a = a;
338
349
  this.model = modelOfLabel(a.input.model);
339
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
+ }
340
364
  /** What the run spent so far: carried from before this turn object, plus what it saw. */
341
365
  totalUsage() {
342
366
  return addUsage(this.a.carried, this.spent);
@@ -593,6 +617,10 @@ var OpenCodeTurn = class {
593
617
  }
594
618
  if (this.ended) return;
595
619
  switch (e.type) {
620
+ case "session.compaction.started":
621
+ case "session.compaction.ended":
622
+ this.compacted = true;
623
+ return;
596
624
  case "session.step.started":
597
625
  case "session.model.selected": {
598
626
  const model = d.model;
@@ -762,6 +790,13 @@ ${delta}` : delta;
762
790
  async spend(d, purpose) {
763
791
  const usage = usageOf(d.tokens);
764
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);
765
800
  const fallback = reportedCostUsd !== void 0 ? {
766
801
  costUsd: reportedCostUsd,
767
802
  costSource: "provider"
@@ -967,6 +1002,8 @@ ${delta}` : delta;
967
1002
  }
968
1003
  const ttlMs = requirement.ttlMs !== void 0 && Number.isFinite(requirement.ttlMs) && requirement.ttlMs > 0 ? Math.floor(requirement.ttlMs) : void 0;
969
1004
  const expiresAt = ttlMs !== void 0 ? new Date(Date.now() + ttlMs).toISOString() : void 0;
1005
+ const tool = this.a.approvalPresentation?.(action);
1006
+ const confirmation = tool !== void 0 ? resolveToolConfirmation(tool.name, args, tool.presentation) : void 0;
970
1007
  this.segment.calls.push({
971
1008
  id: req.id,
972
1009
  name: action,
@@ -981,6 +1018,9 @@ ${delta}` : delta;
981
1018
  toolType: "action",
982
1019
  input: args,
983
1020
  status: "pending_approval",
1021
+ ...confirmation !== void 0 ? {
1022
+ confirmation
1023
+ } : {},
984
1024
  runId: this.a.runId,
985
1025
  approver: requirement.approver,
986
1026
  ...expiresAt !== void 0 ? {
@@ -998,6 +1038,9 @@ ${delta}` : delta;
998
1038
  kind: "approval-requested",
999
1039
  id: req.id,
1000
1040
  approver: requirement.approver,
1041
+ ...confirmation !== void 0 ? {
1042
+ confirmation
1043
+ } : {},
1001
1044
  ...expiresAt !== void 0 ? {
1002
1045
  expiresAt
1003
1046
  } : {}
@@ -1229,9 +1272,39 @@ ${delta}` : delta;
1229
1272
  this.messageResults.set(messageId, results);
1230
1273
  await this.a.store.setMessageToolResults(messageId, results);
1231
1274
  }
1275
+ /** What the session spent since the baseline that no event of this turn reported. */
1276
+ async unreported() {
1277
+ const baseline = this.baseline;
1278
+ if (baseline === void 0) return;
1279
+ this.baseline = void 0;
1280
+ const info = await this.a.client.session.get?.({
1281
+ sessionID: this.a.sessionId
1282
+ }).catch(() => void 0);
1283
+ const after = info?.tokens;
1284
+ if (after === void 0) return;
1285
+ const before = baseline.tokens;
1286
+ const left = /* @__PURE__ */ __name((now, then, seen) => Math.max(0, (Number(now) || 0) - (Number(then) || 0) - seen), "left");
1287
+ const tokens = {
1288
+ input: left(after.input, before.input, this.seen.input),
1289
+ output: left(after.output, before.output, this.seen.output),
1290
+ reasoning: left(after.reasoning, before.reasoning, this.seen.reasoning),
1291
+ cache: {
1292
+ read: left(after.cache?.read, before.cache?.read, this.seen.read),
1293
+ write: left(after.cache?.write, before.cache?.write, this.seen.write)
1294
+ }
1295
+ };
1296
+ const total = tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write;
1297
+ if (total === 0) return;
1298
+ const cost = left(info?.cost, baseline.cost, this.seen.cost);
1299
+ await this.spend({
1300
+ tokens,
1301
+ cost: cost < 1e-9 ? 0 : cost
1302
+ }, this.compacted ? "history_summary" : "title");
1303
+ }
1232
1304
  async end(outcome) {
1233
1305
  if (this.ended) return;
1234
1306
  this.ended = true;
1307
+ await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
1235
1308
  if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
1236
1309
  await this.flush(true);
1237
1310
  }
@@ -1748,6 +1821,25 @@ var OpenCodeTurns = class {
1748
1821
  get toolsServer() {
1749
1822
  return this.settings.tools?.server ?? "aviary";
1750
1823
  }
1824
+ /** The app tool an OpenCode permission `action` names, and its presentation — `undefined` for a built-in. */
1825
+ presentationOf(action) {
1826
+ if (this.settings.tools === void 0) return void 0;
1827
+ const server = this.toolsServer;
1828
+ for (const prefix of [
1829
+ `${server}.`,
1830
+ `${server}_`
1831
+ ]) {
1832
+ if (!action.startsWith(prefix)) continue;
1833
+ const spec = this.registry.spec(action.slice(prefix.length));
1834
+ if (spec !== void 0) return {
1835
+ name: spec.name,
1836
+ ...spec.presentation !== void 0 ? {
1837
+ presentation: spec.presentation
1838
+ } : {}
1839
+ };
1840
+ }
1841
+ return void 0;
1842
+ }
1751
1843
  /** The roles policy a turn's tools are gated by (the agent's own). */
1752
1844
  rolesPolicyFor(input) {
1753
1845
  return this.deps.forAgent(input.agentName).rolesPolicy;
@@ -2348,6 +2440,7 @@ ${body}
2348
2440
  })
2349
2441
  } : {}
2350
2442
  };
2443
+ live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
2351
2444
  await live.client.session.prompt({
2352
2445
  sessionID: handle.sessionId,
2353
2446
  text: prompt.text,
@@ -2427,12 +2520,23 @@ ${body}
2427
2520
  await live.turn.decide(ask, reply, {
2428
2521
  tellOpenCode: false
2429
2522
  });
2523
+ live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
2430
2524
  await live.client.session.prompt({
2431
2525
  sessionID: handle.sessionId,
2432
2526
  text: note
2433
2527
  });
2434
2528
  return handle;
2435
2529
  }
2530
+ /** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
2531
+ async sessionUsage(client, sessionId) {
2532
+ try {
2533
+ return await client.session.get?.({
2534
+ sessionID: sessionId
2535
+ });
2536
+ } catch {
2537
+ return void 0;
2538
+ }
2539
+ }
2436
2540
  /** Interrupt the thread's session (cancel). */
2437
2541
  async interrupt(input) {
2438
2542
  const known = await this.sessions.get(input.threadId);
@@ -2468,6 +2572,9 @@ ${body}
2468
2572
  agentName: input.agentName
2469
2573
  } : {}
2470
2574
  }), "approvalFor"),
2575
+ // An app tool reaches OpenCode as `<server>.<name>` / `<server>_<name>`: its own presentation
2576
+ // words the approval card. OpenCode's built-ins (`bash`, `edit`) have none.
2577
+ approvalPresentation: /* @__PURE__ */ __name((action) => this.presentationOf(action), "approvalPresentation"),
2471
2578
  modelLabel: input.model ?? "opencode",
2472
2579
  priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
2473
2580
  ...spent !== void 0 ? {