{"version":3,"file":"lifecycle-loading-tracker.cjs","names":["#store","#isDisposed","#lastTerminalLifecycleSeq","isRootNamespace"],"sources":["../../src/stream/lifecycle-loading-tracker.ts"],"sourcesContent":["/**\n * Drives the {@link RootSnapshot.isLoading} flag from root lifecycle\n * events.\n *\n * # What it does\n *\n * The tracker watches a stream of protocol events and flips the\n * `isLoading` slot of a {@link StreamStore} based on root-namespace\n * `lifecycle` payloads:\n *\n *   - `running`                       → `isLoading = true`\n *   - `completed` / `failed` / `interrupted`\n *                                     → `isLoading = false`\n *\n * Non-root, non-lifecycle, and unknown events are ignored.\n *\n * # Why it lives in its own class\n *\n * Lifecycle handling has two subtleties that we want to keep out of\n * the {@link StreamController}'s critical path:\n *\n *   1. **Stale `running` filtering.** SSE replays older events on\n *      reconnect — including a `running` lifecycle that fired before\n *      the run terminated. Without filtering, that replay would flip\n *      `isLoading` back to `true` after a `completed` already brought\n *      it down. We track the highest terminal `seq` we've seen and\n *      drop any `running` whose `seq` is at or below it.\n *   2. **Deferred terminal flip.** The flip from `true → false` is\n *      pushed to the next macrotask (`setTimeout(..., 0)`). This\n *      gives synchronous consumers — most notably `for await`\n *      iterators in framework bindings — one event-loop tick to\n *      observe terminal-related state (e.g. the final assistant\n *      message landing in `values`) before `isLoading` settles.\n *\n * # Why it's safe to register the listener as `controller.onEvent`\n *\n * The tracker subscribes to the controller's root event bus via the\n * exported {@link listener} arrow. Because the listener is bound at\n * construction time, removing it later (`bus.delete(tracker.listener)`)\n * works without `bind()` gymnastics in the controller.\n *\n * @typeParam T - The snapshot shape; must contain an `isLoading` flag.\n */\nimport type { Event, LifecycleEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"./store.js\";\nimport { isRootNamespace } from \"./namespace.js\";\n\n/**\n * Minimal contract the snapshot must satisfy. The tracker only\n * touches `isLoading`, leaving everything else for the controller.\n */\ntype LoadingSnapshot = { readonly isLoading: boolean };\n\n/**\n * Drives root-snapshot `isLoading` from root lifecycle events.\n */\nexport class LifecycleLoadingTracker<T extends LoadingSnapshot> {\n  /** Snapshot store whose `isLoading` slot we manage. */\n  readonly #store: StreamStore<T>;\n\n  /**\n   * Disposal probe. Consulted from the deferred `setTimeout` so a\n   * controller torn down between scheduling and firing doesn't end\n   * up writing to a defunct store.\n   */\n  readonly #isDisposed: () => boolean;\n\n  /**\n   * Highest sequence number of a terminal lifecycle we've observed.\n   * `running` events at or below this seq are stale replays and\n   * are dropped to avoid flipping the loading flag back on after the\n   * run has already ended.\n   */\n  #lastTerminalLifecycleSeq = -1;\n\n  /**\n   * @param params.store      - Store whose `isLoading` slot we drive.\n   * @param params.isDisposed - Disposal probe consulted from deferred callbacks.\n   */\n  constructor(params: { store: StreamStore<T>; isDisposed: () => boolean }) {\n    this.#store = params.store;\n    this.#isDisposed = params.isDisposed;\n  }\n\n  /**\n   * Bound listener suitable for `EventBus.subscribe`. Re-exposed as a\n   * stable property so the controller can later remove the same\n   * function reference from the bus on teardown.\n   */\n  readonly listener = (event: Event): void => {\n    this.handle(event);\n  };\n\n  /**\n   * Reset internal state when rebinding to a new thread.\n   *\n   * The terminal-seq guard is per-thread: a new thread's `running`\n   * events are not stale relative to the old thread's terminals.\n   */\n  reset(): void {\n    this.#lastTerminalLifecycleSeq = -1;\n  }\n\n  /**\n   * Process a single protocol event.\n   *\n   * Filters down to root-namespace lifecycle events, then mutates the\n   * store's `isLoading` slot. All other events are ignored.\n   *\n   * @param event - Any protocol event from the controller's root bus.\n   */\n  handle(event: Event): void {\n    if (event.method !== \"lifecycle\") return;\n    if (!isRootNamespace(event.params.namespace)) return;\n    const lifecycle = (event as LifecycleEvent).params.data as {\n      event?: string;\n    };\n    const seq = typeof event.seq === \"number\" ? event.seq : undefined;\n    if (lifecycle?.event === \"running\") {\n      // Drop stale `running` replays that arrive *after* a terminal\n      // for the same run. SSE re-streams history on reconnect; without\n      // this filter the loading flag would oscillate.\n      if (seq != null && seq <= this.#lastTerminalLifecycleSeq) {\n        return;\n      }\n      this.#store.setState((s) =>\n        s.isLoading ? s : { ...s, isLoading: true }\n      );\n      return;\n    }\n    if (\n      lifecycle?.event === \"completed\" ||\n      lifecycle?.event === \"failed\" ||\n      lifecycle?.event === \"interrupted\"\n    ) {\n      if (seq != null) {\n        this.#lastTerminalLifecycleSeq = Math.max(\n          this.#lastTerminalLifecycleSeq,\n          seq\n        );\n      }\n      // Flip `isLoading=false` on the next macrotask so synchronous\n      // consumers iterating events get one tick to observe terminal\n      // state (the final values snapshot etc.) before the loading\n      // indicator drops.\n      setTimeout(() => {\n        if (this.#isDisposed()) return;\n        this.#store.setState((s) =>\n          s.isLoading ? { ...s, isLoading: false } : s\n        );\n      }, 0);\n    }\n  }\n}\n"],"mappings":";;;;;AAwDA,IAAa,0BAAb,MAAgE;;CAE9D;;;;;;CAOA;;;;;;;CAQA,4BAA4B;;;;;CAM5B,YAAY,QAA8D;AACxE,QAAA,QAAc,OAAO;AACrB,QAAA,aAAmB,OAAO;;;;;;;CAQ5B,YAAqB,UAAuB;AAC1C,OAAK,OAAO,MAAM;;;;;;;;CASpB,QAAc;AACZ,QAAA,2BAAiC;;;;;;;;;;CAWnC,OAAO,OAAoB;AACzB,MAAI,MAAM,WAAW,YAAa;AAClC,MAAI,CAACG,kBAAAA,gBAAgB,MAAM,OAAO,UAAU,CAAE;EAC9C,MAAM,YAAa,MAAyB,OAAO;EAGnD,MAAM,MAAM,OAAO,MAAM,QAAQ,WAAW,MAAM,MAAM,KAAA;AACxD,MAAI,WAAW,UAAU,WAAW;AAIlC,OAAI,OAAO,QAAQ,OAAO,MAAA,yBACxB;AAEF,SAAA,MAAY,UAAU,MACpB,EAAE,YAAY,IAAI;IAAE,GAAG;IAAG,WAAW;IAAM,CAC5C;AACD;;AAEF,MACE,WAAW,UAAU,eACrB,WAAW,UAAU,YACrB,WAAW,UAAU,eACrB;AACA,OAAI,OAAO,KACT,OAAA,2BAAiC,KAAK,IACpC,MAAA,0BACA,IACD;AAMH,oBAAiB;AACf,QAAI,MAAA,YAAkB,CAAE;AACxB,UAAA,MAAY,UAAU,MACpB,EAAE,YAAY;KAAE,GAAG;KAAG,WAAW;KAAO,GAAG,EAC5C;MACA,EAAE"}