@dudousxd/nestjs-agent 1.36.0 → 1.36.2

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,8 +1,8 @@
1
1
  import { A2uiStreamOptions, a2uiThreadReplay } from '@dudousxd/nestjs-agent-core/a2ui';
2
- import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalView, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver } from '@dudousxd/nestjs-agent-core';
2
+ import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalView, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, SubscribeOptions, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver } from '@dudousxd/nestjs-agent-core';
3
3
  import { Request, Response } from 'express';
4
4
  import { UiCapabilities } from '@dudousxd/nestjs-agent-core/genui';
5
- import { b as AgentModuleOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter } from './chat-queue.service-CF2fcJDx.cjs';
5
+ import { b as AgentModuleOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter } from './chat-queue.service-fopUN313.cjs';
6
6
 
7
7
  /** Default per-file size cap (20 MiB). */
8
8
  declare const DEFAULT_MAX_ATTACHMENT_BYTES: number;
@@ -355,14 +355,14 @@ declare class AgentService {
355
355
  * {@link subscribeAs} instead: a runId is the only thing standing between a caller and another
356
356
  * actor's answer, tool arguments and tool results.
357
357
  */
358
- subscribe(runId: string): AsyncIterable<Uint8Array>;
358
+ subscribe(runId: string, options?: SubscribeOptions): AsyncIterable<Uint8Array>;
359
359
  /**
360
360
  * {@link subscribe}, gated the way {@link cancel} is: only the actor whose thread is currently
361
361
  * streaming this run may read it. A run that has already finished is reported missing rather than
362
362
  * replayed — the runner clears the thread's active stream as the run ends, and that field is what
363
363
  * ownership is derived from.
364
364
  */
365
- subscribeAs(actor: Actor, runId: string): Promise<AsyncIterable<Uint8Array>>;
365
+ subscribeAs(actor: Actor, runId: string, options?: SubscribeOptions): Promise<AsyncIterable<Uint8Array>>;
366
366
  /**
367
367
  * Approve a parked action call as `actor`. Who may is the call's recorded approver's business
368
368
  * (see {@link assertMayDecide}); `remember` approves later calls of the same tool in the same
@@ -460,6 +460,8 @@ declare class AgentService {
460
460
  * the full read.
461
461
  */
462
462
  private resolveAgentName;
463
+ /** Everything a send reads off `threadId`, in one read where the store allows it. */
464
+ private threadReads;
463
465
  private threadDefaultAgent;
464
466
  private toSummaryView;
465
467
  private toDetailView;
@@ -1,8 +1,8 @@
1
1
  import { A2uiStreamOptions, a2uiThreadReplay } from '@dudousxd/nestjs-agent-core/a2ui';
2
- import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalView, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver } from '@dudousxd/nestjs-agent-core';
2
+ import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalView, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, SubscribeOptions, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver } from '@dudousxd/nestjs-agent-core';
3
3
  import { Request, Response } from 'express';
4
4
  import { UiCapabilities } from '@dudousxd/nestjs-agent-core/genui';
5
- import { b as AgentModuleOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter } from './chat-queue.service-CzXsKVb1.js';
5
+ import { b as AgentModuleOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter } from './chat-queue.service-YjiKVTKE.js';
6
6
 
7
7
  /** Default per-file size cap (20 MiB). */
8
8
  declare const DEFAULT_MAX_ATTACHMENT_BYTES: number;
@@ -355,14 +355,14 @@ declare class AgentService {
355
355
  * {@link subscribeAs} instead: a runId is the only thing standing between a caller and another
356
356
  * actor's answer, tool arguments and tool results.
357
357
  */
358
- subscribe(runId: string): AsyncIterable<Uint8Array>;
358
+ subscribe(runId: string, options?: SubscribeOptions): AsyncIterable<Uint8Array>;
359
359
  /**
360
360
  * {@link subscribe}, gated the way {@link cancel} is: only the actor whose thread is currently
361
361
  * streaming this run may read it. A run that has already finished is reported missing rather than
362
362
  * replayed — the runner clears the thread's active stream as the run ends, and that field is what
363
363
  * ownership is derived from.
364
364
  */
365
- subscribeAs(actor: Actor, runId: string): Promise<AsyncIterable<Uint8Array>>;
365
+ subscribeAs(actor: Actor, runId: string, options?: SubscribeOptions): Promise<AsyncIterable<Uint8Array>>;
366
366
  /**
367
367
  * Approve a parked action call as `actor`. Who may is the call's recorded approver's business
368
368
  * (see {@link assertMayDecide}); `remember` approves later calls of the same tool in the same
@@ -460,6 +460,8 @@ declare class AgentService {
460
460
  * the full read.
461
461
  */
462
462
  private resolveAgentName;
463
+ /** Everything a send reads off `threadId`, in one read where the store allows it. */
464
+ private threadReads;
463
465
  private threadDefaultAgent;
464
466
  private toSummaryView;
465
467
  private toDetailView;
package/dist/index.cjs CHANGED
@@ -1676,6 +1676,26 @@ var RunNotActiveException = class extends import_common5.ConflictException {
1676
1676
  }
1677
1677
  };
1678
1678
 
1679
+ // src/thread-reads.ts
1680
+ function threadReads(store, threadId, separately) {
1681
+ const projecting = store;
1682
+ if (typeof projecting.threadSettings !== "function") {
1683
+ return separately;
1684
+ }
1685
+ let settings;
1686
+ const read = /* @__PURE__ */ __name(() => {
1687
+ settings ??= projecting.threadSettings?.(threadId) ?? Promise.resolve(null);
1688
+ return settings;
1689
+ }, "read");
1690
+ return {
1691
+ owner: /* @__PURE__ */ __name(async () => (await read())?.actorRef ?? null, "owner"),
1692
+ defaultAgent: /* @__PURE__ */ __name(async () => (await read())?.defaultAgent ?? null, "defaultAgent"),
1693
+ persona: /* @__PURE__ */ __name(async () => (await read())?.persona ?? null, "persona"),
1694
+ model: /* @__PURE__ */ __name(async () => (await read())?.model ?? null, "model")
1695
+ };
1696
+ }
1697
+ __name(threadReads, "threadReads");
1698
+
1679
1699
  // src/agent.service.ts
1680
1700
  function _ts_decorate5(decorators, target, key, desc) {
1681
1701
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -1939,7 +1959,7 @@ var AgentService2 = class {
1939
1959
  * else the thread's pinned one, else none. An agent the catalog locks to one model runs on it
1940
1960
  * whatever was sent or pinned, and a send naming another model is refused.
1941
1961
  */
1942
- async resolveModel(actor, agent, requested, threadId) {
1962
+ async resolveModel(actor, agent, requested, threadId, reads) {
1943
1963
  const locked = this.models !== void 0 ? (await this.models.list({
1944
1964
  actor,
1945
1965
  agent
@@ -1950,7 +1970,7 @@ var AgentService2 = class {
1950
1970
  }
1951
1971
  return locked.model;
1952
1972
  }
1953
- const model = requested ?? (threadId !== void 0 ? await this.threadModel(threadId) : null);
1973
+ const model = requested ?? (threadId !== void 0 ? await (reads?.model() ?? this.threadModel(threadId)) : null);
1954
1974
  if (model === null || model === void 0) {
1955
1975
  return void 0;
1956
1976
  }
@@ -2010,10 +2030,11 @@ var AgentService2 = class {
2010
2030
  if ("proposalDecision" in decision) return decision;
2011
2031
  }
2012
2032
  await this.assertWithinQuota(params.actor);
2013
- const target = this.deps.resolveAgent(await this.resolveAgentName(params.agentName, params.threadId));
2033
+ const reads = params.threadId !== void 0 ? this.threadReads(params.threadId) : void 0;
2034
+ const target = this.deps.resolveAgent(await this.resolveAgentName(params.agentName, params.threadId, reads));
2014
2035
  const agentName = target.agentName;
2015
- const persona = await this.resolvePersona(agentName, params.personaId ?? target.persona, params.threadId);
2016
- const model = await this.resolveModel(params.actor, agentName, params.model, params.threadId);
2036
+ const persona = await this.resolvePersona(agentName, params.personaId ?? target.persona, params.threadId, reads);
2037
+ const model = await this.resolveModel(params.actor, agentName, params.model, params.threadId, reads);
2017
2038
  const attachments = await this.resolveAttachments(params.actor, params.attachments ?? []);
2018
2039
  let threadId = params.threadId;
2019
2040
  if (threadId === void 0) {
@@ -2037,9 +2058,9 @@ var AgentService2 = class {
2037
2058
  });
2038
2059
  threadId = created.id;
2039
2060
  } else {
2040
- if (params.authorized !== true) await this.assertOwnsThread(params.actor, threadId);
2061
+ if (params.authorized !== true) await this.assertOwnsThread(params.actor, threadId, reads);
2041
2062
  if (params.personaId !== void 0) {
2042
- await this.pinThreadPersona(threadId, params.personaId);
2063
+ await this.pinThreadPersona(threadId, params.personaId, reads);
2043
2064
  }
2044
2065
  }
2045
2066
  const input = {
@@ -2365,8 +2386,8 @@ var AgentService2 = class {
2365
2386
  * {@link subscribeAs} instead: a runId is the only thing standing between a caller and another
2366
2387
  * actor's answer, tool arguments and tool results.
2367
2388
  */
2368
- subscribe(runId) {
2369
- return this.deps.forAgent().sink.subscribe(runId);
2389
+ subscribe(runId, options) {
2390
+ return this.deps.forAgent().sink.subscribe(runId, options);
2370
2391
  }
2371
2392
  /**
2372
2393
  * {@link subscribe}, gated the way {@link cancel} is: only the actor whose thread is currently
@@ -2374,9 +2395,9 @@ var AgentService2 = class {
2374
2395
  * replayed — the runner clears the thread's active stream as the run ends, and that field is what
2375
2396
  * ownership is derived from.
2376
2397
  */
2377
- async subscribeAs(actor, runId) {
2398
+ async subscribeAs(actor, runId, options) {
2378
2399
  await this.assertOwnsActiveStream(actor, runId);
2379
- return this.subscribe(runId);
2400
+ return this.subscribe(runId, options);
2380
2401
  }
2381
2402
  /**
2382
2403
  * Approve a parked action call as `actor`. Who may is the call's recorded approver's business
@@ -2583,7 +2604,7 @@ var AgentService2 = class {
2583
2604
  * The persona this send runs under — see {@link AgentDepsFactory.resolvePersona}. The thread's
2584
2605
  * pinned persona is read only when the send names none AND the agent has personas to pick from.
2585
2606
  */
2586
- async resolvePersona(agentName, requested, threadId) {
2607
+ async resolvePersona(agentName, requested, threadId, reads) {
2587
2608
  const needsThread = requested === void 0 && threadId !== void 0 && this.deps.personaCatalog(agentName).length > 0;
2588
2609
  return this.deps.resolvePersona({
2589
2610
  agentName,
@@ -2591,16 +2612,16 @@ var AgentService2 = class {
2591
2612
  requested
2592
2613
  } : {},
2593
2614
  ...needsThread ? {
2594
- threadPersona: await threadPersona(this.store, threadId)
2615
+ threadPersona: await (reads?.persona() ?? threadPersona(this.store, threadId))
2595
2616
  } : {}
2596
2617
  });
2597
2618
  }
2598
2619
  /** Pin `persona` on the thread when it is not already — a no-op on a store that cannot. */
2599
- async pinThreadPersona(threadId, persona) {
2620
+ async pinThreadPersona(threadId, persona, reads) {
2600
2621
  if (this.store.updateThread === void 0) {
2601
2622
  return;
2602
2623
  }
2603
- if (await threadPersona(this.store, threadId) !== persona) {
2624
+ if (await (reads?.persona() ?? threadPersona(this.store, threadId)) !== persona) {
2604
2625
  await this.store.updateThread(threadId, {
2605
2626
  persona
2606
2627
  });
@@ -2627,18 +2648,27 @@ var AgentService2 = class {
2627
2648
  * workflow, for a string. A store predating {@link ThreadDefaultAgentReader} still answers, via
2628
2649
  * the full read.
2629
2650
  */
2630
- async resolveAgentName(agentName, threadId) {
2651
+ async resolveAgentName(agentName, threadId, reads) {
2631
2652
  if (agentName !== void 0) {
2632
2653
  return agentName;
2633
2654
  }
2634
2655
  if (threadId !== void 0) {
2635
- const threadDefault = await this.threadDefaultAgent(threadId);
2656
+ const threadDefault = await (reads?.defaultAgent() ?? this.threadDefaultAgent(threadId));
2636
2657
  if (threadDefault !== null) {
2637
2658
  return threadDefault;
2638
2659
  }
2639
2660
  }
2640
2661
  return this.deps.defaultAgentName();
2641
2662
  }
2663
+ /** Everything a send reads off `threadId`, in one read where the store allows it. */
2664
+ threadReads(threadId) {
2665
+ return threadReads(this.store, threadId, {
2666
+ owner: /* @__PURE__ */ __name(() => this.store.ownerOfThread(threadId), "owner"),
2667
+ defaultAgent: /* @__PURE__ */ __name(() => this.threadDefaultAgent(threadId), "defaultAgent"),
2668
+ persona: /* @__PURE__ */ __name(() => threadPersona(this.store, threadId), "persona"),
2669
+ model: /* @__PURE__ */ __name(() => this.threadModel(threadId), "model")
2670
+ });
2671
+ }
2642
2672
  async threadDefaultAgent(threadId) {
2643
2673
  const projecting = this.store;
2644
2674
  if (typeof projecting.defaultAgentForThread === "function") {
@@ -2808,8 +2838,8 @@ var AgentService2 = class {
2808
2838
  * Authorization seam for thread-scoped endpoints: the caller must own the thread. A missing
2809
2839
  * thread is a 404; someone else's thread is a 403 — one actor never touches another's thread.
2810
2840
  */
2811
- async assertOwnsThread(actor, threadId) {
2812
- const owner = await this.store.ownerOfThread(threadId);
2841
+ async assertOwnsThread(actor, threadId, reads) {
2842
+ const owner = await (reads?.owner() ?? this.store.ownerOfThread(threadId));
2813
2843
  if (owner === null) {
2814
2844
  throw new import_common6.NotFoundException(`thread ${threadId} not found`);
2815
2845
  }
@@ -3249,6 +3279,18 @@ AttachmentsController = _ts_decorate8([
3249
3279
  var import_nestjs_agent_core12 = require("@dudousxd/nestjs-agent-core");
3250
3280
  var import_genui2 = require("@dudousxd/nestjs-agent-core/genui");
3251
3281
  var import_common10 = require("@nestjs/common");
3282
+
3283
+ // src/controller/reader-gone.ts
3284
+ function readerGone(res) {
3285
+ const controller = new AbortController();
3286
+ res.once("close", () => controller.abort());
3287
+ return {
3288
+ signal: controller.signal
3289
+ };
3290
+ }
3291
+ __name(readerGone, "readerGone");
3292
+
3293
+ // src/controller/chat.controller.ts
3252
3294
  function _ts_decorate9(decorators, target, key, desc) {
3253
3295
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3254
3296
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -3382,7 +3424,8 @@ var ChatController = class {
3382
3424
  return;
3383
3425
  }
3384
3426
  const { runId, threadId } = result;
3385
- await this.pipe(res, runId, this.agent.subscribe(runId), threadId);
3427
+ const gone = readerGone(res);
3428
+ await this.pipe(res, runId, this.agent.subscribe(runId, gone), threadId, 0, gone.signal);
3386
3429
  }
3387
3430
  /**
3388
3431
  * Attach to a run's stream: every buffered frame, then live ones. `?after=<seq>` (or the SSE
@@ -3392,7 +3435,8 @@ var ChatController = class {
3392
3435
  async stream(req, runId, res, after) {
3393
3436
  const actor = await this.actorResolver.resolve(req);
3394
3437
  const cursor = parseCursor(after) ?? parseCursor(req.headers["last-event-id"]) ?? 0;
3395
- await this.pipe(res, runId, await this.agent.subscribeAs(actor, runId), void 0, cursor);
3438
+ const gone = readerGone(res);
3439
+ await this.pipe(res, runId, await this.agent.subscribeAs(actor, runId, gone), void 0, cursor, gone.signal);
3396
3440
  }
3397
3441
  async cancel(req, runId) {
3398
3442
  const actor = await this.actorResolver.resolve(req);
@@ -3408,8 +3452,11 @@ var ChatController = class {
3408
3452
  * and on any later `GET …/stream`, whichever replica serves it. A reconnecting client passes the
3409
3453
  * last one it saw as `?after=`; frames at or below it are read off the sink but not re-sent.
3410
3454
  * `meta`, `done` and `error` carry no id: `meta` is re-sent on every attach, the terminals once.
3455
+ *
3456
+ * A reader that disconnects aborts `gone`, which the sink honours by ending the subscription even
3457
+ * while the run is silent (parked on a person, or never writing again).
3411
3458
  */
3412
- async pipe(res, runId, events, threadId, after = 0) {
3459
+ async pipe(res, runId, events, threadId, after = 0, gone) {
3413
3460
  res.setHeader("Content-Type", "text/event-stream");
3414
3461
  res.setHeader("Cache-Control", "no-cache");
3415
3462
  res.setHeader("Connection", "keep-alive");
@@ -3438,6 +3485,9 @@ data: ${line}
3438
3485
  }, "forward");
3439
3486
  try {
3440
3487
  for await (const chunk of events) {
3488
+ if (gone?.aborted) {
3489
+ return;
3490
+ }
3441
3491
  buffer += decoder.decode(chunk, {
3442
3492
  stream: true
3443
3493
  });
@@ -3451,11 +3501,17 @@ data: ${line}
3451
3501
  newline = buffer.indexOf("\n");
3452
3502
  }
3453
3503
  }
3504
+ if (gone?.aborted) {
3505
+ return;
3506
+ }
3454
3507
  if (buffer.length > 0) {
3455
3508
  forward(buffer);
3456
3509
  }
3457
3510
  res.write("event: done\ndata: {}\n\n");
3458
3511
  } catch (error) {
3512
+ if (gone?.aborted) {
3513
+ return;
3514
+ }
3459
3515
  const payload = error instanceof import_nestjs_agent_core12.AgentStreamError ? {
3460
3516
  code: error.code,
3461
3517
  message: error.message
@@ -5385,49 +5441,121 @@ SkillDiscoveryService = _ts_decorate22([
5385
5441
 
5386
5442
  // src/in-process-sink.ts
5387
5443
  var import_nestjs_agent_core27 = require("@dudousxd/nestjs-agent-core");
5444
+ var LIVE_TTL_MS = 36e5;
5445
+ var ENDED_GRACE_MS = 6e4;
5388
5446
  var InProcessTokenStreamSink = class {
5389
5447
  static {
5390
5448
  __name(this, "InProcessTokenStreamSink");
5391
5449
  }
5392
5450
  runs = /* @__PURE__ */ new Map();
5451
+ /** Subscribers of a run nothing has opened yet, woken by its `open()`. */
5452
+ waiting = /* @__PURE__ */ new Map();
5393
5453
  buffer(runId) {
5394
5454
  let buf = this.runs.get(runId);
5395
5455
  if (buf === void 0) {
5396
5456
  buf = {
5397
5457
  chunks: [],
5398
5458
  ended: false,
5399
- notify: /* @__PURE__ */ new Set()
5459
+ notify: /* @__PURE__ */ new Set(),
5460
+ lastWriteAt: Date.now(),
5461
+ expiry: void 0
5400
5462
  };
5401
5463
  this.runs.set(runId, buf);
5464
+ this.expireAfter(runId, buf, LIVE_TTL_MS);
5465
+ const waiters = this.waiting.get(runId);
5466
+ this.waiting.delete(runId);
5467
+ for (const resolve2 of waiters ?? []) {
5468
+ resolve2();
5469
+ }
5402
5470
  }
5403
5471
  return buf;
5404
5472
  }
5473
+ expireAfter(runId, buf, ms) {
5474
+ if (buf.expiry !== void 0) {
5475
+ clearTimeout(buf.expiry);
5476
+ }
5477
+ buf.expiry = setTimeout(() => {
5478
+ buf.expiry = void 0;
5479
+ const idle = Date.now() - buf.lastWriteAt;
5480
+ if (!buf.ended && idle < LIVE_TTL_MS) {
5481
+ this.expireAfter(runId, buf, LIVE_TTL_MS - idle);
5482
+ return;
5483
+ }
5484
+ this.drop(runId, buf);
5485
+ }, ms);
5486
+ buf.expiry.unref?.();
5487
+ }
5488
+ /** Forget the run, and let anyone still following it finish as if it had ended. */
5489
+ drop(runId, buf) {
5490
+ if (buf.expiry !== void 0) {
5491
+ clearTimeout(buf.expiry);
5492
+ buf.expiry = void 0;
5493
+ }
5494
+ if (this.runs.get(runId) === buf) {
5495
+ this.runs.delete(runId);
5496
+ }
5497
+ buf.ended = true;
5498
+ this.wake(buf);
5499
+ }
5405
5500
  wake(buf) {
5406
5501
  for (const resolve2 of buf.notify) {
5407
5502
  resolve2();
5408
5503
  }
5409
5504
  buf.notify.clear();
5410
5505
  }
5506
+ finish(runId, buf) {
5507
+ buf.ended = true;
5508
+ this.wake(buf);
5509
+ this.expireAfter(runId, buf, ENDED_GRACE_MS);
5510
+ }
5511
+ /**
5512
+ * The run's writer. Each call finds the run's buffer afresh, so a run that went quiet past the
5513
+ * live TTL (parked on a person) and then writes again starts a new buffer its next subscriber
5514
+ * follows, rather than writing into one nothing reads.
5515
+ */
5411
5516
  open(runId) {
5412
- const buf = this.buffer(runId);
5517
+ this.buffer(runId);
5413
5518
  return {
5414
5519
  write: /* @__PURE__ */ __name((chunk) => {
5520
+ const buf = this.buffer(runId);
5415
5521
  buf.chunks.push(chunk);
5522
+ buf.lastWriteAt = Date.now();
5416
5523
  this.wake(buf);
5417
5524
  }, "write"),
5418
5525
  end: /* @__PURE__ */ __name(() => {
5419
- buf.ended = true;
5420
- this.wake(buf);
5526
+ this.finish(runId, this.buffer(runId));
5421
5527
  }, "end"),
5422
5528
  fail: /* @__PURE__ */ __name((error) => {
5529
+ const buf = this.buffer(runId);
5423
5530
  buf.failure = error;
5424
- buf.ended = true;
5425
- this.wake(buf);
5531
+ this.finish(runId, buf);
5426
5532
  }, "fail")
5427
5533
  };
5428
5534
  }
5429
- async *subscribe(runId) {
5430
- const buf = this.buffer(runId);
5535
+ async *subscribe(runId, options = {}) {
5536
+ const { signal } = options;
5537
+ let buf = this.runs.get(runId);
5538
+ while (buf === void 0) {
5539
+ const waiters = this.waiting.get(runId) ?? /* @__PURE__ */ new Set();
5540
+ this.waiting.set(runId, waiters);
5541
+ let resolve2 = /* @__PURE__ */ __name(() => {
5542
+ }, "resolve");
5543
+ const opened = new Promise((r) => {
5544
+ resolve2 = r;
5545
+ });
5546
+ waiters.add(resolve2);
5547
+ try {
5548
+ if (!await untilAborted(opened, signal)) {
5549
+ return;
5550
+ }
5551
+ } finally {
5552
+ waiters.delete(resolve2);
5553
+ if (waiters.size === 0 && this.waiting.get(runId) === waiters) {
5554
+ this.waiting.delete(runId);
5555
+ }
5556
+ }
5557
+ buf = this.runs.get(runId);
5558
+ }
5431
5559
  let index = 0;
5432
5560
  while (true) {
5433
5561
  while (index < buf.chunks.length) {
@@ -5443,13 +5571,56 @@ var InProcessTokenStreamSink = class {
5443
5571
  }
5444
5572
  return;
5445
5573
  }
5446
- await new Promise((resolve2) => buf.notify.add(resolve2));
5574
+ const current = buf;
5575
+ let resolve2 = /* @__PURE__ */ __name(() => {
5576
+ }, "resolve");
5577
+ const woken = new Promise((r) => {
5578
+ resolve2 = r;
5579
+ });
5580
+ current.notify.add(resolve2);
5581
+ try {
5582
+ if (!await untilAborted(woken, signal)) {
5583
+ return;
5584
+ }
5585
+ } finally {
5586
+ current.notify.delete(resolve2);
5587
+ }
5447
5588
  }
5448
5589
  }
5449
5590
  close(runId) {
5591
+ const buf = this.runs.get(runId);
5592
+ if (buf?.expiry !== void 0) {
5593
+ clearTimeout(buf.expiry);
5594
+ }
5450
5595
  this.runs.delete(runId);
5451
5596
  }
5452
5597
  };
5598
+ async function untilAborted(promise, signal) {
5599
+ if (signal === void 0) {
5600
+ await promise;
5601
+ return true;
5602
+ }
5603
+ if (signal.aborted) {
5604
+ return false;
5605
+ }
5606
+ let onAbort = /* @__PURE__ */ __name(() => {
5607
+ }, "onAbort");
5608
+ const aborted = new Promise((resolve2) => {
5609
+ onAbort = /* @__PURE__ */ __name(() => resolve2(false), "onAbort");
5610
+ signal.addEventListener("abort", onAbort, {
5611
+ once: true
5612
+ });
5613
+ });
5614
+ try {
5615
+ return await Promise.race([
5616
+ promise.then(() => true),
5617
+ aborted
5618
+ ]);
5619
+ } finally {
5620
+ signal.removeEventListener("abort", onAbort);
5621
+ }
5622
+ }
5623
+ __name(untilAborted, "untilAborted");
5453
5624
 
5454
5625
  // src/ledger-quota-provider.ts
5455
5626
  var import_nestjs_agent_core28 = require("@dudousxd/nestjs-agent-core");
@@ -5468,13 +5639,22 @@ var LedgerQuotaProvider = class {
5468
5639
  async report(query) {
5469
5640
  const now = query.now ?? /* @__PURE__ */ new Date();
5470
5641
  const actorRef = query.actor.id;
5471
- const windows = [
5472
- await this.dayWindow(actorRef, now)
5473
- ];
5474
- if (this.store.usageBetween !== void 0) {
5475
- const range = (0, import_nestjs_agent_core28.quotaPeriodRange)("month", now);
5476
- const used = await this.store.usageBetween(actorRef, range.fromDay, range.toDay);
5477
- windows.push(this.window("month", used.usedTokens, this.usd(used), range.resetsAt));
5642
+ const day = (0, import_nestjs_agent_core28.quotaPeriodRange)("day", now);
5643
+ const month = (0, import_nestjs_agent_core28.quotaPeriodRange)("month", now);
5644
+ const windows = [];
5645
+ if (this.store.usageWindows !== void 0) {
5646
+ const [dayUsed, monthUsed] = await this.store.usageWindows(actorRef, [
5647
+ day,
5648
+ month
5649
+ ]);
5650
+ windows.push(this.usedWindow("day", dayUsed, day.resetsAt));
5651
+ windows.push(this.usedWindow("month", monthUsed, month.resetsAt));
5652
+ } else {
5653
+ windows.push(this.usedWindow("day", await this.store.quotaToday(actorRef, day.fromDay), day.resetsAt));
5654
+ if (this.store.usageBetween !== void 0) {
5655
+ const used = await this.store.usageBetween(actorRef, month.fromDay, month.toDay);
5656
+ windows.push(this.usedWindow("month", used, month.resetsAt));
5657
+ }
5478
5658
  }
5479
5659
  const blocked = (0, import_nestjs_agent_core28.exhaustedWindow)(windows);
5480
5660
  const warning = (0, import_nestjs_agent_core28.quotaWarning)(windows);
@@ -5488,10 +5668,8 @@ var LedgerQuotaProvider = class {
5488
5668
  } : {}
5489
5669
  };
5490
5670
  }
5491
- async dayWindow(actorRef, now) {
5492
- const range = (0, import_nestjs_agent_core28.quotaPeriodRange)("day", now);
5493
- const used = await this.store.quotaToday(actorRef, range.fromDay);
5494
- return this.window("day", used.usedTokens, this.usd(used), range.resetsAt);
5671
+ usedWindow(period, used, resetsAt) {
5672
+ return this.window(period, used?.usedTokens ?? 0, used === void 0 ? 0 : this.usd(used), resetsAt);
5495
5673
  }
5496
5674
  /** The window's USD: every priced row, or (with `countEstimatedCost: false`) reported cost only. */
5497
5675
  usd(used) {
@@ -7336,7 +7514,7 @@ var AgUiRunHandler = class {
7336
7514
  seq: 0
7337
7515
  };
7338
7516
  try {
7339
- const frames = (0, import_ag_ui.agUiFramesFromNdjson)(this.agent.subscribe(runId));
7517
+ const frames = (0, import_ag_ui.agUiFramesFromNdjson)(this.agent.subscribe(runId, readerGone(res)));
7340
7518
  for await (const event of (0, import_ag_ui.agUiEvents)(frames, {
7341
7519
  ...options,
7342
7520
  cursor
@@ -7667,7 +7845,7 @@ var A2uiRunHandler = class {
7667
7845
  `);
7668
7846
  }, "write");
7669
7847
  try {
7670
- const frames = (0, import_ag_ui2.agUiFramesFromNdjson)(this.agent.subscribe(runId));
7848
+ const frames = (0, import_ag_ui2.agUiFramesFromNdjson)(this.agent.subscribe(runId, readerGone(res)));
7671
7849
  for await (const event of (0, import_ag_ui2.agUiEvents)(frames, {
7672
7850
  threadId: options.threadId ?? runId,
7673
7851
  runId,