@dudousxd/nestjs-agent-opencode 0.2.0 → 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.
@@ -40,6 +40,9 @@ interface OpenCodeClient {
40
40
  metadata?: {
41
41
  readonly [key: string]: OpenCodeJson;
42
42
  };
43
+ /** What the session spent so far, every model call OpenCode made for it (the title too). */
44
+ cost?: number;
45
+ tokens?: OpenCodeTokens;
43
46
  }>;
44
47
  /** Resolves when the session goes idle. Optional: a safety net for a missed terminal event. */
45
48
  wait?(args: {
@@ -204,6 +207,16 @@ interface OpenCodeFormField {
204
207
  /** The user may type their own answer instead of picking one of `options`. */
205
208
  custom?: boolean;
206
209
  }
210
+ /** A token count as OpenCode reports it: `input` is the uncached input, the cache beside it. */
211
+ interface OpenCodeTokens {
212
+ input: number;
213
+ output: number;
214
+ reasoning?: number;
215
+ cache?: {
216
+ read: number;
217
+ write: number;
218
+ };
219
+ }
207
220
 
208
221
  /** How an OpenCode execution ended. */
209
222
  type TurnOutcome = {
@@ -344,6 +357,10 @@ declare class OpenCodeTurn {
344
357
  private reported;
345
358
  private priced;
346
359
  private model;
360
+ /** What the session had spent when this turn prompted it; what OpenCode reported since. */
361
+ private baseline;
362
+ private readonly seen;
363
+ private compacted;
347
364
  private stepOpen;
348
365
  private sawText;
349
366
  private separator;
@@ -352,6 +369,16 @@ declare class OpenCodeTurn {
352
369
  private ended;
353
370
  private stepError;
354
371
  constructor(a: OpenCodeTurnArgs);
372
+ /**
373
+ * What the session had spent before this turn prompted it (`session.get`). When the execution
374
+ * ends, what the session spent since that this turn's events did not report — the title OpenCode
375
+ * generates, a compaction: OpenCode records those calls on the session without streaming them — is
376
+ * recorded as a usage row of its own.
377
+ */
378
+ setBaseline(info: {
379
+ cost?: number;
380
+ tokens?: OpenCodeTokens;
381
+ } | undefined): void;
355
382
  /** What the run spent so far: carried from before this turn object, plus what it saw. */
356
383
  totalUsage(): OpenCodeUsage;
357
384
  get sessionId(): string;
@@ -418,6 +445,8 @@ declare class OpenCodeTurn {
418
445
  private onForm;
419
446
  private answered;
420
447
  private addResult;
448
+ /** What the session spent since the baseline that no event of this turn reported. */
449
+ private unreported;
421
450
  private end;
422
451
  }
423
452
  /**
@@ -889,6 +918,8 @@ declare class OpenCodeTurns implements OnApplicationShutdown {
889
918
  */
890
919
  reply(runId: string, input: AgentRunInput, handle: SessionHandle, ask: PendingAsk, reply: HumanReply, spent?: OpenCodeUsage): Promise<SessionHandle>;
891
920
  private continueAfterRestart;
921
+ /** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
922
+ private sessionUsage;
892
923
  /** Interrupt the thread's session (cancel). */
893
924
  interrupt(input: AgentRunInput): Promise<void>;
894
925
  private ensureLive;
@@ -40,6 +40,9 @@ interface OpenCodeClient {
40
40
  metadata?: {
41
41
  readonly [key: string]: OpenCodeJson;
42
42
  };
43
+ /** What the session spent so far, every model call OpenCode made for it (the title too). */
44
+ cost?: number;
45
+ tokens?: OpenCodeTokens;
43
46
  }>;
44
47
  /** Resolves when the session goes idle. Optional: a safety net for a missed terminal event. */
45
48
  wait?(args: {
@@ -204,6 +207,16 @@ interface OpenCodeFormField {
204
207
  /** The user may type their own answer instead of picking one of `options`. */
205
208
  custom?: boolean;
206
209
  }
210
+ /** A token count as OpenCode reports it: `input` is the uncached input, the cache beside it. */
211
+ interface OpenCodeTokens {
212
+ input: number;
213
+ output: number;
214
+ reasoning?: number;
215
+ cache?: {
216
+ read: number;
217
+ write: number;
218
+ };
219
+ }
207
220
 
208
221
  /** How an OpenCode execution ended. */
209
222
  type TurnOutcome = {
@@ -344,6 +357,10 @@ declare class OpenCodeTurn {
344
357
  private reported;
345
358
  private priced;
346
359
  private model;
360
+ /** What the session had spent when this turn prompted it; what OpenCode reported since. */
361
+ private baseline;
362
+ private readonly seen;
363
+ private compacted;
347
364
  private stepOpen;
348
365
  private sawText;
349
366
  private separator;
@@ -352,6 +369,16 @@ declare class OpenCodeTurn {
352
369
  private ended;
353
370
  private stepError;
354
371
  constructor(a: OpenCodeTurnArgs);
372
+ /**
373
+ * What the session had spent before this turn prompted it (`session.get`). When the execution
374
+ * ends, what the session spent since that this turn's events did not report — the title OpenCode
375
+ * generates, a compaction: OpenCode records those calls on the session without streaming them — is
376
+ * recorded as a usage row of its own.
377
+ */
378
+ setBaseline(info: {
379
+ cost?: number;
380
+ tokens?: OpenCodeTokens;
381
+ } | undefined): void;
355
382
  /** What the run spent so far: carried from before this turn object, plus what it saw. */
356
383
  totalUsage(): OpenCodeUsage;
357
384
  get sessionId(): string;
@@ -418,6 +445,8 @@ declare class OpenCodeTurn {
418
445
  private onForm;
419
446
  private answered;
420
447
  private addResult;
448
+ /** What the session spent since the baseline that no event of this turn reported. */
449
+ private unreported;
421
450
  private end;
422
451
  }
423
452
  /**
@@ -889,6 +918,8 @@ declare class OpenCodeTurns implements OnApplicationShutdown {
889
918
  */
890
919
  reply(runId: string, input: AgentRunInput, handle: SessionHandle, ask: PendingAsk, reply: HumanReply, spent?: OpenCodeUsage): Promise<SessionHandle>;
891
920
  private continueAfterRestart;
921
+ /** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
922
+ private sessionUsage;
892
923
  /** Interrupt the thread's session (cancel). */
893
924
  interrupt(input: AgentRunInput): Promise<void>;
894
925
  private ensureLive;
package/dist/index.cjs CHANGED
@@ -395,6 +395,17 @@ var OpenCodeTurn = class {
395
395
  reported = emptyUsage();
396
396
  priced = false;
397
397
  model;
398
+ /** What the session had spent when this turn prompted it; what OpenCode reported since. */
399
+ baseline;
400
+ seen = {
401
+ cost: 0,
402
+ input: 0,
403
+ output: 0,
404
+ reasoning: 0,
405
+ read: 0,
406
+ write: 0
407
+ };
408
+ compacted = false;
398
409
  stepOpen = false;
399
410
  sawText = false;
400
411
  separator = false;
@@ -406,6 +417,19 @@ var OpenCodeTurn = class {
406
417
  this.a = a;
407
418
  this.model = modelOfLabel(a.input.model);
408
419
  }
420
+ /**
421
+ * What the session had spent before this turn prompted it (`session.get`). When the execution
422
+ * ends, what the session spent since that this turn's events did not report — the title OpenCode
423
+ * generates, a compaction: OpenCode records those calls on the session without streaming them — is
424
+ * recorded as a usage row of its own.
425
+ */
426
+ setBaseline(info) {
427
+ if (info?.tokens === void 0) return;
428
+ this.baseline = {
429
+ cost: Number(info.cost) || 0,
430
+ tokens: info.tokens
431
+ };
432
+ }
409
433
  /** What the run spent so far: carried from before this turn object, plus what it saw. */
410
434
  totalUsage() {
411
435
  return addUsage(this.a.carried, this.spent);
@@ -662,6 +686,10 @@ var OpenCodeTurn = class {
662
686
  }
663
687
  if (this.ended) return;
664
688
  switch (e.type) {
689
+ case "session.compaction.started":
690
+ case "session.compaction.ended":
691
+ this.compacted = true;
692
+ return;
665
693
  case "session.step.started":
666
694
  case "session.model.selected": {
667
695
  const model = d.model;
@@ -831,6 +859,13 @@ ${delta}` : delta;
831
859
  async spend(d, purpose) {
832
860
  const usage = usageOf(d.tokens);
833
861
  const reportedCostUsd = typeof d.cost === "number" && Number.isFinite(d.cost) && d.cost >= 0 ? d.cost : void 0;
862
+ const n = /* @__PURE__ */ __name((v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0, "n");
863
+ this.seen.cost += reportedCostUsd ?? 0;
864
+ this.seen.input += n(d.tokens?.input);
865
+ this.seen.output += n(d.tokens?.output);
866
+ this.seen.reasoning += n(d.tokens?.reasoning);
867
+ this.seen.read += n(d.tokens?.cache?.read);
868
+ this.seen.write += n(d.tokens?.cache?.write);
834
869
  const fallback = reportedCostUsd !== void 0 ? {
835
870
  costUsd: reportedCostUsd,
836
871
  costSource: "provider"
@@ -1298,9 +1333,39 @@ ${delta}` : delta;
1298
1333
  this.messageResults.set(messageId, results);
1299
1334
  await this.a.store.setMessageToolResults(messageId, results);
1300
1335
  }
1336
+ /** What the session spent since the baseline that no event of this turn reported. */
1337
+ async unreported() {
1338
+ const baseline = this.baseline;
1339
+ if (baseline === void 0) return;
1340
+ this.baseline = void 0;
1341
+ const info = await this.a.client.session.get?.({
1342
+ sessionID: this.a.sessionId
1343
+ }).catch(() => void 0);
1344
+ const after = info?.tokens;
1345
+ if (after === void 0) return;
1346
+ const before = baseline.tokens;
1347
+ const left = /* @__PURE__ */ __name((now, then, seen) => Math.max(0, (Number(now) || 0) - (Number(then) || 0) - seen), "left");
1348
+ const tokens = {
1349
+ input: left(after.input, before.input, this.seen.input),
1350
+ output: left(after.output, before.output, this.seen.output),
1351
+ reasoning: left(after.reasoning, before.reasoning, this.seen.reasoning),
1352
+ cache: {
1353
+ read: left(after.cache?.read, before.cache?.read, this.seen.read),
1354
+ write: left(after.cache?.write, before.cache?.write, this.seen.write)
1355
+ }
1356
+ };
1357
+ const total = tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write;
1358
+ if (total === 0) return;
1359
+ const cost = left(info?.cost, baseline.cost, this.seen.cost);
1360
+ await this.spend({
1361
+ tokens,
1362
+ cost: cost < 1e-9 ? 0 : cost
1363
+ }, this.compacted ? "history_summary" : "title");
1364
+ }
1301
1365
  async end(outcome) {
1302
1366
  if (this.ended) return;
1303
1367
  this.ended = true;
1368
+ await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
1304
1369
  if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
1305
1370
  await this.flush(true);
1306
1371
  }
@@ -2417,6 +2482,7 @@ ${body}
2417
2482
  })
2418
2483
  } : {}
2419
2484
  };
2485
+ live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
2420
2486
  await live.client.session.prompt({
2421
2487
  sessionID: handle.sessionId,
2422
2488
  text: prompt.text,
@@ -2496,12 +2562,23 @@ ${body}
2496
2562
  await live.turn.decide(ask, reply, {
2497
2563
  tellOpenCode: false
2498
2564
  });
2565
+ live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
2499
2566
  await live.client.session.prompt({
2500
2567
  sessionID: handle.sessionId,
2501
2568
  text: note
2502
2569
  });
2503
2570
  return handle;
2504
2571
  }
2572
+ /** What a session spent so far (`session.get`), or nothing when OpenCode does not say. */
2573
+ async sessionUsage(client, sessionId) {
2574
+ try {
2575
+ return await client.session.get?.({
2576
+ sessionID: sessionId
2577
+ });
2578
+ } catch {
2579
+ return void 0;
2580
+ }
2581
+ }
2505
2582
  /** Interrupt the thread's session (cancel). */
2506
2583
  async interrupt(input) {
2507
2584
  const known = await this.sessions.get(input.threadId);