{"version":3,"file":"lifecycle-C4kwYJ-N.mjs","names":[],"sources":["../src/batteries/llm/chat_common/lifecycle.ts"],"sourcesContent":["/**\n * Shared, provider-neutral lifecycle/boot-progress contract for the on-device + remote LLM batteries.\n *\n * @remarks\n * Each battery already exposes a provider-shaped `onInitProgress` covering only the **download** phase\n * (and shaped differently per provider). This module adds a NORMALIZED layer on top: a coarse phase\n * machine — `loading → compiling → ready → generating → complete` (or `error`) — observable via an\n * aggregate firehose callback ({@link BatteryLifecycleHooks.onLifecycle}) AND targeted per-phase hooks.\n * It exists because the WebGPU/wasm **boot** between download and first token (engine/graph/shader\n * compilation, accelerator registration) was otherwise invisible to a consumer, and because there was no\n * cross-battery notion of \"what phase is this model in right now.\" That boot span is now surfaced as the\n * `compiling` phase — a COARSE marker (the runtimes expose the boundary, not a granular progress stream).\n *\n * The hooks are OPT-IN and additive: `onInitProgress` is unchanged; where a provider reports download\n * progress, the battery ALSO forwards it into a `loading` lifecycle report (with normalized `progress`\n * 0..1 and the raw payload on `raw`). This submodule is private to the bundled batteries — consumers\n * import the re-exported names from each battery's public subpath, never from here.\n */\n\nimport { DateTime } from 'luxon'\nimport type { GpuBudget } from './gpu_budget'\n\n/** The coarse lifecycle phases a battery transitions through. */\nexport type BatteryLifecyclePhase =\n  | 'loading'\n  | 'compiling'\n  | 'ready'\n  | 'generating'\n  | 'complete'\n  | 'error'\n\n/** Which battery emitted a lifecycle report. */\nexport type BatteryLifecycleBattery =\n  | 'transformers_js'\n  | 'litert_lm'\n  | 'webllm'\n  | 'transformers_js_embed'\n  | 'transformers_js_stt'\n  | 'transformers_js_caption'\n  | 'transformers_js_generation'\n  | 'transformers_js_tts'\n  | 'local_diffusion_generation'\n  | 'tesseract_js_ocr'\n\n/** A single normalized lifecycle report. */\nexport interface BatteryLifecycleReport {\n  /** The phase being entered. */\n  phase: BatteryLifecyclePhase\n  /** The battery that produced this report. */\n  battery: BatteryLifecycleBattery\n  /** Best-effort model identifier (the `model` option; `'<stream>'` / `'<blob>'` when not a string). */\n  model: string\n  /** ISO-8601 timestamp stamped when the report was emitted. */\n  at: string\n  /** Human-readable detail, e.g. `'booting WebGPU runtime'`. */\n  detail?: string\n  /**\n   * Normalized progress in `0..1`, when the provider reports it. Emitted during the `loading` phase\n   * (weights download/compile) and — for engines that stream per-step generation progress, e.g. the\n   * diffusion denoise loop — during the `generating` phase as well.\n   */\n  progress?: number\n  /** The provider's own progress payload, passed through verbatim (the `loading` or `generating` phase). */\n  raw?: unknown\n  /** The failure, populated only when `phase === 'error'`. */\n  error?: unknown\n  /**\n   * The probed WebGPU device budget, populated on the `ready` phase for on-device batteries running on\n   * the WebGPU EP. SURFACED, not enforced — the consumer reads this to know the per-allocation ceiling\n   * (the wall an over-large context window hits) and choose its window accordingly. Absent on non-WebGPU\n   * runtimes (Node/wasm) and on every other phase. See {@link GpuBudget}.\n   */\n  gpuBudget?: GpuBudget\n}\n\n/** A lifecycle report consumer. */\nexport type BatteryLifecycleCallback = (report: BatteryLifecycleReport) => void\n\n/**\n * The opt-in lifecycle option block mixed into each battery's options interface. Every phase transition\n * fires {@link onLifecycle} (the firehose) AND the matching per-phase hook; subscribe to either or both.\n * All optional — omitting them leaves behavior byte-for-byte unchanged.\n */\nexport interface BatteryLifecycleHooks {\n  /** Fires on EVERY phase transition (the firehose). */\n  onLifecycle?: BatteryLifecycleCallback\n  /** Weights/runtime loading — may fire repeatedly with `progress` as the provider reports it. */\n  onLoading?: BatteryLifecycleCallback\n  /**\n   * Engine/graph/shader compilation after download, before the first token. A COARSE marker: the\n   * on-device runtimes (LiteRT `Engine.create`, transformers.js `from_pretrained`) expose the boundary —\n   * download done, opaque WebGPU/WASM graph build about to run — but NOT a progress stream, so `progress`\n   * is usually absent. Often the slowest part of a cold start; without this it was invisible.\n   */\n  onCompiling?: BatteryLifecycleCallback\n  /** Engine/pipeline resolved and cached, before the first generation. */\n  onReady?: BatteryLifecycleCallback\n  /**\n   * The generate call is in progress (fires per turn). Fires once immediately before the provider call\n   * for most engines; engines that stream per-step generation progress (e.g. a diffusion denoise loop)\n   * fire it REPEATEDLY during generation, each carrying a `progress` in `0..1`.\n   */\n  onGenerating?: BatteryLifecycleCallback\n  /** After the turn's output is parsed + persisted, before `ack` (fires per turn). */\n  onComplete?: BatteryLifecycleCallback\n  /** A load or generation failure (paired with `nack`). */\n  onError?: BatteryLifecycleCallback\n}\n\n/** Map each phase to the per-phase hook key on {@link BatteryLifecycleHooks}. */\nconst PER_PHASE_HOOK: Record<BatteryLifecyclePhase, keyof BatteryLifecycleHooks> = {\n  loading: 'onLoading',\n  compiling: 'onCompiling',\n  ready: 'onReady',\n  generating: 'onGenerating',\n  complete: 'onComplete',\n  error: 'onError',\n}\n\n/** Invoke a consumer callback, swallowing any throw so a misbehaving hook never breaks a dispatch. */\nconst safeInvoke = (\n  cb: BatteryLifecycleCallback | undefined,\n  report: BatteryLifecycleReport\n): void => {\n  if (typeof cb !== 'function') return\n  try {\n    cb(report)\n  } catch {\n    // A throwing consumer hook must never abort loading or a turn. Intentionally swallowed.\n  }\n}\n\n/**\n * Build a {@link BatteryLifecycleReport} (stamping `at`) and dispatch it to the firehose\n * ({@link BatteryLifecycleHooks.onLifecycle}) AND the per-phase hook for `phase`. A no-op when `hooks`\n * is undefined or carries no relevant callbacks. Defensive: each callback is invoked through\n * {@link safeInvoke}, so a throwing consumer never disrupts the battery.\n *\n * @param hooks - The merged lifecycle hooks (may be undefined).\n * @param battery - Which battery is emitting.\n * @param model - Best-effort model id string.\n * @param phase - The phase being entered.\n * @param extra - Optional `detail` / `progress` / `raw` / `error` fields.\n * @param now - Injectable clock for tests; defaults to luxon `DateTime.now().toISO()`.\n */\nexport const emitLifecycle = (\n  hooks: BatteryLifecycleHooks | undefined,\n  battery: BatteryLifecycleBattery,\n  model: string,\n  phase: BatteryLifecyclePhase,\n  extra?: Partial<\n    Pick<BatteryLifecycleReport, 'detail' | 'progress' | 'raw' | 'error' | 'gpuBudget'>\n  >,\n  now: () => string = () => DateTime.now().toISO() as string\n): void => {\n  if (!hooks) return\n  if (!hooks.onLifecycle && !hooks[PER_PHASE_HOOK[phase]]) return\n  const report: BatteryLifecycleReport = {\n    phase,\n    battery,\n    model,\n    at: now(),\n    ...(extra ?? {}),\n  }\n  safeInvoke(hooks.onLifecycle, report)\n  safeInvoke(hooks[PER_PHASE_HOOK[phase]], report)\n}\n\n/** Default {@link emitLifecycle}. */\nexport const defaultEmitLifecycle = emitLifecycle\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA8GA,IAAM,iBAA6E;CACjF,SAAS;CACT,WAAW;CACX,OAAO;CACP,YAAY;CACZ,UAAU;CACV,OAAO;AACT;;AAGA,IAAM,cACJ,IACA,WACS;CACT,IAAI,OAAO,OAAO,YAAY;CAC9B,IAAI;EACF,GAAG,MAAM;CACX,QAAQ,CAER;AACF;;;;;;;;;;;;;;AAeA,IAAa,iBACX,OACA,SACA,OACA,OACA,OAGA,YAA0B,SAAS,IAAI,EAAE,MAAM,MACtC;CACT,IAAI,CAAC,OAAO;CACZ,IAAI,CAAC,MAAM,eAAe,CAAC,MAAM,eAAe,SAAS;CACzD,MAAM,SAAiC;EACrC;EACA;EACA;EACA,IAAI,IAAI;EACR,GAAI,SAAS,CAAC;CAChB;CACA,WAAW,MAAM,aAAa,MAAM;CACpC,WAAW,MAAM,eAAe,SAAS,MAAM;AACjD;;AAGA,IAAa,uBAAuB"}