@effect-uai/core 0.17.0 → 0.18.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.
Files changed (79) hide show
  1. package/README.md +1 -1
  2. package/dist/decision-model/Decision.test.mjs +4 -4
  3. package/dist/dist-C8RmtoR8.mjs +172 -0
  4. package/dist/dist-C8RmtoR8.mjs.map +1 -0
  5. package/dist/dist-CorQTQL-.mjs +1533 -0
  6. package/dist/dist-CorQTQL-.mjs.map +1 -0
  7. package/dist/dist-DNeuh84d.mjs +7 -0
  8. package/dist/dist-DNeuh84d.mjs.map +1 -0
  9. package/dist/dist-DzeSyeYF.mjs +10253 -0
  10. package/dist/dist-DzeSyeYF.mjs.map +1 -0
  11. package/dist/domain/Transcript.test.mjs +3 -3
  12. package/dist/domain/Turn.test.mjs +2 -2
  13. package/dist/embedding-model/EmbeddingModel.test.mjs +2 -2
  14. package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
  15. package/dist/index.C4tKZ0yW-LuFnBtmu.mjs +7042 -0
  16. package/dist/index.C4tKZ0yW-LuFnBtmu.mjs.map +1 -0
  17. package/dist/language-model/LanguageModel.test.mjs +2 -2
  18. package/dist/loop/Loop.mjs.map +1 -1
  19. package/dist/loop/Loop.test.mjs +3 -3
  20. package/dist/math/Probability.test.mjs +2 -2
  21. package/dist/messenger/MessengerAdapter.test.mjs +3 -3
  22. package/dist/music-generator/MusicGenerator.test.mjs +3 -3
  23. package/dist/observability/Metrics.test.mjs +3 -3
  24. package/dist/observability/Telemetry.d.mts +1 -1
  25. package/dist/observability/Telemetry.mjs +1 -1
  26. package/dist/observability/Telemetry.mjs.map +1 -1
  27. package/dist/observability/Telemetry.test.mjs +4 -4
  28. package/dist/observability/Telemetry.test.mjs.map +1 -1
  29. package/dist/realtime/RealtimeSession.test.mjs +4 -4
  30. package/dist/retry/Retry.test.mjs +2 -2
  31. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +3 -3
  32. package/dist/streaming/JSONL.test.mjs +2 -2
  33. package/dist/streaming/Multipart.d.mts +1 -1
  34. package/dist/streaming/Multipart.mjs +1 -1
  35. package/dist/streaming/Multipart.mjs.map +1 -1
  36. package/dist/streaming/SSE.d.mts +1 -1
  37. package/dist/streaming/SSE.mjs +2 -2
  38. package/dist/streaming/SSE.mjs.map +1 -1
  39. package/dist/streaming/SSE.test.mjs +2 -2
  40. package/dist/streaming/Settle.test.mjs +3 -3
  41. package/dist/streaming/WebSocketSession.d.mts +5 -3
  42. package/dist/streaming/WebSocketSession.d.mts.map +1 -1
  43. package/dist/streaming/WebSocketSession.mjs +22 -10
  44. package/dist/streaming/WebSocketSession.mjs.map +1 -1
  45. package/dist/streaming/WebSocketSession.test.mjs +41 -5
  46. package/dist/streaming/WebSocketSession.test.mjs.map +1 -1
  47. package/dist/structured-format/StructuredFormat.mjs.map +1 -1
  48. package/dist/structured-format/StructuredFormat.test.mjs +2 -2
  49. package/dist/testing/FakeWebSocket.d.mts +5 -1
  50. package/dist/testing/FakeWebSocket.d.mts.map +1 -1
  51. package/dist/testing/FakeWebSocket.mjs +12 -6
  52. package/dist/testing/FakeWebSocket.mjs.map +1 -1
  53. package/dist/tool/Approval.test.mjs +2 -2
  54. package/dist/tool/Tool.mjs.map +1 -1
  55. package/dist/tool/Tool.test.mjs +3 -3
  56. package/dist/tool/Toolkit.test.mjs +3 -3
  57. package/dist/transcriber/Transcriber.test.mjs +3 -3
  58. package/package.json +3 -3
  59. package/src/embedding-model/EmbeddingModel.test.ts +2 -2
  60. package/src/loop/Loop.ts +1 -1
  61. package/src/observability/Telemetry.test.ts +1 -1
  62. package/src/observability/Telemetry.ts +2 -2
  63. package/src/streaming/Multipart.ts +1 -1
  64. package/src/streaming/SSE.ts +2 -2
  65. package/src/streaming/WebSocketSession.test.ts +54 -2
  66. package/src/streaming/WebSocketSession.ts +27 -6
  67. package/src/structured-format/StructuredFormat.ts +1 -1
  68. package/src/testing/FakeWebSocket.ts +21 -6
  69. package/src/tool/Tool.ts +1 -1
  70. package/dist/dist-BaenoU1A.mjs +0 -145
  71. package/dist/dist-BaenoU1A.mjs.map +0 -1
  72. package/dist/dist-nohcnkD6.mjs +0 -182
  73. package/dist/dist-nohcnkD6.mjs.map +0 -1
  74. package/dist/magic-string.es-CB2hOw9v.mjs +0 -1014
  75. package/dist/magic-string.es-CB2hOw9v.mjs.map +0 -1
  76. package/dist/test.DNmyFkvJ-YPzDR0Co.mjs +0 -10254
  77. package/dist/test.DNmyFkvJ-YPzDR0Co.mjs.map +0 -1
  78. package/dist/utils-CiH_DjFW.mjs +0 -3918
  79. package/dist/utils-CiH_DjFW.mjs.map +0 -1
@@ -2,8 +2,8 @@ import { IncompleteTurn, RateLimited } from "../domain/AiError.mjs";
2
2
  import { assistantText, userText } from "../domain/Items.mjs";
3
3
  import { TurnEvent } from "../domain/Turn.mjs";
4
4
  import { LanguageModel, turn, turnFromStream } from "./LanguageModel.mjs";
5
- import { m as it, o as describe } from "../utils-CiH_DjFW.mjs";
6
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
5
+ import { b as it, m as describe } from "../dist-DzeSyeYF.mjs";
6
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
7
7
  import { layer } from "../testing/MockProvider.mjs";
8
8
  import { Cause, Effect, Exit, Layer, Option, Stream } from "effect";
9
9
  //#region src/language-model/LanguageModel.test.ts
@@ -1 +1 @@
1
- {"version":3,"file":"Loop.mjs","names":["Arr"],"sources":["../../src/loop/Loop.ts"],"sourcesContent":["/**\n * Pull-based `loop` for state-threaded sub-streams.\n *\n * Each iteration runs a body that returns a `Stream<Step<A, S>>`. The body\n * emits values via `Loop.value(a)` and signals iteration control via\n * `Loop.next(state)` (continue with new state) or `Loop.stop()` (terminate).\n * The loop unwraps `Value` steps back to `A` for downstream consumers, so\n * the resulting stream is a plain `Stream<A>`.\n *\n * The next body stream is only pulled when downstream pulls the outer\n * stream - no producer fiber, no queue buffering. Cancellation, failures,\n * scoped resources, and backpressure stay aligned with normal Stream\n * semantics.\n *\n * Convention: a `Next` or `Stop` step is the terminal element of a body's\n * iteration. Values emitted in the same chunk after one are discarded\n * (their producing side effects may already have run). The `Loop.next` /\n * `Loop.stop` helpers each emit a single terminal step, so concatenate\n * your values before them (`values.pipe(Stream.concat(Loop.next(state)))`).\n */\nimport {\n Array as Arr,\n Cause,\n Channel,\n Data,\n Effect,\n Exit,\n Function,\n Match,\n Option,\n Ref,\n Scope,\n Stream,\n SubscriptionRef,\n} from \"effect\"\nimport { IncompleteTurn } from \"../domain/AiError.js\"\nimport { isTurnComplete, type Turn, type TurnEvent } from \"../domain/Turn.js\"\n\n// ---------------------------------------------------------------------------\n// Step type - the body's emit shape\n// ---------------------------------------------------------------------------\n\n/**\n * The tagged union a body emits per pull. `Value` carries a payload that\n * flows downstream. `Next` ends the current iteration and continues with a\n * new state. `Stop` ends the loop entirely with no carried state.\n * `StopWith` also ends the loop but carries a final state that `loopOver`\n * will thread to the next input and `loopWithState` will write to its\n * `SubscriptionRef` before the loop ends. Plain `loop` has no next\n * iteration to apply it to and treats `StopWith` like `Stop`.\n *\n * `Stop` is intentionally `{}` so the bare `stop()` helper doesn't\n * constrain `S` from a body's stream type — every body has a `Stop`\n * variant in its union, and forcing `S` to flow through it would break\n * inference whenever the body never uses `next` / `stop(state)`.\n */\nexport type Step<A, S> = Data.TaggedEnum<{\n Value: { readonly value: A }\n Next: { readonly state: S }\n Stop: {}\n StopWith: { readonly state: S }\n}>\n\ninterface StepDef extends Data.TaggedEnum.WithGenerics<2> {\n readonly taggedEnum: Step<this[\"A\"], this[\"B\"]>\n}\n\nconst Step = Data.taggedEnum<StepDef>()\n\n/**\n * Bare step constructors, so a loop body can emit `Value(...)`, `Next(...)`,\n * `Stop()`, `StopWith(...)` directly inside a `Stream.make(...)` without the\n * `Step.` prefix. (The `Step` object itself can't be exported under that name\n * because it collides with the exported `Step` type.) For the common cases\n * prefer the `value` / `next` / `stop` helpers, which also handle the\n * single-element-stream wrapping for `next` / `stop`.\n */\nexport const { Value, Next, Stop, StopWith } = Step\n\n/** Wrap a value so it flows through the loop to downstream consumers. */\nexport const value = <A>(a: A): Step<A, never> => Step.Value({ value: a })\n\n/**\n * End the current iteration and continue with a new state. Emits a single\n * terminal `Next` step as a one-element stream, so it slots directly into a\n * loop body or after a run of values: `values.pipe(Stream.concat(next(s)))`.\n */\nexport const next = <S>(state: S): Stream.Stream<Step<never, S>> =>\n Stream.succeed(Step.Next({ state }))\n\n/**\n * End the loop. Called with no argument it emits a bare `Stop`; called with\n * a state it emits `StopWith(state)` — `loopOver` threads that final state\n * to the next input and `loopWithState` writes it to its `SubscriptionRef`\n * before the loop ends. Either way it returns a single-element stream, so\n * return it directly from a loop body.\n */\nexport const stop = <S = never>(state?: S): Stream.Stream<Step<never, S>> =>\n Stream.succeed<Step<never, S>>(state === undefined ? Step.Stop() : Step.StopWith({ state }))\n\n// ---------------------------------------------------------------------------\n// onTurnComplete - turn-aware stream operator for loop bodies\n// ---------------------------------------------------------------------------\n\n/**\n * Lift a provider's `Stream<TurnEvent>` into a loop body's `Stream<Step<TurnEvent | A, S>>`.\n * Each delta passes through as `value(delta)` (including the terminal\n * `TurnComplete`, so the consumer sees turn boundaries naturally). Once\n * the terminal arrives, `then(turn)` runs and its returned stream of loop\n * steps (typically tool outputs followed by `next(state)` or `stop()`) is\n * concatenated.\n *\n * Pre-pipe transforms (`Stream.tap` / `Stream.map` / `Stream.filter`) on\n * the raw delta stream cover anything an `emit`-style callback would do.\n *\n * `then` may return the step stream directly, or an `Effect` that produces\n * it (for branches that need to read a `Ref`, decode tool args, log, etc.).\n * Mirrors `loop`'s body: return a bare `stop()` / `next(s)` where no effect\n * is needed, lift to `Effect` only where one is.\n *\n * If the upstream ends without a `TurnComplete`, the resulting stream\n * fails with `AiError.IncompleteTurn`. Catch it via `Stream.catchTag` if\n * you want to recover.\n *\n * Dual: data-first `onTurnComplete(deltas, then)` and data-last\n * `deltas.pipe(onTurnComplete(then))` both work.\n */\ntype TurnContinuation<S, A, E2, R2> =\n | Stream.Stream<Step<A, S>, E2, R2>\n | Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, E2, R2>\n\nexport const onTurnComplete: {\n <S, A, E2 = never, R2 = never>(\n then: (turn: Turn) => TurnContinuation<S, A, E2, R2>,\n ): <E, R>(\n deltas: Stream.Stream<TurnEvent, E, R>,\n ) => Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>\n <S, A, E, R, E2 = never, R2 = never>(\n deltas: Stream.Stream<TurnEvent, E, R>,\n then: (turn: Turn) => TurnContinuation<S, A, E2, R2>,\n ): Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>\n} = Function.dual(\n 2,\n <S, A, E, R, E2, R2>(\n deltas: Stream.Stream<TurnEvent, E, R>,\n then: (turn: Turn) => TurnContinuation<S, A, E2, R2>,\n ): Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const turnRef = yield* Ref.make<Option.Option<Turn>>(Option.none())\n\n const events: Stream.Stream<Step<TurnEvent, S>, E, R> = deltas.pipe(\n Stream.tap((delta) =>\n isTurnComplete(delta) ? Ref.set(turnRef, Option.some(delta.turn)) : Effect.void,\n ),\n Stream.map(value),\n )\n\n const continuation = Stream.unwrap(\n Effect.gen(function* () {\n const opt = yield* Ref.get(turnRef)\n if (Option.isNone(opt)) return yield* new IncompleteTurn({})\n const result = then(opt.value)\n return Effect.isEffect(result) ? yield* result : result\n }),\n )\n\n return Stream.concat(events, continuation)\n }),\n ),\n)\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst isNonEmpty = <A>(array: ReadonlyArray<A>): array is readonly [A, ...Array<A>] =>\n array.length > 0\n\ntype CurrentBody<S, A, E, R> = {\n readonly scope: Scope.Closeable\n readonly pull: Effect.Effect<ReadonlyArray<Step<A, S>>, E | Cause.Done<void>, R>\n}\n\nconst closeBody = <S, A, E, R>(\n current: CurrentBody<S, A, E, R>,\n exit: Exit.Exit<unknown, unknown>,\n) => Scope.close(current.scope, exit)\n\n/**\n * Walk a chunk of `Step<A, S>` until a terminal `Next` or `Stop` is found.\n * Returns the unwrapped values seen so far and (optionally) the terminal\n * step. Anything in the chunk after the terminal is discarded - its\n * producing side effects may have run, but downstream never sees it.\n */\nconst partitionChunk = <A, S>(\n chunk: ReadonlyArray<Step<A, S>>,\n): {\n readonly values: ReadonlyArray<A>\n readonly decision: Option.Option<Step<A, S>>\n} => {\n const [valueSteps, rest] = Arr.span(\n chunk,\n (e): e is Step<A, S> & { _tag: \"Value\" } => e._tag === \"Value\",\n )\n return {\n values: valueSteps.map((e) => e.value),\n decision: Arr.head(rest),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\ntype LoopBody<S, A, E, R> = (\n state: S,\n) => Stream.Stream<Step<A, S>, E, R> | Effect.Effect<Stream.Stream<Step<A, S>, E, R>, E, R>\n\n/**\n * Drive a state-threaded loop body. Each iteration runs `body(state)` to get\n * a `Stream<Step<A, S>>`; values flow downstream, `next(s)` continues with\n * a new state, `stop()` ends the loop. See the file header for the full\n * pull-based execution model.\n *\n * Dual: data-first `loop(initial, body)` and data-last `loop(body)(initial)`\n * (or `pipe(initial, loop(body))`) both work.\n */\nexport const loop: {\n <S, A, E, R>(body: LoopBody<S, A, E, R>): (initial: S) => Stream.Stream<A, E, R>\n <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>): Stream.Stream<A, E, R>\n} = Function.dual(2, <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>): Stream.Stream<A, E, R> =>\n Stream.scoped(\n Stream.fromPull(\n Effect.gen(function* () {\n const outerScope = yield* Effect.scope\n let state = initial\n let current: CurrentBody<S, A, E, R> | undefined\n let done = false\n\n const closeActive = (\n active: CurrentBody<S, A, E, R>,\n exit: Exit.Exit<unknown, unknown>,\n ) => {\n const isActive = current === active\n if (isActive) current = undefined\n // Scope.close is idempotent. Multiple paths can race to close the\n // active body during cancellation/failure, so closing twice is safe.\n return closeBody(active, exit)\n }\n\n yield* Scope.addFinalizerExit(outerScope, (exit) =>\n current === undefined ? Effect.void : closeActive(current, exit),\n )\n\n const pull = Effect.gen(function* () {\n while (true) {\n if (done) return yield* Cause.done()\n\n if (current === undefined) {\n const result = body(state)\n const stream = Effect.isEffect(result) ? Stream.unwrap(result) : result\n const bodyScope = yield* Scope.fork(outerScope)\n const bodyPull = yield* Channel.toPullScoped(\n Stream.toChannel(stream),\n bodyScope,\n ).pipe(Effect.onError((cause) => Scope.close(bodyScope, Exit.failCause(cause))))\n current = { scope: bodyScope, pull: bodyPull }\n }\n\n const active = current\n const chunk = yield* active.pull.pipe(\n Effect.catchIf(Cause.isDone, () =>\n closeActive(active, Exit.void).pipe(\n Effect.as(undefined as ReadonlyArray<Step<A, S>> | undefined),\n ),\n ),\n Effect.onError((cause) => closeActive(active, Exit.failCause(cause))),\n )\n\n if (chunk === undefined) {\n done = true\n return yield* Cause.done()\n }\n\n const { values, decision } = partitionChunk(chunk)\n\n if (Option.isSome(decision)) {\n yield* closeActive(active, Exit.void)\n if (decision.value._tag === \"Stop\" || decision.value._tag === \"StopWith\") {\n // `loop` has no next iteration to apply StopWith's state to;\n // the state lands in `loopOver`'s outer ref or\n // `loopWithState`'s SubscriptionRef via their taps.\n done = true\n } else if (decision.value._tag === \"Next\") {\n state = decision.value.state\n }\n }\n\n // Emit the values seen so far if any. Chunks from a Stream pull\n // are non-empty, so when `decision` is `None` every step was\n // a `Value` and `values` is non-empty here. With a decision and\n // no preceding values, fall through to the next iteration.\n if (isNonEmpty(values)) return values\n }\n })\n\n // `Stream.fromPull` expects the generator to RETURN the pull effect,\n // not run it; `return yield* pull` would execute a pull and break it.\n // @effect-diagnostics-next-line effect/returnEffectInGen:off\n return pull\n }),\n ),\n ),\n)\n\n// ---------------------------------------------------------------------------\n// loopOver - stream-driven sibling of loop. One input item runs a full\n// multi-turn inner loop.\n// ---------------------------------------------------------------------------\n\ntype LoopOverBody<S, I, A, E, R> = (\n state: S,\n input: I,\n) => Stream.Stream<Step<A, S>, E, R> | Effect.Effect<Stream.Stream<Step<A, S>, E, R>, E, R>\n\n/**\n * Input-driven sibling of `loop`. For each item pulled from the input\n * stream, runs an inner seed-driven `loop` whose body is\n * `(s) => body(s, item)`. State is threaded across input items.\n *\n * **Per-input semantics — the body emits standard `Step<A, S>`:**\n * - `value(a)`: emit `a` downstream\n * - `next(s)`: re-run the body with the SAME input and new state `s`\n * (multi-turn within one input — e.g. multiple model turns + tool\n * calls for one document)\n * - `stop()`: end this input's inner loop, advance to the next input\n * (state preserved)\n * - body stream ending without a decision: same as `stop()` (advance)\n *\n * **Outer termination:** the input stream ending. To halt programmatically\n * from within, end the input stream upstream (`Stream.takeWhile`, a\n * `SubscriptionRef` gate, etc.). Reserving `stop()` for per-item\n * advancement is what makes the common \"stream of documents, multi-turn\n * conversation per document\" shape readable.\n *\n * Dual: data-first `loopOver(input, initial, body)` and data-last\n * `input.pipe(loopOver(initial, body))` both work.\n */\nexport const loopOver: {\n <S, I, A, E, R>(\n initial: S,\n body: LoopOverBody<S, I, A, E, R>,\n ): <EI, RI>(input: Stream.Stream<I, EI, RI>) => Stream.Stream<A, E | EI, R | RI>\n <S, I, A, E, R, EI, RI>(\n input: Stream.Stream<I, EI, RI>,\n initial: S,\n body: LoopOverBody<S, I, A, E, R>,\n ): Stream.Stream<A, E | EI, R | RI>\n} = Function.dual(\n 3,\n <S, I, A, E, R, EI, RI>(\n input: Stream.Stream<I, EI, RI>,\n initial: S,\n body: LoopOverBody<S, I, A, E, R>,\n ): Stream.Stream<A, E | EI, R | RI> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const stateRef = yield* Ref.make<S>(initial)\n return input.pipe(\n Stream.flatMap((item) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const state = yield* Ref.get(stateRef)\n // Capture Next states (and stop(state)'s final state) into the\n // outer ref so the LAST state seen in this input's inner\n // loop is what the next input starts from.\n const wrappedBody = (s: S) => {\n const result = body(s, item)\n const stream = Effect.isEffect(result) ? Stream.unwrap(result) : result\n return stream.pipe(\n Stream.tap((step) =>\n Match.value(step).pipe(\n Match.tags({\n Next: (e) => Ref.set(stateRef, e.state),\n StopWith: (e) => Ref.set(stateRef, e.state),\n }),\n Match.orElse(() => Effect.void),\n ),\n ),\n )\n }\n return loop(state, wrappedBody)\n }),\n ),\n ),\n )\n }),\n ),\n)\n\n// ---------------------------------------------------------------------------\n// loopWithState - same body protocol, plus a live state observable.\n// ---------------------------------------------------------------------------\n\n/**\n * Like `loop`, but exposes the current loop state as a `SubscriptionRef`\n * alongside the value stream.\n *\n * Allocates one `SubscriptionRef<S>` seeded with `initial`, then runs the\n * loop with a wrapped body that taps every `Next(s)` step into the ref\n * before forwarding it. The caller decides how to consume both channels:\n *\n * - **Final state**: drain the stream, then `SubscriptionRef.get(state)`\n * - the ref holds the state from the last `Next` (or `initial` if the\n * loop ended without advancing).\n * - **Live transitions**: `SubscriptionRef.changes(state)` is a\n * `Stream<S>` of every state observed; subscribe alongside the value\n * stream.\n * - **Mid-iteration peek**: `SubscriptionRef.get(state)` at any time.\n *\n * The returned stream and ref are independent of each other - the ref\n * lives outside the stream's scope, so reading it after the stream\n * completes is safe.\n */\nexport const loopWithState = <S, A, E, R>(\n initial: S,\n body: LoopBody<S, A, E, R>,\n): Effect.Effect<{\n readonly stream: Stream.Stream<A, E, R>\n readonly state: SubscriptionRef.SubscriptionRef<S>\n}> =>\n Effect.gen(function* () {\n const stateRef = yield* SubscriptionRef.make(initial)\n\n const tap = (stream: Stream.Stream<Step<A, S>, E, R>): Stream.Stream<Step<A, S>, E, R> =>\n stream.pipe(\n Stream.tap((step) =>\n Match.value(step).pipe(\n Match.tags({\n Next: (e) => SubscriptionRef.set(stateRef, e.state),\n StopWith: (e) => SubscriptionRef.set(stateRef, e.state),\n }),\n Match.orElse(() => Effect.void),\n ),\n ),\n )\n\n const wrappedBody: LoopBody<S, A, E, R> = (s) => {\n const result = body(s)\n return Effect.isEffect(result) ? Effect.map(result, tap) : tap(result)\n }\n\n return {\n stream: loop(initial, wrappedBody),\n state: stateRef,\n }\n })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,MAAM,OAAO,KAAK,WAAoB;;;;;;;;;AAUtC,MAAa,EAAE,OAAO,MAAM,MAAM,aAAa;;AAG/C,MAAa,SAAY,MAAyB,KAAK,MAAM,EAAE,OAAO,EAAE,CAAC;;;;;;AAOzE,MAAa,QAAW,UACtB,OAAO,QAAQ,KAAK,KAAK,EAAE,MAAM,CAAC,CAAC;;;;;;;;AASrC,MAAa,QAAmB,UAC9B,OAAO,QAAwB,UAAU,KAAA,IAAY,KAAK,KAAK,IAAI,KAAK,SAAS,EAAE,MAAM,CAAC,CAAC;AAiC7F,MAAa,iBAUT,SAAS,KACX,IAEE,QACA,SAEA,OAAO,OACL,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,IAAI,KAA0B,OAAO,KAAK,CAAC;CAElE,MAAM,SAAkD,OAAO,KAC7D,OAAO,KAAK,UACV,eAAe,KAAK,IAAI,IAAI,IAAI,SAAS,OAAO,KAAK,MAAM,IAAI,CAAC,IAAI,OAAO,IAC7E,GACA,OAAO,IAAI,KAAK,CAClB;CAEA,MAAM,eAAe,OAAO,OAC1B,OAAO,IAAI,aAAa;EACtB,MAAM,MAAM,OAAO,IAAI,IAAI,OAAO;EAClC,IAAI,OAAO,OAAO,GAAG,GAAG,OAAO,OAAO,IAAI,eAAe,CAAC,CAAC;EAC3D,MAAM,SAAS,KAAK,IAAI,KAAK;EAC7B,OAAO,OAAO,SAAS,MAAM,IAAI,OAAO,SAAS;CACnD,CAAC,CACH;CAEA,OAAO,OAAO,OAAO,QAAQ,YAAY;AAC3C,CAAC,CACH,CACJ;AAMA,MAAM,cAAiB,UACrB,MAAM,SAAS;AAOjB,MAAM,aACJ,SACA,SACG,MAAM,MAAM,QAAQ,OAAO,IAAI;;;;;;;AAQpC,MAAM,kBACJ,UAIG;CACH,MAAM,CAAC,YAAY,QAAQA,MAAI,KAC7B,QACC,MAA2C,EAAE,SAAS,OACzD;CACA,OAAO;EACL,QAAQ,WAAW,KAAK,MAAM,EAAE,KAAK;EACrC,UAAUA,MAAI,KAAK,IAAI;CACzB;AACF;;;;;;;;;;AAmBA,MAAa,OAGT,SAAS,KAAK,IAAgB,SAAY,SAC5C,OAAO,OACL,OAAO,SACL,OAAO,IAAI,aAAa;CACtB,MAAM,aAAa,OAAO,OAAO;CACjC,IAAI,QAAQ;CACZ,IAAI;CACJ,IAAI,OAAO;CAEX,MAAM,eACJ,QACA,SACG;EAEH,IADiB,YAAY,QACf,UAAU,KAAA;EAGxB,OAAO,UAAU,QAAQ,IAAI;CAC/B;CAEA,OAAO,MAAM,iBAAiB,aAAa,SACzC,YAAY,KAAA,IAAY,OAAO,OAAO,YAAY,SAAS,IAAI,CACjE;CAyDA,OAvDa,OAAO,IAAI,aAAa;EACnC,OAAO,MAAM;GACX,IAAI,MAAM,OAAO,OAAO,MAAM,KAAK;GAEnC,IAAI,YAAY,KAAA,GAAW;IACzB,MAAM,SAAS,KAAK,KAAK;IACzB,MAAM,SAAS,OAAO,SAAS,MAAM,IAAI,OAAO,OAAO,MAAM,IAAI;IACjE,MAAM,YAAY,OAAO,MAAM,KAAK,UAAU;IAK9C,UAAU;KAAE,OAAO;KAAW,MAAM,OAJZ,QAAQ,aAC9B,OAAO,UAAU,MAAM,GACvB,SACF,CAAC,CAAC,KAAK,OAAO,SAAS,UAAU,MAAM,MAAM,WAAW,KAAK,UAAU,KAAK,CAAC,CAAC,CAAC;IAClC;GAC/C;GAEA,MAAM,SAAS;GACf,MAAM,QAAQ,OAAO,OAAO,KAAK,KAC/B,OAAO,QAAQ,MAAM,cACnB,YAAY,QAAQ,KAAK,IAAI,CAAC,CAAC,KAC7B,OAAO,GAAG,KAAA,CAAkD,CAC9D,CACF,GACA,OAAO,SAAS,UAAU,YAAY,QAAQ,KAAK,UAAU,KAAK,CAAC,CAAC,CACtE;GAEA,IAAI,UAAU,KAAA,GAAW;IACvB,OAAO;IACP,OAAO,OAAO,MAAM,KAAK;GAC3B;GAEA,MAAM,EAAE,QAAQ,aAAa,eAAe,KAAK;GAEjD,IAAI,OAAO,OAAO,QAAQ,GAAG;IAC3B,OAAO,YAAY,QAAQ,KAAK,IAAI;IACpC,IAAI,SAAS,MAAM,SAAS,UAAU,SAAS,MAAM,SAAS,YAI5D,OAAO;SACF,IAAI,SAAS,MAAM,SAAS,QACjC,QAAQ,SAAS,MAAM;GAE3B;GAMA,IAAI,WAAW,MAAM,GAAG,OAAO;EACjC;CACF,CAKU;AACZ,CAAC,CACH,CACF,CACF;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,MAAa,WAUT,SAAS,KACX,IAEE,OACA,SACA,SAEA,OAAO,OACL,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,OAAO,IAAI,KAAQ,OAAO;CAC3C,OAAO,MAAM,KACX,OAAO,SAAS,SACd,OAAO,OACL,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,IAAI,IAAI,QAAQ;EAIrC,MAAM,eAAe,MAAS;GAC5B,MAAM,SAAS,KAAK,GAAG,IAAI;GAE3B,QADe,OAAO,SAAS,MAAM,IAAI,OAAO,OAAO,MAAM,IAAI,OAAA,CACnD,KACZ,OAAO,KAAK,SACV,MAAM,MAAM,IAAI,CAAC,CAAC,KAChB,MAAM,KAAK;IACT,OAAO,MAAM,IAAI,IAAI,UAAU,EAAE,KAAK;IACtC,WAAW,MAAM,IAAI,IAAI,UAAU,EAAE,KAAK;GAC5C,CAAC,GACD,MAAM,aAAa,OAAO,IAAI,CAChC,CACF,CACF;EACF;EACA,OAAO,KAAK,OAAO,WAAW;CAChC,CAAC,CACH,CACF,CACF;AACF,CAAC,CACH,CACJ;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,iBACX,SACA,SAKA,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,OAAO,gBAAgB,KAAK,OAAO;CAEpD,MAAM,OAAO,WACX,OAAO,KACL,OAAO,KAAK,SACV,MAAM,MAAM,IAAI,CAAC,CAAC,KAChB,MAAM,KAAK;EACT,OAAO,MAAM,gBAAgB,IAAI,UAAU,EAAE,KAAK;EAClD,WAAW,MAAM,gBAAgB,IAAI,UAAU,EAAE,KAAK;CACxD,CAAC,GACD,MAAM,aAAa,OAAO,IAAI,CAChC,CACF,CACF;CAEF,MAAM,eAAqC,MAAM;EAC/C,MAAM,SAAS,KAAK,CAAC;EACrB,OAAO,OAAO,SAAS,MAAM,IAAI,OAAO,IAAI,QAAQ,GAAG,IAAI,IAAI,MAAM;CACvE;CAEA,OAAO;EACL,QAAQ,KAAK,SAAS,WAAW;EACjC,OAAO;CACT;AACF,CAAC"}
1
+ {"version":3,"file":"Loop.mjs","names":["Arr"],"sources":["../../src/loop/Loop.ts"],"sourcesContent":["/**\n * Pull-based `loop` for state-threaded sub-streams.\n *\n * Each iteration runs a body that returns a `Stream<Step<A, S>>`. The body\n * emits values via `Loop.value(a)` and signals iteration control via\n * `Loop.next(state)` (continue with new state) or `Loop.stop()` (terminate).\n * The loop unwraps `Value` steps back to `A` for downstream consumers, so\n * the resulting stream is a plain `Stream<A>`.\n *\n * The next body stream is only pulled when downstream pulls the outer\n * stream - no producer fiber, no queue buffering. Cancellation, failures,\n * scoped resources, and backpressure stay aligned with normal Stream\n * semantics.\n *\n * Convention: a `Next` or `Stop` step is the terminal element of a body's\n * iteration. Values emitted in the same chunk after one are discarded\n * (their producing side effects may already have run). The `Loop.next` /\n * `Loop.stop` helpers each emit a single terminal step, so concatenate\n * your values before them (`values.pipe(Stream.concat(Loop.next(state)))`).\n */\nimport {\n Array as Arr,\n Cause,\n Channel,\n Data,\n Effect,\n Exit,\n Function,\n Match,\n Option,\n Ref,\n Scope,\n Stream,\n SubscriptionRef,\n} from \"effect\"\nimport { IncompleteTurn } from \"../domain/AiError.js\"\nimport { isTurnComplete, type Turn, type TurnEvent } from \"../domain/Turn.js\"\n\n// ---------------------------------------------------------------------------\n// Step type - the body's emit shape\n// ---------------------------------------------------------------------------\n\n/**\n * The tagged union a body emits per pull. `Value` carries a payload that\n * flows downstream. `Next` ends the current iteration and continues with a\n * new state. `Stop` ends the loop entirely with no carried state.\n * `StopWith` also ends the loop but carries a final state that `loopOver`\n * will thread to the next input and `loopWithState` will write to its\n * `SubscriptionRef` before the loop ends. Plain `loop` has no next\n * iteration to apply it to and treats `StopWith` like `Stop`.\n *\n * `Stop` is intentionally `{}` so the bare `stop()` helper doesn't\n * constrain `S` from a body's stream type — every body has a `Stop`\n * variant in its union, and forcing `S` to flow through it would break\n * inference whenever the body never uses `next` / `stop(state)`.\n */\nexport type Step<A, S> = Data.TaggedEnum<{\n Value: { readonly value: A }\n Next: { readonly state: S }\n Stop: {}\n StopWith: { readonly state: S }\n}>\n\ninterface StepDef extends Data.TaggedEnum.WithGenerics<2> {\n readonly taggedEnum: Step<this[\"A\"], this[\"B\"]>\n}\n\nconst Step = Data.taggedEnum<StepDef>()\n\n/**\n * Bare step constructors, so a loop body can emit `Value(...)`, `Next(...)`,\n * `Stop()`, `StopWith(...)` directly inside a `Stream.make(...)` without the\n * `Step.` prefix. (The `Step` object itself can't be exported under that name\n * because it collides with the exported `Step` type.) For the common cases\n * prefer the `value` / `next` / `stop` helpers, which also handle the\n * single-element-stream wrapping for `next` / `stop`.\n */\nexport const { Value, Next, Stop, StopWith } = Step\n\n/** Wrap a value so it flows through the loop to downstream consumers. */\nexport const value = <A>(a: A): Step<A, never> => Step.Value({ value: a })\n\n/**\n * End the current iteration and continue with a new state. Emits a single\n * terminal `Next` step as a one-element stream, so it slots directly into a\n * loop body or after a run of values: `values.pipe(Stream.concat(next(s)))`.\n */\nexport const next = <S>(state: S): Stream.Stream<Step<never, S>> =>\n Stream.succeed(Step.Next({ state }))\n\n/**\n * End the loop. Called with no argument it emits a bare `Stop`; called with\n * a state it emits `StopWith(state)` — `loopOver` threads that final state\n * to the next input and `loopWithState` writes it to its `SubscriptionRef`\n * before the loop ends. Either way it returns a single-element stream, so\n * return it directly from a loop body.\n */\nexport const stop = <S = never>(state?: S): Stream.Stream<Step<never, S>> =>\n Stream.succeed<Step<never, S>>(state === undefined ? Step.Stop() : Step.StopWith({ state }))\n\n// ---------------------------------------------------------------------------\n// onTurnComplete - turn-aware stream operator for loop bodies\n// ---------------------------------------------------------------------------\n\n/**\n * Lift a provider's `Stream<TurnEvent>` into a loop body's `Stream<Step<TurnEvent | A, S>>`.\n * Each delta passes through as `value(delta)` (including the terminal\n * `TurnComplete`, so the consumer sees turn boundaries naturally). Once\n * the terminal arrives, `then(turn)` runs and its returned stream of loop\n * steps (typically tool outputs followed by `next(state)` or `stop()`) is\n * concatenated.\n *\n * Pre-pipe transforms (`Stream.tap` / `Stream.map` / `Stream.filter`) on\n * the raw delta stream cover anything an `emit`-style callback would do.\n *\n * `then` may return the step stream directly, or an `Effect` that produces\n * it (for branches that need to read a `Ref`, decode tool args, log, etc.).\n * Mirrors `loop`'s body: return a bare `stop()` / `next(s)` where no effect\n * is needed, lift to `Effect` only where one is.\n *\n * If the upstream ends without a `TurnComplete`, the resulting stream\n * fails with `AiError.IncompleteTurn`. Catch it via `Stream.catchTag` if\n * you want to recover.\n *\n * Dual: data-first `onTurnComplete(deltas, then)` and data-last\n * `deltas.pipe(onTurnComplete(then))` both work.\n */\ntype TurnContinuation<S, A, E2, R2> =\n | Stream.Stream<Step<A, S>, E2, R2>\n | Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, E2, R2>\n\nexport const onTurnComplete: {\n <S, A, E2 = never, R2 = never>(\n then: (turn: Turn) => TurnContinuation<S, A, E2, R2>,\n ): <E, R>(\n deltas: Stream.Stream<TurnEvent, E, R>,\n ) => Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>\n <S, A, E, R, E2 = never, R2 = never>(\n deltas: Stream.Stream<TurnEvent, E, R>,\n then: (turn: Turn) => TurnContinuation<S, A, E2, R2>,\n ): Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>\n} = Function.dual(\n 2,\n <S, A, E, R, E2, R2>(\n deltas: Stream.Stream<TurnEvent, E, R>,\n then: (turn: Turn) => TurnContinuation<S, A, E2, R2>,\n ): Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const turnRef = yield* Ref.make<Option.Option<Turn>>(Option.none())\n\n const events: Stream.Stream<Step<TurnEvent, S>, E, R> = deltas.pipe(\n Stream.tap((delta) =>\n isTurnComplete(delta) ? Ref.set(turnRef, Option.some(delta.turn)) : Effect.void,\n ),\n Stream.map(value),\n )\n\n const continuation = Stream.unwrap(\n Effect.gen(function* () {\n const opt = yield* Ref.get(turnRef)\n if (Option.isNone(opt)) return yield* new IncompleteTurn({})\n const result = then(opt.value)\n return Effect.isEffect(result) ? yield* result : result\n }),\n )\n\n return Stream.concat(events, continuation)\n }),\n ),\n)\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst isNonEmpty = <A>(array: ReadonlyArray<A>): array is readonly [A, ...Array<A>] =>\n array.length > 0\n\ntype CurrentBody<S, A, E, R> = {\n readonly scope: Scope.Closeable\n readonly pull: Effect.Effect<ReadonlyArray<Step<A, S>>, E | Cause.Done<void>, R>\n}\n\nconst closeBody = <S, A, E, R>(\n current: CurrentBody<S, A, E, R>,\n exit: Exit.Exit<unknown, unknown>,\n) => Scope.close(current.scope, exit)\n\n/**\n * Walk a chunk of `Step<A, S>` until a terminal `Next` or `Stop` is found.\n * Returns the unwrapped values seen so far and (optionally) the terminal\n * step. Anything in the chunk after the terminal is discarded - its\n * producing side effects may have run, but downstream never sees it.\n */\nconst partitionChunk = <A, S>(\n chunk: ReadonlyArray<Step<A, S>>,\n): {\n readonly values: ReadonlyArray<A>\n readonly decision: Option.Option<Step<A, S>>\n} => {\n const [valueSteps, rest] = Arr.span(\n chunk,\n (e): e is Step<A, S> & { _tag: \"Value\" } => e._tag === \"Value\",\n )\n return {\n values: valueSteps.map((e) => e.value),\n decision: Arr.head(rest),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\ntype LoopBody<S, A, E, R> = (\n state: S,\n) => Stream.Stream<Step<A, S>, E, R> | Effect.Effect<Stream.Stream<Step<A, S>, E, R>, E, R>\n\n/**\n * Drive a state-threaded loop body. Each iteration runs `body(state)` to get\n * a `Stream<Step<A, S>>`; values flow downstream, `next(s)` continues with\n * a new state, `stop()` ends the loop. See the file header for the full\n * pull-based execution model.\n *\n * Dual: data-first `loop(initial, body)` and data-last `loop(body)(initial)`\n * (or `pipe(initial, loop(body))`) both work.\n */\nexport const loop: {\n <S, A, E, R>(body: LoopBody<S, A, E, R>): (initial: S) => Stream.Stream<A, E, R>\n <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>): Stream.Stream<A, E, R>\n} = Function.dual(2, <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>): Stream.Stream<A, E, R> =>\n Stream.scoped(\n Stream.fromPull(\n Effect.gen(function* () {\n const outerScope = yield* Effect.scope\n let state = initial\n let current: CurrentBody<S, A, E, R> | undefined\n let done = false\n\n const closeActive = (\n active: CurrentBody<S, A, E, R>,\n exit: Exit.Exit<unknown, unknown>,\n ) => {\n const isActive = current === active\n if (isActive) current = undefined\n // Scope.close is idempotent. Multiple paths can race to close the\n // active body during cancellation/failure, so closing twice is safe.\n return closeBody(active, exit)\n }\n\n yield* Scope.addFinalizerExit(outerScope, (exit) =>\n current === undefined ? Effect.void : closeActive(current, exit),\n )\n\n const pull = Effect.gen(function* () {\n while (true) {\n if (done) return yield* Cause.done()\n\n if (current === undefined) {\n const result = body(state)\n const stream = Effect.isEffect(result) ? Stream.unwrap(result) : result\n const bodyScope = yield* Scope.fork(outerScope)\n const bodyPull = yield* Channel.toPullScoped(\n Stream.toChannel(stream),\n bodyScope,\n ).pipe(Effect.onError((cause) => Scope.close(bodyScope, Exit.failCause(cause))))\n current = { scope: bodyScope, pull: bodyPull }\n }\n\n const active = current\n const chunk = yield* active.pull.pipe(\n Effect.catchIf(Cause.isDone, () =>\n closeActive(active, Exit.void).pipe(\n Effect.as(undefined as ReadonlyArray<Step<A, S>> | undefined),\n ),\n ),\n Effect.onError((cause) => closeActive(active, Exit.failCause(cause))),\n )\n\n if (chunk === undefined) {\n done = true\n return yield* Cause.done()\n }\n\n const { values, decision } = partitionChunk(chunk)\n\n if (Option.isSome(decision)) {\n yield* closeActive(active, Exit.void)\n if (decision.value._tag === \"Stop\" || decision.value._tag === \"StopWith\") {\n // `loop` has no next iteration to apply StopWith's state to;\n // the state lands in `loopOver`'s outer ref or\n // `loopWithState`'s SubscriptionRef via their taps.\n done = true\n } else if (decision.value._tag === \"Next\") {\n state = decision.value.state\n }\n }\n\n // Emit the values seen so far if any. Chunks from a Stream pull\n // are non-empty, so when `decision` is `None` every step was\n // a `Value` and `values` is non-empty here. With a decision and\n // no preceding values, fall through to the next iteration.\n if (isNonEmpty(values)) return values\n }\n })\n\n // `Stream.fromPull` expects the generator to RETURN the pull effect,\n // not run it; `return yield* pull` would execute a pull and break it.\n // @effect-diagnostics-next-line returnEffectInGen:off\n return pull\n }),\n ),\n ),\n)\n\n// ---------------------------------------------------------------------------\n// loopOver - stream-driven sibling of loop. One input item runs a full\n// multi-turn inner loop.\n// ---------------------------------------------------------------------------\n\ntype LoopOverBody<S, I, A, E, R> = (\n state: S,\n input: I,\n) => Stream.Stream<Step<A, S>, E, R> | Effect.Effect<Stream.Stream<Step<A, S>, E, R>, E, R>\n\n/**\n * Input-driven sibling of `loop`. For each item pulled from the input\n * stream, runs an inner seed-driven `loop` whose body is\n * `(s) => body(s, item)`. State is threaded across input items.\n *\n * **Per-input semantics — the body emits standard `Step<A, S>`:**\n * - `value(a)`: emit `a` downstream\n * - `next(s)`: re-run the body with the SAME input and new state `s`\n * (multi-turn within one input — e.g. multiple model turns + tool\n * calls for one document)\n * - `stop()`: end this input's inner loop, advance to the next input\n * (state preserved)\n * - body stream ending without a decision: same as `stop()` (advance)\n *\n * **Outer termination:** the input stream ending. To halt programmatically\n * from within, end the input stream upstream (`Stream.takeWhile`, a\n * `SubscriptionRef` gate, etc.). Reserving `stop()` for per-item\n * advancement is what makes the common \"stream of documents, multi-turn\n * conversation per document\" shape readable.\n *\n * Dual: data-first `loopOver(input, initial, body)` and data-last\n * `input.pipe(loopOver(initial, body))` both work.\n */\nexport const loopOver: {\n <S, I, A, E, R>(\n initial: S,\n body: LoopOverBody<S, I, A, E, R>,\n ): <EI, RI>(input: Stream.Stream<I, EI, RI>) => Stream.Stream<A, E | EI, R | RI>\n <S, I, A, E, R, EI, RI>(\n input: Stream.Stream<I, EI, RI>,\n initial: S,\n body: LoopOverBody<S, I, A, E, R>,\n ): Stream.Stream<A, E | EI, R | RI>\n} = Function.dual(\n 3,\n <S, I, A, E, R, EI, RI>(\n input: Stream.Stream<I, EI, RI>,\n initial: S,\n body: LoopOverBody<S, I, A, E, R>,\n ): Stream.Stream<A, E | EI, R | RI> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const stateRef = yield* Ref.make<S>(initial)\n return input.pipe(\n Stream.flatMap((item) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const state = yield* Ref.get(stateRef)\n // Capture Next states (and stop(state)'s final state) into the\n // outer ref so the LAST state seen in this input's inner\n // loop is what the next input starts from.\n const wrappedBody = (s: S) => {\n const result = body(s, item)\n const stream = Effect.isEffect(result) ? Stream.unwrap(result) : result\n return stream.pipe(\n Stream.tap((step) =>\n Match.value(step).pipe(\n Match.tags({\n Next: (e) => Ref.set(stateRef, e.state),\n StopWith: (e) => Ref.set(stateRef, e.state),\n }),\n Match.orElse(() => Effect.void),\n ),\n ),\n )\n }\n return loop(state, wrappedBody)\n }),\n ),\n ),\n )\n }),\n ),\n)\n\n// ---------------------------------------------------------------------------\n// loopWithState - same body protocol, plus a live state observable.\n// ---------------------------------------------------------------------------\n\n/**\n * Like `loop`, but exposes the current loop state as a `SubscriptionRef`\n * alongside the value stream.\n *\n * Allocates one `SubscriptionRef<S>` seeded with `initial`, then runs the\n * loop with a wrapped body that taps every `Next(s)` step into the ref\n * before forwarding it. The caller decides how to consume both channels:\n *\n * - **Final state**: drain the stream, then `SubscriptionRef.get(state)`\n * - the ref holds the state from the last `Next` (or `initial` if the\n * loop ended without advancing).\n * - **Live transitions**: `SubscriptionRef.changes(state)` is a\n * `Stream<S>` of every state observed; subscribe alongside the value\n * stream.\n * - **Mid-iteration peek**: `SubscriptionRef.get(state)` at any time.\n *\n * The returned stream and ref are independent of each other - the ref\n * lives outside the stream's scope, so reading it after the stream\n * completes is safe.\n */\nexport const loopWithState = <S, A, E, R>(\n initial: S,\n body: LoopBody<S, A, E, R>,\n): Effect.Effect<{\n readonly stream: Stream.Stream<A, E, R>\n readonly state: SubscriptionRef.SubscriptionRef<S>\n}> =>\n Effect.gen(function* () {\n const stateRef = yield* SubscriptionRef.make(initial)\n\n const tap = (stream: Stream.Stream<Step<A, S>, E, R>): Stream.Stream<Step<A, S>, E, R> =>\n stream.pipe(\n Stream.tap((step) =>\n Match.value(step).pipe(\n Match.tags({\n Next: (e) => SubscriptionRef.set(stateRef, e.state),\n StopWith: (e) => SubscriptionRef.set(stateRef, e.state),\n }),\n Match.orElse(() => Effect.void),\n ),\n ),\n )\n\n const wrappedBody: LoopBody<S, A, E, R> = (s) => {\n const result = body(s)\n return Effect.isEffect(result) ? Effect.map(result, tap) : tap(result)\n }\n\n return {\n stream: loop(initial, wrappedBody),\n state: stateRef,\n }\n })\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import { RateLimited } from "../domain/AiError.mjs";
2
2
  import { TurnEvent } from "../domain/Turn.mjs";
3
3
  import { loop, loopOver, loopWithState, next, onTurnComplete, stop, value } from "./Loop.mjs";
4
- import { m as it, o as describe } from "../utils-CiH_DjFW.mjs";
5
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
6
- import { t as import_dist } from "../dist-BaenoU1A.mjs";
4
+ import { b as it, m as describe } from "../dist-DzeSyeYF.mjs";
5
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
6
+ import { t as import_dist } from "../dist-DNeuh84d.mjs";
7
7
  import { Deferred, Effect, Fiber, Latch, Ref, Stream, SubscriptionRef, pipe } from "effect";
8
8
  //#region src/loop/Loop.test.ts
9
9
  describe("Loop.loop", () => {
@@ -1,6 +1,6 @@
1
1
  import { confidence, margin } from "./Probability.mjs";
2
- import { m as it, o as describe } from "../utils-CiH_DjFW.mjs";
3
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
2
+ import { b as it, m as describe } from "../dist-DzeSyeYF.mjs";
3
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
4
4
  //#region src/math/Probability.test.ts
5
5
  describe("confidence", () => {
6
6
  it("is 1 when all mass sits on one outcome", () => {
@@ -1,9 +1,9 @@
1
1
  import { ChannelId, MessageId, inConversation } from "./Messenger.mjs";
2
2
  import { splitForLimit } from "./MessengerAdapter.mjs";
3
3
  import { MessengerRateLimited } from "./MessengerError.mjs";
4
- import { o as describe } from "../utils-CiH_DjFW.mjs";
5
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
6
- import { t as it } from "../dist-nohcnkD6.mjs";
4
+ import { m as describe } from "../dist-DzeSyeYF.mjs";
5
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
6
+ import { t as it } from "../dist-C8RmtoR8.mjs";
7
7
  import { make } from "../testing/MockMessenger.mjs";
8
8
  import { Array, Duration, Effect, Option, Result, Stream } from "effect";
9
9
  import { TestClock } from "effect/testing";
@@ -1,8 +1,8 @@
1
1
  import { audioEvent, controlInput, promptsInput, singleVariant } from "../domain/Music.mjs";
2
2
  import { generate, streamGeneration, streamGenerationFrom } from "./MusicGenerator.mjs";
3
- import { m as it, o as describe } from "../utils-CiH_DjFW.mjs";
4
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
5
- import { t as import_dist } from "../dist-BaenoU1A.mjs";
3
+ import { b as it, m as describe } from "../dist-DzeSyeYF.mjs";
4
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
5
+ import { t as import_dist } from "../dist-DNeuh84d.mjs";
6
6
  import { layer, layerWithoutInteractive } from "../testing/MockMusicGenerator.mjs";
7
7
  import { Duration, Effect, Stream } from "effect";
8
8
  //#region src/music-generator/MusicGenerator.test.ts
@@ -1,8 +1,8 @@
1
1
  import { TurnEvent } from "../domain/Turn.mjs";
2
2
  import { computeThroughputTick, isMetricEvent, makeEvent, metricEvents, throughput, timeToCompletion, timeToFirstToken, tokenTotals } from "./Metrics.mjs";
3
- import { o as describe } from "../utils-CiH_DjFW.mjs";
4
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
5
- import { t as it } from "../dist-nohcnkD6.mjs";
3
+ import { m as describe } from "../dist-DzeSyeYF.mjs";
4
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
5
+ import { t as it } from "../dist-C8RmtoR8.mjs";
6
6
  import { Array, Duration, Effect, Fiber, Option, Stream } from "effect";
7
7
  import * as TestClock from "effect/testing/TestClock";
8
8
  //#region src/observability/Metrics.test.ts
@@ -1,5 +1,5 @@
1
1
  import { Duration, Layer, Stream } from "effect";
2
- import { HttpClient } from "effect/unstable/http";
2
+ import { HttpClient } from "effect/http";
3
3
  declare namespace Telemetry_d_exports {
4
4
  export { LayerOtlpOptions, RecordOptions, layerOtlp, record };
5
5
  }
@@ -1,7 +1,7 @@
1
1
  import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { isMetricEvent } from "./Metrics.mjs";
3
3
  import { Effect, Layer, Match, Metric, Stream } from "effect";
4
- import { OtlpMetrics, OtlpSerialization } from "effect/unstable/observability";
4
+ import { OtlpMetrics, OtlpSerialization } from "effect/observability";
5
5
  //#region src/observability/Telemetry.ts
6
6
  var Telemetry_exports = /* @__PURE__ */ __exportAll({
7
7
  layerOtlp: () => layerOtlp,
@@ -1 +1 @@
1
- {"version":3,"file":"Telemetry.mjs","names":[],"sources":["../../src/observability/Telemetry.ts"],"sourcesContent":["import { Duration, Effect, Layer, Match, Metric, Stream } from \"effect\"\nimport type { HttpClient } from \"effect/unstable/http\"\nimport { OtlpMetrics, OtlpSerialization } from \"effect/unstable/observability\"\nimport { isMetricEvent, type Measurement, type MetricEvent } from \"./Metrics.js\"\n\n// ---------------------------------------------------------------------------\n// The agnostic recorder\n// ---------------------------------------------------------------------------\n\n/**\n * Histogram bucket boundaries used for every histogram measurement (the only\n * built-in one is throughput). Custom histogram metrics share these; a metric\n * that needs bespoke boundaries can update its own instrument directly instead\n * of routing through `record`.\n */\nconst histogramBoundaries = Metric.exponentialBoundaries({ start: 1, factor: 2, count: 16 })\n\n/**\n * Build the effect `Metric` instrument a measurement maps to. Dispatches on\n * `kind`, never on the event `_tag`, so a new built-in or custom metric needs\n * no change here. Instruments dedupe by name in the global registry, so a fresh\n * instance per call accumulates onto the same series (no cache needed).\n */\nconst buildInstrument = (m: Measurement): Metric.Metric<any, any> =>\n Match.value(m.kind).pipe(\n Match.when(\"counter\", () => Metric.counter(m.name, { incremental: true })),\n Match.when(\"histogram\", () => Metric.histogram(m.name, { boundaries: histogramBoundaries })),\n Match.when(\"timer\", () => Metric.timer(m.name)),\n Match.when(\"gauge\", () => Metric.gauge(m.name)),\n Match.exhaustive,\n )\n\nconst mergeAttributes = (\n base: Readonly<Record<string, string>> | undefined,\n extra: Readonly<Record<string, string>> | undefined,\n): Readonly<Record<string, string>> | undefined =>\n base === undefined ? extra : extra === undefined ? base : { ...base, ...extra }\n\nconst recordEvent = (\n ev: MetricEvent,\n baseAttributes: Readonly<Record<string, string>> | undefined,\n): Effect.Effect<void> =>\n Effect.forEach(\n ev.measurements,\n (m) => {\n const attributes = mergeAttributes(baseAttributes, m.attributes)\n const base = buildInstrument(m)\n const instrument = attributes === undefined ? base : Metric.withAttributes(base, attributes)\n return Metric.update(instrument, m.value)\n },\n { discard: true },\n )\n\nexport type RecordOptions = {\n /** Attributes applied to every recorded metric, e.g. `{ model, provider }`. */\n readonly attributes?: Readonly<Record<string, string>>\n}\n\n/**\n * Record every measurement on every metric event into effect `Metric`\n * instruments, then pass events through unchanged. Matches events structurally\n * (`isMetricEvent`), so it records built-in and custom metrics alike, and\n * dispatches on `measurement.kind`, never on the event tag. Sits mid-pipeline\n * before the transport. Provide an OTLP (or Prometheus) layer to export the\n * instruments it populates.\n */\nexport const record =\n (options?: RecordOptions) =>\n <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>\n self.pipe(\n Stream.tap((ev) => (isMetricEvent(ev) ? recordEvent(ev, options?.attributes) : Effect.void)),\n )\n\n// ---------------------------------------------------------------------------\n// OTLP wiring\n// ---------------------------------------------------------------------------\n\nexport type LayerOtlpOptions = {\n readonly url: string\n readonly resource?: {\n readonly serviceName?: string\n readonly serviceVersion?: string\n }\n readonly exportInterval?: Duration.Input\n}\n\n/**\n * Convenience layer that exports recorded metrics over OTLP. Provides the JSON\n * serializer and leaves `HttpClient` as a requirement, so the platform's client\n * (FetchHttpClient, NodeHttpClient, ...) is provided at the edge. This keeps\n * the library platform-independent.\n */\nexport const layerOtlp = (\n options: LayerOtlpOptions,\n): Layer.Layer<never, never, HttpClient.HttpClient> =>\n OtlpMetrics.layer(options).pipe(Layer.provide(OtlpSerialization.layerJson))\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,sBAAsB,OAAO,sBAAsB;CAAE,OAAO;CAAG,QAAQ;CAAG,OAAO;AAAG,CAAC;;;;;;;AAQ3F,MAAM,mBAAmB,MACvB,MAAM,MAAM,EAAE,IAAI,CAAC,CAAC,KAClB,MAAM,KAAK,iBAAiB,OAAO,QAAQ,EAAE,MAAM,EAAE,aAAa,KAAK,CAAC,CAAC,GACzE,MAAM,KAAK,mBAAmB,OAAO,UAAU,EAAE,MAAM,EAAE,YAAY,oBAAoB,CAAC,CAAC,GAC3F,MAAM,KAAK,eAAe,OAAO,MAAM,EAAE,IAAI,CAAC,GAC9C,MAAM,KAAK,eAAe,OAAO,MAAM,EAAE,IAAI,CAAC,GAC9C,MAAM,UACR;AAEF,MAAM,mBACJ,MACA,UAEA,SAAS,KAAA,IAAY,QAAQ,UAAU,KAAA,IAAY,OAAO;CAAE,GAAG;CAAM,GAAG;AAAM;AAEhF,MAAM,eACJ,IACA,mBAEA,OAAO,QACL,GAAG,eACF,MAAM;CACL,MAAM,aAAa,gBAAgB,gBAAgB,EAAE,UAAU;CAC/D,MAAM,OAAO,gBAAgB,CAAC;CAC9B,MAAM,aAAa,eAAe,KAAA,IAAY,OAAO,OAAO,eAAe,MAAM,UAAU;CAC3F,OAAO,OAAO,OAAO,YAAY,EAAE,KAAK;AAC1C,GACA,EAAE,SAAS,KAAK,CAClB;;;;;;;;;AAeF,MAAa,UACV,aACS,SACR,KAAK,KACH,OAAO,KAAK,OAAQ,cAAc,EAAE,IAAI,YAAY,IAAI,SAAS,UAAU,IAAI,OAAO,IAAK,CAC7F;;;;;;;AAqBJ,MAAa,aACX,YAEA,YAAY,MAAM,OAAO,CAAC,CAAC,KAAK,MAAM,QAAQ,kBAAkB,SAAS,CAAC"}
1
+ {"version":3,"file":"Telemetry.mjs","names":[],"sources":["../../src/observability/Telemetry.ts"],"sourcesContent":["import { Duration, Effect, Layer, Match, Metric, Stream } from \"effect\"\nimport type { HttpClient } from \"effect/http\"\nimport { OtlpMetrics, OtlpSerialization } from \"effect/observability\"\nimport { isMetricEvent, type Measurement, type MetricEvent } from \"./Metrics.js\"\n\n// ---------------------------------------------------------------------------\n// The agnostic recorder\n// ---------------------------------------------------------------------------\n\n/**\n * Histogram bucket boundaries used for every histogram measurement (the only\n * built-in one is throughput). Custom histogram metrics share these; a metric\n * that needs bespoke boundaries can update its own instrument directly instead\n * of routing through `record`.\n */\nconst histogramBoundaries = Metric.exponentialBoundaries({ start: 1, factor: 2, count: 16 })\n\n/**\n * Build the effect `Metric` instrument a measurement maps to. Dispatches on\n * `kind`, never on the event `_tag`, so a new built-in or custom metric needs\n * no change here. Instruments dedupe by name in the global registry, so a fresh\n * instance per call accumulates onto the same series (no cache needed).\n */\nconst buildInstrument = (m: Measurement): Metric.Metric<any, any> =>\n Match.value(m.kind).pipe(\n Match.when(\"counter\", () => Metric.counter(m.name, { incremental: true })),\n Match.when(\"histogram\", () => Metric.histogram(m.name, { boundaries: histogramBoundaries })),\n Match.when(\"timer\", () => Metric.timer(m.name)),\n Match.when(\"gauge\", () => Metric.gauge(m.name)),\n Match.exhaustive,\n )\n\nconst mergeAttributes = (\n base: Readonly<Record<string, string>> | undefined,\n extra: Readonly<Record<string, string>> | undefined,\n): Readonly<Record<string, string>> | undefined =>\n base === undefined ? extra : extra === undefined ? base : { ...base, ...extra }\n\nconst recordEvent = (\n ev: MetricEvent,\n baseAttributes: Readonly<Record<string, string>> | undefined,\n): Effect.Effect<void> =>\n Effect.forEach(\n ev.measurements,\n (m) => {\n const attributes = mergeAttributes(baseAttributes, m.attributes)\n const base = buildInstrument(m)\n const instrument = attributes === undefined ? base : Metric.withAttributes(base, attributes)\n return Metric.update(instrument, m.value)\n },\n { discard: true },\n )\n\nexport type RecordOptions = {\n /** Attributes applied to every recorded metric, e.g. `{ model, provider }`. */\n readonly attributes?: Readonly<Record<string, string>>\n}\n\n/**\n * Record every measurement on every metric event into effect `Metric`\n * instruments, then pass events through unchanged. Matches events structurally\n * (`isMetricEvent`), so it records built-in and custom metrics alike, and\n * dispatches on `measurement.kind`, never on the event tag. Sits mid-pipeline\n * before the transport. Provide an OTLP (or Prometheus) layer to export the\n * instruments it populates.\n */\nexport const record =\n (options?: RecordOptions) =>\n <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>\n self.pipe(\n Stream.tap((ev) => (isMetricEvent(ev) ? recordEvent(ev, options?.attributes) : Effect.void)),\n )\n\n// ---------------------------------------------------------------------------\n// OTLP wiring\n// ---------------------------------------------------------------------------\n\nexport type LayerOtlpOptions = {\n readonly url: string\n readonly resource?: {\n readonly serviceName?: string\n readonly serviceVersion?: string\n }\n readonly exportInterval?: Duration.Input\n}\n\n/**\n * Convenience layer that exports recorded metrics over OTLP. Provides the JSON\n * serializer and leaves `HttpClient` as a requirement, so the platform's client\n * (FetchHttpClient, NodeHttpClient, ...) is provided at the edge. This keeps\n * the library platform-independent.\n */\nexport const layerOtlp = (\n options: LayerOtlpOptions,\n): Layer.Layer<never, never, HttpClient.HttpClient> =>\n OtlpMetrics.layer(options).pipe(Layer.provide(OtlpSerialization.layerJson))\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,sBAAsB,OAAO,sBAAsB;CAAE,OAAO;CAAG,QAAQ;CAAG,OAAO;AAAG,CAAC;;;;;;;AAQ3F,MAAM,mBAAmB,MACvB,MAAM,MAAM,EAAE,IAAI,CAAC,CAAC,KAClB,MAAM,KAAK,iBAAiB,OAAO,QAAQ,EAAE,MAAM,EAAE,aAAa,KAAK,CAAC,CAAC,GACzE,MAAM,KAAK,mBAAmB,OAAO,UAAU,EAAE,MAAM,EAAE,YAAY,oBAAoB,CAAC,CAAC,GAC3F,MAAM,KAAK,eAAe,OAAO,MAAM,EAAE,IAAI,CAAC,GAC9C,MAAM,KAAK,eAAe,OAAO,MAAM,EAAE,IAAI,CAAC,GAC9C,MAAM,UACR;AAEF,MAAM,mBACJ,MACA,UAEA,SAAS,KAAA,IAAY,QAAQ,UAAU,KAAA,IAAY,OAAO;CAAE,GAAG;CAAM,GAAG;AAAM;AAEhF,MAAM,eACJ,IACA,mBAEA,OAAO,QACL,GAAG,eACF,MAAM;CACL,MAAM,aAAa,gBAAgB,gBAAgB,EAAE,UAAU;CAC/D,MAAM,OAAO,gBAAgB,CAAC;CAC9B,MAAM,aAAa,eAAe,KAAA,IAAY,OAAO,OAAO,eAAe,MAAM,UAAU;CAC3F,OAAO,OAAO,OAAO,YAAY,EAAE,KAAK;AAC1C,GACA,EAAE,SAAS,KAAK,CAClB;;;;;;;;;AAeF,MAAa,UACV,aACS,SACR,KAAK,KACH,OAAO,KAAK,OAAQ,cAAc,EAAE,IAAI,YAAY,IAAI,SAAS,UAAU,IAAI,OAAO,IAAK,CAC7F;;;;;;;AAqBJ,MAAa,aACX,YAEA,YAAY,MAAM,OAAO,CAAC,CAAC,KAAK,MAAM,QAAQ,kBAAkB,SAAS,CAAC"}
@@ -1,10 +1,10 @@
1
1
  import { makeEvent } from "./Metrics.mjs";
2
2
  import { layerOtlp, record } from "./Telemetry.mjs";
3
- import { o as describe } from "../utils-CiH_DjFW.mjs";
4
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
5
- import { t as it } from "../dist-nohcnkD6.mjs";
3
+ import { m as describe } from "../dist-DzeSyeYF.mjs";
4
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
5
+ import { t as it } from "../dist-C8RmtoR8.mjs";
6
6
  import { Array, Duration, Effect, Layer, Metric, Option, Ref, Stream } from "effect";
7
- import { HttpClient, HttpClientResponse } from "effect/unstable/http";
7
+ import { HttpClient, HttpClientResponse } from "effect/http";
8
8
  //#region src/observability/Telemetry.test.ts
9
9
  const event = (measurements) => makeEvent({
10
10
  _tag: "Test",
@@ -1 +1 @@
1
- {"version":3,"file":"Telemetry.test.mjs","names":["Arr"],"sources":["../../src/observability/Telemetry.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport { Array as Arr, Duration, Effect, Layer, Metric, Option, Ref, Stream } from \"effect\"\nimport { type HttpBody, HttpClient, HttpClientResponse } from \"effect/unstable/http\"\nimport { expect } from \"vitest\"\nimport { type Measurement, makeEvent } from \"./Metrics.js\"\nimport { layerOtlp, record } from \"./Telemetry.js\"\n\nconst event = (measurements: ReadonlyArray<Measurement>) =>\n makeEvent({ _tag: \"Test\", turnIndex: 0, measurements })\n\n// Each test gets a fresh registry so instruments do not leak across tests.\nconst isolated = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>\n Effect.provideService(self, Metric.MetricRegistry, new Map())\n\nconst findById = (snap: ReadonlyArray<Metric.Metric.Snapshot>, id: string) =>\n Option.getOrThrow(Arr.findFirst(snap, (s) => s.id === id))\n\ndescribe(\"record\", () => {\n it.effect(\"accumulates a counter measurement across events\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_counter\", kind: \"counter\", value: 5 }])\n yield* Stream.make(ev, ev).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"rec_counter\")\n expect(c.type).toBe(\"Counter\")\n expect((c.state as { readonly count: number }).count).toBe(10)\n }),\n ),\n )\n\n it.effect(\"records a timer measurement as a histogram observation\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_timer\", kind: \"timer\", value: Duration.millis(100) }])\n yield* Stream.make(ev).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const h = findById(snap, \"rec_timer\")\n expect(h.type).toBe(\"Histogram\")\n expect((h.state as { readonly count: number }).count).toBe(1)\n }),\n ),\n )\n\n it.effect(\"applies base attributes to every recorded metric\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_attr\", kind: \"counter\", value: 1 }])\n yield* Stream.make(ev).pipe(\n record({ attributes: { model: \"test-model\" } }),\n Stream.runDrain,\n )\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"rec_attr\")\n expect(c.attributes).toMatchObject({ model: \"test-model\" })\n }),\n ),\n )\n\n it.effect(\"records a custom metric with a novel name, unchanged recorder\", () =>\n isolated(\n Effect.gen(function* () {\n // A user-defined event the recorder has never seen: still exported.\n const custom = makeEvent({\n _tag: \"ToolLatency\",\n turnIndex: 0,\n measurements: [{ name: \"myapp_tool_latency_total\", kind: \"counter\", value: 3 }],\n })\n yield* Stream.make(custom).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"myapp_tool_latency_total\")\n expect((c.state as { readonly count: number }).count).toBe(3)\n }),\n ),\n )\n\n it.effect(\"passes non-metric elements through untouched\", () =>\n isolated(\n Effect.gen(function* () {\n const out = yield* Stream.make(\"a\", \"b\", \"c\").pipe(record(), Stream.runCollect)\n expect(out).toEqual([\"a\", \"b\", \"c\"])\n }),\n ),\n )\n})\n\nconst bodyToString = (body: HttpBody.HttpBody): string =>\n body._tag === \"Uint8Array\" ? new TextDecoder().decode(body.body) : \"\"\n\ndescribe(\"layerOtlp\", () => {\n it.effect(\"exports recorded metrics over OTLP via a mock HttpClient\", () =>\n isolated(\n Effect.gen(function* () {\n const bodies = yield* Ref.make<ReadonlyArray<string>>([])\n const mockClient = HttpClient.make((request) =>\n Ref.update(bodies, (b) => [...b, bodyToString(request.body)]).pipe(\n Effect.as(HttpClientResponse.fromWeb(request, new Response(\"{}\", { status: 200 }))),\n ),\n )\n const clientLayer = Layer.succeed(HttpClient.HttpClient, mockClient)\n const ev = event([{ name: \"otlp_export_counter\", kind: \"counter\", value: 3 }])\n\n // Provide the OTLP layer scoped to the recording program. The exporter\n // flushes on scope close (interval is long, so we rely on the flush).\n yield* Stream.make(ev).pipe(\n record(),\n Stream.runDrain,\n Effect.provide(\n layerOtlp({\n url: \"http://localhost:4318/v1/metrics\",\n resource: { serviceName: \"test-service\" },\n exportInterval: \"10 minutes\",\n }).pipe(Layer.provide(clientLayer)),\n ),\n )\n\n const captured = yield* Ref.get(bodies)\n expect(captured.length).toBeGreaterThanOrEqual(1)\n expect(captured.join(\"\\n\")).toContain(\"otlp_export_counter\")\n }),\n ),\n )\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"Telemetry.test.mjs","names":["Arr"],"sources":["../../src/observability/Telemetry.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport { Array as Arr, Duration, Effect, Layer, Metric, Option, Ref, Stream } from \"effect\"\nimport { type HttpBody, HttpClient, HttpClientResponse } from \"effect/http\"\nimport { expect } from \"vitest\"\nimport { type Measurement, makeEvent } from \"./Metrics.js\"\nimport { layerOtlp, record } from \"./Telemetry.js\"\n\nconst event = (measurements: ReadonlyArray<Measurement>) =>\n makeEvent({ _tag: \"Test\", turnIndex: 0, measurements })\n\n// Each test gets a fresh registry so instruments do not leak across tests.\nconst isolated = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>\n Effect.provideService(self, Metric.MetricRegistry, new Map())\n\nconst findById = (snap: ReadonlyArray<Metric.Metric.Snapshot>, id: string) =>\n Option.getOrThrow(Arr.findFirst(snap, (s) => s.id === id))\n\ndescribe(\"record\", () => {\n it.effect(\"accumulates a counter measurement across events\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_counter\", kind: \"counter\", value: 5 }])\n yield* Stream.make(ev, ev).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"rec_counter\")\n expect(c.type).toBe(\"Counter\")\n expect((c.state as { readonly count: number }).count).toBe(10)\n }),\n ),\n )\n\n it.effect(\"records a timer measurement as a histogram observation\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_timer\", kind: \"timer\", value: Duration.millis(100) }])\n yield* Stream.make(ev).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const h = findById(snap, \"rec_timer\")\n expect(h.type).toBe(\"Histogram\")\n expect((h.state as { readonly count: number }).count).toBe(1)\n }),\n ),\n )\n\n it.effect(\"applies base attributes to every recorded metric\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_attr\", kind: \"counter\", value: 1 }])\n yield* Stream.make(ev).pipe(\n record({ attributes: { model: \"test-model\" } }),\n Stream.runDrain,\n )\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"rec_attr\")\n expect(c.attributes).toMatchObject({ model: \"test-model\" })\n }),\n ),\n )\n\n it.effect(\"records a custom metric with a novel name, unchanged recorder\", () =>\n isolated(\n Effect.gen(function* () {\n // A user-defined event the recorder has never seen: still exported.\n const custom = makeEvent({\n _tag: \"ToolLatency\",\n turnIndex: 0,\n measurements: [{ name: \"myapp_tool_latency_total\", kind: \"counter\", value: 3 }],\n })\n yield* Stream.make(custom).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"myapp_tool_latency_total\")\n expect((c.state as { readonly count: number }).count).toBe(3)\n }),\n ),\n )\n\n it.effect(\"passes non-metric elements through untouched\", () =>\n isolated(\n Effect.gen(function* () {\n const out = yield* Stream.make(\"a\", \"b\", \"c\").pipe(record(), Stream.runCollect)\n expect(out).toEqual([\"a\", \"b\", \"c\"])\n }),\n ),\n )\n})\n\nconst bodyToString = (body: HttpBody.HttpBody): string =>\n body._tag === \"Uint8Array\" ? new TextDecoder().decode(body.body) : \"\"\n\ndescribe(\"layerOtlp\", () => {\n it.effect(\"exports recorded metrics over OTLP via a mock HttpClient\", () =>\n isolated(\n Effect.gen(function* () {\n const bodies = yield* Ref.make<ReadonlyArray<string>>([])\n const mockClient = HttpClient.make((request) =>\n Ref.update(bodies, (b) => [...b, bodyToString(request.body)]).pipe(\n Effect.as(HttpClientResponse.fromWeb(request, new Response(\"{}\", { status: 200 }))),\n ),\n )\n const clientLayer = Layer.succeed(HttpClient.HttpClient, mockClient)\n const ev = event([{ name: \"otlp_export_counter\", kind: \"counter\", value: 3 }])\n\n // Provide the OTLP layer scoped to the recording program. The exporter\n // flushes on scope close (interval is long, so we rely on the flush).\n yield* Stream.make(ev).pipe(\n record(),\n Stream.runDrain,\n Effect.provide(\n layerOtlp({\n url: \"http://localhost:4318/v1/metrics\",\n resource: { serviceName: \"test-service\" },\n exportInterval: \"10 minutes\",\n }).pipe(Layer.provide(clientLayer)),\n ),\n )\n\n const captured = yield* Ref.get(bodies)\n expect(captured.length).toBeGreaterThanOrEqual(1)\n expect(captured.join(\"\\n\")).toContain(\"otlp_export_counter\")\n }),\n ),\n )\n})\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  import { imageBase64 } from "../domain/Image.mjs";
2
2
  import { RealtimeEvent, RealtimeInput } from "../domain/Realtime.mjs";
3
3
  import { open, sendVideoFrame, toolCalls } from "./RealtimeSession.mjs";
4
- import { o as describe } from "../utils-CiH_DjFW.mjs";
5
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
6
- import { t as import_dist } from "../dist-BaenoU1A.mjs";
7
- import { t as it } from "../dist-nohcnkD6.mjs";
4
+ import { m as describe } from "../dist-DzeSyeYF.mjs";
5
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
6
+ import { t as import_dist } from "../dist-DNeuh84d.mjs";
7
+ import { t as it } from "../dist-C8RmtoR8.mjs";
8
8
  import { layer, layerAudioOnly } from "../testing/MockRealtimeSession.mjs";
9
9
  import { Effect, Stream } from "effect";
10
10
  //#region src/realtime/RealtimeSession.test.ts
@@ -1,7 +1,7 @@
1
1
  import { ContentFiltered, RateLimited, Timeout, Unavailable } from "../domain/AiError.mjs";
2
2
  import { effect, stream } from "./Retry.mjs";
3
- import { m as it, o as describe } from "../utils-CiH_DjFW.mjs";
4
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
3
+ import { b as it, m as describe } from "../dist-DzeSyeYF.mjs";
4
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
5
5
  import { Cause, Effect, Exit, Option, Ref, Schedule, Stream } from "effect";
6
6
  //#region src/retry/Retry.test.ts
7
7
  const planned = (attempts, plan) => Stream.unwrap(Ref.getAndUpdate(attempts, (n) => n + 1).pipe(Effect.map((n) => plan[Math.min(n, plan.length - 1)])));
@@ -1,7 +1,7 @@
1
1
  import { streamSynthesis, streamSynthesisFrom, streamSynthesizeDialogue, synthesize, synthesizeDialogue } from "./SpeechSynthesizer.mjs";
2
- import { m as it, o as describe } from "../utils-CiH_DjFW.mjs";
3
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
4
- import { t as import_dist } from "../dist-BaenoU1A.mjs";
2
+ import { b as it, m as describe } from "../dist-DzeSyeYF.mjs";
3
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
4
+ import { t as import_dist } from "../dist-DNeuh84d.mjs";
5
5
  import { layer, layerWithoutIncremental, layerWithoutMultiSpeaker } from "../testing/MockSpeechSynthesizer.mjs";
6
6
  import { Duration, Effect, Stream } from "effect";
7
7
  //#region src/speech-synthesizer/SpeechSynthesizer.test.ts
@@ -1,6 +1,6 @@
1
1
  import { fromBytes, parse, toBytes } from "./JSONL.mjs";
2
- import { m as it, o as describe } from "../utils-CiH_DjFW.mjs";
3
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
2
+ import { b as it, m as describe } from "../dist-DzeSyeYF.mjs";
3
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
4
4
  import { Effect, Result, Schema, Stream } from "effect";
5
5
  //#region src/streaming/JSONL.test.ts
6
6
  const enc = new TextEncoder();
@@ -1,5 +1,5 @@
1
1
  import { Effect } from "effect";
2
- import { HttpClientRequest } from "effect/unstable/http";
2
+ import { HttpClientRequest } from "effect/http";
3
3
  declare namespace Multipart_d_exports {
4
4
  export { bodyMultipart };
5
5
  }
@@ -1,6 +1,6 @@
1
1
  import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { Effect } from "effect";
3
- import { HttpClientRequest } from "effect/unstable/http";
3
+ import { HttpClientRequest } from "effect/http";
4
4
  //#region src/streaming/Multipart.ts
5
5
  var Multipart_exports = /* @__PURE__ */ __exportAll({ bodyMultipart: () => bodyMultipart });
6
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Multipart.mjs","names":[],"sources":["../../src/streaming/Multipart.ts"],"sourcesContent":["import { Effect } from \"effect\"\nimport { HttpClientRequest } from \"effect/unstable/http\"\n\n/**\n * Multipart body for an `HttpClientRequest`, encoded to bytes.\n *\n * Use this over `HttpClientRequest.bodyFormData`: the Undici client\n * passes the `FormData` object straight to `dispatcher.request`, which\n * cannot serialise it, so the request is never sent and the effect\n * hangs with no error. Encoding here produces the same bytes every\n * other client derives internally, so it works on all of them.\n *\n * The failure is whatever `Response.arrayBuffer` rejects with; map it\n * to your provider's transport error at the call site.\n */\nexport const bodyMultipart = (\n form: FormData,\n): Effect.Effect<\n (request: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest,\n unknown\n> =>\n Effect.gen(function* () {\n const encoded = new Response(form)\n const buffer = yield* Effect.tryPromise(() => encoded.arrayBuffer())\n return HttpClientRequest.bodyUint8Array(\n new Uint8Array(buffer),\n encoded.headers.get(\"content-type\") ?? \"multipart/form-data\",\n )\n })\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,MAAa,iBACX,SAKA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,IAAI,SAAS,IAAI;CACjC,MAAM,SAAS,OAAO,OAAO,iBAAiB,QAAQ,YAAY,CAAC;CACnE,OAAO,kBAAkB,eACvB,IAAI,WAAW,MAAM,GACrB,QAAQ,QAAQ,IAAI,cAAc,KAAK,qBACzC;AACF,CAAC"}
1
+ {"version":3,"file":"Multipart.mjs","names":[],"sources":["../../src/streaming/Multipart.ts"],"sourcesContent":["import { Effect } from \"effect\"\nimport { HttpClientRequest } from \"effect/http\"\n\n/**\n * Multipart body for an `HttpClientRequest`, encoded to bytes.\n *\n * Use this over `HttpClientRequest.bodyFormData`: the Undici client\n * passes the `FormData` object straight to `dispatcher.request`, which\n * cannot serialise it, so the request is never sent and the effect\n * hangs with no error. Encoding here produces the same bytes every\n * other client derives internally, so it works on all of them.\n *\n * The failure is whatever `Response.arrayBuffer` rejects with; map it\n * to your provider's transport error at the call site.\n */\nexport const bodyMultipart = (\n form: FormData,\n): Effect.Effect<\n (request: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest,\n unknown\n> =>\n Effect.gen(function* () {\n const encoded = new Response(form)\n const buffer = yield* Effect.tryPromise(() => encoded.arrayBuffer())\n return HttpClientRequest.bodyUint8Array(\n new Uint8Array(buffer),\n encoded.headers.get(\"content-type\") ?? \"multipart/form-data\",\n )\n })\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,MAAa,iBACX,SAKA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,IAAI,SAAS,IAAI;CACjC,MAAM,SAAS,OAAO,OAAO,iBAAiB,QAAQ,YAAY,CAAC;CACnE,OAAO,kBAAkB,eACvB,IAAI,WAAW,MAAM,GACrB,QAAQ,QAAQ,IAAI,cAAc,KAAK,qBACzC;AACF,CAAC"}
@@ -15,7 +15,7 @@ type Event = {
15
15
  };
16
16
  /**
17
17
  * Decode a `Stream<Uint8Array>` (e.g. an HTTP response body) into a
18
- * `Stream<SSE.Event>`, using the spec parser from `effect/unstable/encoding`.
18
+ * `Stream<SSE.Event>`, using the spec parser from `effect/encoding`.
19
19
  * An event is dispatched only once its terminating blank line arrives, so a
20
20
  * partial event at stream end (clean or failed) is discarded per the spec
21
21
  * rather than surfaced as a truncated frame.
@@ -1,6 +1,6 @@
1
1
  import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { Stream } from "effect";
3
- import { Sse } from "effect/unstable/encoding";
3
+ import { Sse } from "effect/encoding";
4
4
  //#region src/streaming/SSE.ts
5
5
  var SSE_exports = /* @__PURE__ */ __exportAll({
6
6
  fromBytes: () => fromBytes,
@@ -22,7 +22,7 @@ const makeParserState = () => {
22
22
  };
23
23
  /**
24
24
  * Decode a `Stream<Uint8Array>` (e.g. an HTTP response body) into a
25
- * `Stream<SSE.Event>`, using the spec parser from `effect/unstable/encoding`.
25
+ * `Stream<SSE.Event>`, using the spec parser from `effect/encoding`.
26
26
  * An event is dispatched only once its terminating blank line arrives, so a
27
27
  * partial event at stream end (clean or failed) is discarded per the spec
28
28
  * rather than surfaced as a truncated frame.
@@ -1 +1 @@
1
- {"version":3,"file":"SSE.mjs","names":[],"sources":["../../src/streaming/SSE.ts"],"sourcesContent":["import { Stream } from \"effect\"\nimport { Sse } from \"effect/unstable/encoding\"\n\n/**\n * One Server-Sent Event. Fields per the WHATWG spec:\n * - `event`: optional event name (absent for the default \"message\")\n * - `data`: payload, with multiple `data:` lines joined by `\\n`\n * - `id`: optional last-event id\n */\nexport type Event = {\n readonly event?: string\n readonly data: string\n readonly id?: string\n}\n\nconst toEvent = (wire: Sse.Event): Event => ({\n data: wire.data,\n ...(wire.event !== \"message\" && { event: wire.event }),\n ...(wire.id !== undefined && { id: wire.id }),\n})\n\ntype ParserState = {\n readonly parser: Sse.Parser\n readonly out: Array<Event>\n}\n\nconst makeParserState = (): ParserState => {\n const out: Array<Event> = []\n const parser = Sse.makeParser((event) => {\n // `retry:` reconnection hints have no representation in `Event`\n if (event._tag === \"Event\") out.push(toEvent(event))\n })\n return { parser, out }\n}\n\n/**\n * Decode a `Stream<Uint8Array>` (e.g. an HTTP response body) into a\n * `Stream<SSE.Event>`, using the spec parser from `effect/unstable/encoding`.\n * An event is dispatched only once its terminating blank line arrives, so a\n * partial event at stream end (clean or failed) is discarded per the spec\n * rather than surfaced as a truncated frame.\n */\nexport const fromBytes = <E, R>(\n self: Stream.Stream<Uint8Array, E, R>,\n): Stream.Stream<Event, E, R> =>\n self.pipe(\n Stream.decodeText,\n Stream.mapAccum(makeParserState, (state, text: string) => {\n state.parser.feed(text)\n return [state, state.out.splice(0)] as const\n }),\n )\n\nconst encoder = new TextEncoder()\n\n/**\n * Encode a `Stream<Event>` as `Stream<Uint8Array>` ready to send on an\n * HTTP response with `Content-Type: text/event-stream`.\n */\nexport const toBytes = <E, R>(self: Stream.Stream<Event, E, R>): Stream.Stream<Uint8Array, E, R> =>\n Stream.map(self, (ev) =>\n encoder.encode(\n Sse.encoder.write({\n _tag: \"Event\",\n event: ev.event ?? \"message\",\n id: ev.id,\n data: ev.data,\n }),\n ),\n )\n"],"mappings":";;;;;;;;AAeA,MAAM,WAAW,UAA4B;CAC3C,MAAM,KAAK;CACX,GAAI,KAAK,UAAU,aAAa,EAAE,OAAO,KAAK,MAAM;CACpD,GAAI,KAAK,OAAO,KAAA,KAAa,EAAE,IAAI,KAAK,GAAG;AAC7C;AAOA,MAAM,wBAAqC;CACzC,MAAM,MAAoB,CAAC;CAK3B,OAAO;EAAE,QAJM,IAAI,YAAY,UAAU;GAEvC,IAAI,MAAM,SAAS,SAAS,IAAI,KAAK,QAAQ,KAAK,CAAC;EACrD,CACc;EAAG;CAAI;AACvB;;;;;;;;AASA,MAAa,aACX,SAEA,KAAK,KACH,OAAO,YACP,OAAO,SAAS,kBAAkB,OAAO,SAAiB;CACxD,MAAM,OAAO,KAAK,IAAI;CACtB,OAAO,CAAC,OAAO,MAAM,IAAI,OAAO,CAAC,CAAC;AACpC,CAAC,CACH;AAEF,MAAM,UAAU,IAAI,YAAY;;;;;AAMhC,MAAa,WAAiB,SAC5B,OAAO,IAAI,OAAO,OAChB,QAAQ,OACN,IAAI,QAAQ,MAAM;CAChB,MAAM;CACN,OAAO,GAAG,SAAS;CACnB,IAAI,GAAG;CACP,MAAM,GAAG;AACX,CAAC,CACH,CACF"}
1
+ {"version":3,"file":"SSE.mjs","names":[],"sources":["../../src/streaming/SSE.ts"],"sourcesContent":["import { Stream } from \"effect\"\nimport { Sse } from \"effect/encoding\"\n\n/**\n * One Server-Sent Event. Fields per the WHATWG spec:\n * - `event`: optional event name (absent for the default \"message\")\n * - `data`: payload, with multiple `data:` lines joined by `\\n`\n * - `id`: optional last-event id\n */\nexport type Event = {\n readonly event?: string\n readonly data: string\n readonly id?: string\n}\n\nconst toEvent = (wire: Sse.Event): Event => ({\n data: wire.data,\n ...(wire.event !== \"message\" && { event: wire.event }),\n ...(wire.id !== undefined && { id: wire.id }),\n})\n\ntype ParserState = {\n readonly parser: Sse.Parser\n readonly out: Array<Event>\n}\n\nconst makeParserState = (): ParserState => {\n const out: Array<Event> = []\n const parser = Sse.makeParser((event) => {\n // `retry:` reconnection hints have no representation in `Event`\n if (event._tag === \"Event\") out.push(toEvent(event))\n })\n return { parser, out }\n}\n\n/**\n * Decode a `Stream<Uint8Array>` (e.g. an HTTP response body) into a\n * `Stream<SSE.Event>`, using the spec parser from `effect/encoding`.\n * An event is dispatched only once its terminating blank line arrives, so a\n * partial event at stream end (clean or failed) is discarded per the spec\n * rather than surfaced as a truncated frame.\n */\nexport const fromBytes = <E, R>(\n self: Stream.Stream<Uint8Array, E, R>,\n): Stream.Stream<Event, E, R> =>\n self.pipe(\n Stream.decodeText,\n Stream.mapAccum(makeParserState, (state, text: string) => {\n state.parser.feed(text)\n return [state, state.out.splice(0)] as const\n }),\n )\n\nconst encoder = new TextEncoder()\n\n/**\n * Encode a `Stream<Event>` as `Stream<Uint8Array>` ready to send on an\n * HTTP response with `Content-Type: text/event-stream`.\n */\nexport const toBytes = <E, R>(self: Stream.Stream<Event, E, R>): Stream.Stream<Uint8Array, E, R> =>\n Stream.map(self, (ev) =>\n encoder.encode(\n Sse.encoder.write({\n _tag: \"Event\",\n event: ev.event ?? \"message\",\n id: ev.id,\n data: ev.data,\n }),\n ),\n )\n"],"mappings":";;;;;;;;AAeA,MAAM,WAAW,UAA4B;CAC3C,MAAM,KAAK;CACX,GAAI,KAAK,UAAU,aAAa,EAAE,OAAO,KAAK,MAAM;CACpD,GAAI,KAAK,OAAO,KAAA,KAAa,EAAE,IAAI,KAAK,GAAG;AAC7C;AAOA,MAAM,wBAAqC;CACzC,MAAM,MAAoB,CAAC;CAK3B,OAAO;EAAE,QAJM,IAAI,YAAY,UAAU;GAEvC,IAAI,MAAM,SAAS,SAAS,IAAI,KAAK,QAAQ,KAAK,CAAC;EACrD,CACc;EAAG;CAAI;AACvB;;;;;;;;AASA,MAAa,aACX,SAEA,KAAK,KACH,OAAO,YACP,OAAO,SAAS,kBAAkB,OAAO,SAAiB;CACxD,MAAM,OAAO,KAAK,IAAI;CACtB,OAAO,CAAC,OAAO,MAAM,IAAI,OAAO,CAAC,CAAC;AACpC,CAAC,CACH;AAEF,MAAM,UAAU,IAAI,YAAY;;;;;AAMhC,MAAa,WAAiB,SAC5B,OAAO,IAAI,OAAO,OAChB,QAAQ,OACN,IAAI,QAAQ,MAAM;CAChB,MAAM;CACN,OAAO,GAAG,SAAS;CACnB,IAAI,GAAG;CACP,MAAM,GAAG;AACX,CAAC,CACH,CACF"}
@@ -1,6 +1,6 @@
1
1
  import { fromBytes, toBytes } from "./SSE.mjs";
2
- import { m as it, o as describe } from "../utils-CiH_DjFW.mjs";
3
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
2
+ import { b as it, m as describe } from "../dist-DzeSyeYF.mjs";
3
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
4
4
  import { Cause, Deferred, Effect, Exit, Fiber, Result, Stream } from "effect";
5
5
  //#region src/streaming/SSE.test.ts
6
6
  const enc = new TextEncoder();
@@ -1,7 +1,7 @@
1
1
  import { drainBurst, onQuiet, settleBurst } from "./Settle.mjs";
2
- import { o as describe } from "../utils-CiH_DjFW.mjs";
3
- import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
4
- import { t as it } from "../dist-nohcnkD6.mjs";
2
+ import { m as describe } from "../dist-DzeSyeYF.mjs";
3
+ import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
4
+ import { t as it } from "../dist-C8RmtoR8.mjs";
5
5
  import { Effect, Fiber, Option, Queue, Ref, Stream } from "effect";
6
6
  import { TestClock } from "effect/testing";
7
7
  //#region src/streaming/Settle.test.ts
@@ -1,8 +1,8 @@
1
1
  import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
2
  import { Cause, Duration, Effect, Queue, Schema, Scope } from "effect";
3
- import * as Socket from "effect/unstable/socket/Socket";
3
+ import * as Socket from "effect/socket/Socket";
4
4
  declare namespace WebSocketSession_d_exports {
5
- export { OpenOptions, WebSocketSession, open, redactUrl, toAiError };
5
+ export { OpenOptions, WebSocketSession, isCleanClose, open, redactUrl, toAiError };
6
6
  }
7
7
  type WebSocketSession<A> = {
8
8
  /** Fails `Unavailable` once the socket is closed. */
@@ -25,6 +25,8 @@ type OpenOptions<A, I> = {
25
25
  */
26
26
  declare const redactUrl: (text: string) => string;
27
27
  declare const toAiError: (provider: string) => (error: Socket.SocketError) => AiError;
28
+ /** Effect's `Socket` fails on every close, clean or not. */
29
+ declare const isCleanClose: (error: Socket.SocketError) => boolean;
28
30
  /**
29
31
  * Connect for the lifetime of the surrounding `Scope`. The reader runs in a
30
32
  * forked fiber; a dirty close reaches the consumer as a failed queue rather
@@ -32,5 +34,5 @@ declare const toAiError: (provider: string) => (error: Socket.SocketError) => Ai
32
34
  */
33
35
  declare const open: <A, I>(options: OpenOptions<A, I>) => Effect.Effect<WebSocketSession<A>, AiError, Scope.Scope | Socket.WebSocketConstructor>;
34
36
  //#endregion
35
- export { OpenOptions, WebSocketSession, open, redactUrl, WebSocketSession_d_exports as t, toAiError };
37
+ export { OpenOptions, WebSocketSession, isCleanClose, open, redactUrl, WebSocketSession_d_exports as t, toAiError };
36
38
  //# sourceMappingURL=WebSocketSession.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"WebSocketSession.d.mts","names":[],"sources":["../../src/streaming/WebSocketSession.ts"],"mappings":";;;;;;KAuBY,iBAAiB;;WAElB,OAAO,kBAAkB,OAAO,aAAa;;WAE7C,QAAQ,MAAM,QAAQ,GAAG,UAAkB,MAAM;;KAGhD,YAAY,GAAG;WAChB;WACA;;WAEA,QAAQ,OAAO,MAAM,GAAG;WACxB,cAAc,SAAS;WACvB;;;;;;;cAiBE,YAAS;cAeT,YAAS,sBACH,OACT,OAAO,gBAAc;;;;;;cAsBlB,OAAQ,GAAG,GAAC,SACd,YAAY,GAAG,OACvB,OAAO,OAAO,iBAAiB,IAAI,SAAiB,MAAM,QAAQ,OAAO"}
1
+ {"version":3,"file":"WebSocketSession.d.mts","names":[],"sources":["../../src/streaming/WebSocketSession.ts"],"mappings":";;;;;;KAyBY,iBAAiB;;WAElB,OAAO,kBAAkB,OAAO,aAAa;;WAE7C,QAAQ,MAAM,QAAQ,GAAG,UAAkB,MAAM;;KAGhD,YAAY,GAAG;WAChB;WACA;;WAEA,QAAQ,OAAO,MAAM,GAAG;WACxB,cAAc,SAAS;WACvB;;;;;;;cAiBE,YAAS;cAeT,YAAS,sBACH,OACT,OAAO,gBAAc;;cAqBlB,eAAY,OAAW,OAAO;;;;;;cAQ9B,OAAQ,GAAG,GAAC,SACd,YAAY,GAAG,OACvB,OAAO,OAAO,iBAAiB,IAAI,SAAiB,MAAM,QAAQ,OAAO"}
@@ -1,8 +1,8 @@
1
1
  import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { AuthFailed, Timeout, Unavailable } from "../domain/AiError.mjs";
3
3
  import { parseSafe } from "./JSONL.mjs";
4
- import { Cause, Effect, Match, Option, Predicate, Queue, Schema } from "effect";
5
- import * as Socket from "effect/unstable/socket/Socket";
4
+ import { Cause, Effect, Match, Option, Predicate, Queue, Ref, Schema, Stream } from "effect";
5
+ import * as Socket from "effect/socket/Socket";
6
6
  //#region src/streaming/WebSocketSession.ts
7
7
  /**
8
8
  * One JSON-over-WebSocket session: connect, read decoded frames off a queue,
@@ -13,6 +13,7 @@ import * as Socket from "effect/unstable/socket/Socket";
13
13
  * own copies of this.
14
14
  */
15
15
  var WebSocketSession_exports = /* @__PURE__ */ __exportAll({
16
+ isCleanClose: () => isCleanClose,
16
17
  open: () => open,
17
18
  redactUrl: () => redactUrl,
18
19
  toAiError: () => toAiError
@@ -50,6 +51,14 @@ const toAiError = (provider) => (error) => {
50
51
  raw
51
52
  })));
52
53
  };
54
+ /** Normal closure, going away, and no status code. */
55
+ const cleanCloseCodes = /* @__PURE__ */ new Set([
56
+ 1e3,
57
+ 1001,
58
+ 1005
59
+ ]);
60
+ /** Effect's `Socket` fails on every close, clean or not. */
61
+ const isCleanClose = (error) => error.reason._tag === "SocketCloseError" && cleanCloseCodes.has(error.reason.code);
53
62
  /**
54
63
  * Connect for the lifetime of the surrounding `Scope`. The reader runs in a
55
64
  * forked fiber; a dirty close reaches the consumer as a failed queue rather
@@ -58,12 +67,14 @@ const toAiError = (provider) => (error) => {
58
67
  const open = (options) => Effect.gen(function* () {
59
68
  const asAiError = toAiError(options.provider);
60
69
  const decode = Schema.decodeUnknownEffect(options.schema);
61
- const socket = yield* Socket.makeWebSocket(options.url, {
62
- closeCodeIsError: (code) => code !== 1e3 && code !== 1001 && code !== 1005,
63
- ...options.openTimeout !== void 0 && { openTimeout: options.openTimeout }
64
- });
70
+ const socket = yield* Socket.makeWebSocket(options.url, { ...options.openTimeout !== void 0 && { openTimeout: options.openTimeout } });
65
71
  const frames = yield* Queue.bounded(options.capacity ?? 64);
66
- const write = yield* socket.writer;
72
+ const { write } = yield* socket.writer;
73
+ const ended = yield* Ref.make(false);
74
+ const closed = new Unavailable({
75
+ provider: options.provider,
76
+ raw: "socket closed"
77
+ });
67
78
  const onFrame = (raw) => Effect.gen(function* () {
68
79
  const json = yield* parseSafe(raw);
69
80
  if (json === void 0) return;
@@ -74,16 +85,17 @@ const open = (options) => Effect.gen(function* () {
74
85
  }
75
86
  yield* Queue.offer(frames, decoded.value);
76
87
  });
77
- yield* socket.runString(onFrame).pipe(Effect.mapError(asAiError), Effect.matchCauseEffect({
88
+ yield* Stream.fromPull(Socket.readerString(socket)).pipe(Stream.scoped, Stream.catchIf(isCleanClose, () => Stream.empty), Stream.runForEach(onFrame), Effect.mapError(asAiError), Effect.ensuring(Ref.set(ended, true)), Effect.matchCauseEffect({
78
89
  onFailure: (cause) => Cause.hasFails(cause) ? Queue.failCause(frames, cause) : Queue.end(frames),
79
90
  onSuccess: () => Queue.end(frames)
80
91
  }), Effect.forkScoped);
92
+ const send = (frame) => Effect.flatMap(Ref.get(ended), (isEnded) => isEnded ? Effect.fail(closed) : Effect.mapError(write(frame), asAiError));
81
93
  return {
82
- send: (frame) => write(frame).pipe(Effect.mapError(asAiError)),
94
+ send,
83
95
  frames
84
96
  };
85
97
  });
86
98
  //#endregion
87
- export { open, redactUrl, WebSocketSession_exports as t, toAiError };
99
+ export { isCleanClose, open, redactUrl, WebSocketSession_exports as t, toAiError };
88
100
 
89
101
  //# sourceMappingURL=WebSocketSession.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"WebSocketSession.mjs","names":["AiError.Timeout","AiError.AuthFailed","AiError.Unavailable","JSONL.parseSafe"],"sources":["../../src/streaming/WebSocketSession.ts"],"sourcesContent":["/**\n * One JSON-over-WebSocket session: connect, read decoded frames off a queue,\n * write frames back.\n *\n * For duplex protocols where the caller drives the conversation frame by\n * frame. The stream-shaped adapters (realtime STT and TTS) still carry their\n * own copies of this.\n */\nimport {\n Cause,\n type Duration,\n Effect,\n Match,\n Option,\n Predicate,\n Queue,\n Schema,\n type Scope,\n} from \"effect\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport * as AiError from \"../domain/AiError.js\"\nimport * as JSONL from \"./JSONL.js\"\n\nexport type WebSocketSession<A> = {\n /** Fails `Unavailable` once the socket is closed. */\n readonly send: (frame: string) => Effect.Effect<void, AiError.AiError>\n /** Ends when the socket closes cleanly, fails when it does not. */\n readonly frames: Queue.Dequeue<A, AiError.AiError | Cause.Done>\n}\n\nexport type OpenOptions<A, I> = {\n readonly url: string\n readonly provider: string\n /** Frames that do not decode are dropped, and logged at debug level. */\n readonly schema: Schema.Codec<A, I>\n readonly openTimeout?: Duration.Input\n readonly capacity?: number\n}\n\n/**\n * The cause of a failed open is a DOM `ErrorEvent`, not an `Error`, and its\n * `message` is a prototype getter, so a structural decode does not see it.\n */\nconst describeCause = (cause: unknown): string =>\n Predicate.hasProperty(cause, \"message\") && Predicate.isString(cause.message)\n ? cause.message\n : String(cause)\n\n/**\n * Some wires authenticate with a key in the query string, and some runtimes\n * name the socket URL in their open error (Bun does). Everything taken off a\n * socket error goes through this.\n */\nexport const redactUrl = (text: string): string =>\n text.replace(/(\\b(?:wss?|https?):\\/\\/[^\\s\"']*?)\\?[^\\s\"']*/gi, \"$1?<redacted>\")\n\nconst detailOf = Match.type<Socket.SocketError[\"reason\"]>().pipe(\n Match.tag(\"SocketCloseError\", (reason) => reason.message),\n Match.orElse((reason) => describeCause(reason.cause)),\n)\n\n/** Never the `SocketError` itself: callers print `raw` with `Cause.pretty`. */\nconst rawOf = (error: Socket.SocketError): string =>\n redactUrl(`${error.reason._tag}: ${detailOf(error.reason)}`)\n\n/** A rejected WS upgrade surfaces only as prose, so the status is read from it. */\nconst isAuthRejection = (text: string): boolean => /\\b40[13]\\b/.test(text)\n\nexport const toAiError =\n (provider: string) =>\n (error: Socket.SocketError): AiError.AiError => {\n const raw = rawOf(error)\n return Match.value(error.reason).pipe(\n Match.tag(\"SocketOpenError\", (reason) =>\n Match.value(reason.kind).pipe(\n Match.when(\"Timeout\", (): AiError.AiError => new AiError.Timeout({ provider, raw })),\n Match.orElse((): AiError.AiError =>\n isAuthRejection(describeCause(reason.cause))\n ? new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n : new AiError.Unavailable({ provider, raw }),\n ),\n ),\n ),\n Match.orElse((): AiError.AiError => new AiError.Unavailable({ provider, raw })),\n )\n }\n\n/**\n * Connect for the lifetime of the surrounding `Scope`. The reader runs in a\n * forked fiber; a dirty close reaches the consumer as a failed queue rather\n * than a clean end.\n */\nexport const open = <A, I>(\n options: OpenOptions<A, I>,\n): Effect.Effect<WebSocketSession<A>, AiError.AiError, Scope.Scope | Socket.WebSocketConstructor> =>\n Effect.gen(function* () {\n const asAiError = toAiError(options.provider)\n const decode = Schema.decodeUnknownEffect(options.schema)\n const socket = yield* Socket.makeWebSocket(options.url, {\n // Effect's Socket treats every close code as an error by default.\n closeCodeIsError: (code) => code !== 1000 && code !== 1001 && code !== 1005,\n ...(options.openTimeout !== undefined && { openTimeout: options.openTimeout }),\n })\n const frames = yield* Queue.bounded<A, AiError.AiError | Cause.Done>(options.capacity ?? 64)\n const write = yield* socket.writer\n\n const onFrame = (raw: string) =>\n Effect.gen(function* () {\n const json = yield* JSONL.parseSafe(raw)\n if (json === undefined) return\n const decoded = yield* decode(json).pipe(Effect.option)\n if (Option.isNone(decoded)) {\n yield* Effect.logDebug(`[${options.provider}] dropped frame`, { frame: json })\n return\n }\n yield* Queue.offer(frames, decoded.value)\n })\n\n yield* socket.runString(onFrame).pipe(\n Effect.mapError(asAiError),\n // `Queue.end` on a clean close so the consumer drains what is queued;\n // a failure must not arrive looking like the end of the conversation.\n Effect.matchCauseEffect({\n onFailure: (cause) =>\n Cause.hasFails(cause) ? Queue.failCause(frames, cause) : Queue.end(frames),\n onSuccess: () => Queue.end(frames),\n }),\n Effect.forkScoped,\n )\n\n return { send: (frame: string) => write(frame).pipe(Effect.mapError(asAiError)), frames }\n })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAM,iBAAiB,UACrB,UAAU,YAAY,OAAO,SAAS,KAAK,UAAU,SAAS,MAAM,OAAO,IACvE,MAAM,UACN,OAAO,KAAK;;;;;;AAOlB,MAAa,aAAa,SACxB,KAAK,QAAQ,iDAAiD,eAAe;AAE/E,MAAM,WAAW,MAAM,KAAmC,CAAC,CAAC,KAC1D,MAAM,IAAI,qBAAqB,WAAW,OAAO,OAAO,GACxD,MAAM,QAAQ,WAAW,cAAc,OAAO,KAAK,CAAC,CACtD;;AAGA,MAAM,SAAS,UACb,UAAU,GAAG,MAAM,OAAO,KAAK,IAAI,SAAS,MAAM,MAAM,GAAG;;AAG7D,MAAM,mBAAmB,SAA0B,aAAa,KAAK,IAAI;AAEzE,MAAa,aACV,cACA,UAA+C;CAC9C,MAAM,MAAM,MAAM,KAAK;CACvB,OAAO,MAAM,MAAM,MAAM,MAAM,CAAC,CAAC,KAC/B,MAAM,IAAI,oBAAoB,WAC5B,MAAM,MAAM,OAAO,IAAI,CAAC,CAAC,KACvB,MAAM,KAAK,iBAAkC,IAAIA,QAAgB;EAAE;EAAU;CAAI,CAAC,CAAC,GACnF,MAAM,aACJ,gBAAgB,cAAc,OAAO,KAAK,CAAC,IACvC,IAAIC,WAAmB;EAAE;EAAU,SAAS;EAAQ;CAAI,CAAC,IACzD,IAAIC,YAAoB;EAAE;EAAU;CAAI,CAAC,CAC/C,CACF,CACF,GACA,MAAM,aAA8B,IAAIA,YAAoB;EAAE;EAAU;CAAI,CAAC,CAAC,CAChF;AACF;;;;;;AAOF,MAAa,QACX,YAEA,OAAO,IAAI,aAAa;CACtB,MAAM,YAAY,UAAU,QAAQ,QAAQ;CAC5C,MAAM,SAAS,OAAO,oBAAoB,QAAQ,MAAM;CACxD,MAAM,SAAS,OAAO,OAAO,cAAc,QAAQ,KAAK;EAEtD,mBAAmB,SAAS,SAAS,OAAQ,SAAS,QAAQ,SAAS;EACvE,GAAI,QAAQ,gBAAgB,KAAA,KAAa,EAAE,aAAa,QAAQ,YAAY;CAC9E,CAAC;CACD,MAAM,SAAS,OAAO,MAAM,QAAyC,QAAQ,YAAY,EAAE;CAC3F,MAAM,QAAQ,OAAO,OAAO;CAE5B,MAAM,WAAW,QACf,OAAO,IAAI,aAAa;EACtB,MAAM,OAAO,OAAOC,UAAgB,GAAG;EACvC,IAAI,SAAS,KAAA,GAAW;EACxB,MAAM,UAAU,OAAO,OAAO,IAAI,CAAC,CAAC,KAAK,OAAO,MAAM;EACtD,IAAI,OAAO,OAAO,OAAO,GAAG;GAC1B,OAAO,OAAO,SAAS,IAAI,QAAQ,SAAS,kBAAkB,EAAE,OAAO,KAAK,CAAC;GAC7E;EACF;EACA,OAAO,MAAM,MAAM,QAAQ,QAAQ,KAAK;CAC1C,CAAC;CAEH,OAAO,OAAO,UAAU,OAAO,CAAC,CAAC,KAC/B,OAAO,SAAS,SAAS,GAGzB,OAAO,iBAAiB;EACtB,YAAY,UACV,MAAM,SAAS,KAAK,IAAI,MAAM,UAAU,QAAQ,KAAK,IAAI,MAAM,IAAI,MAAM;EAC3E,iBAAiB,MAAM,IAAI,MAAM;CACnC,CAAC,GACD,OAAO,UACT;CAEA,OAAO;EAAE,OAAO,UAAkB,MAAM,KAAK,CAAC,CAAC,KAAK,OAAO,SAAS,SAAS,CAAC;EAAG;CAAO;AAC1F,CAAC"}
1
+ {"version":3,"file":"WebSocketSession.mjs","names":["AiError.Timeout","AiError.AuthFailed","AiError.Unavailable","JSONL.parseSafe"],"sources":["../../src/streaming/WebSocketSession.ts"],"sourcesContent":["/**\n * One JSON-over-WebSocket session: connect, read decoded frames off a queue,\n * write frames back.\n *\n * For duplex protocols where the caller drives the conversation frame by\n * frame. The stream-shaped adapters (realtime STT and TTS) still carry their\n * own copies of this.\n */\nimport {\n Cause,\n type Duration,\n Effect,\n Match,\n Option,\n Predicate,\n Queue,\n Ref,\n Schema,\n type Scope,\n Stream,\n} from \"effect\"\nimport * as Socket from \"effect/socket/Socket\"\nimport * as AiError from \"../domain/AiError.js\"\nimport * as JSONL from \"./JSONL.js\"\n\nexport type WebSocketSession<A> = {\n /** Fails `Unavailable` once the socket is closed. */\n readonly send: (frame: string) => Effect.Effect<void, AiError.AiError>\n /** Ends when the socket closes cleanly, fails when it does not. */\n readonly frames: Queue.Dequeue<A, AiError.AiError | Cause.Done>\n}\n\nexport type OpenOptions<A, I> = {\n readonly url: string\n readonly provider: string\n /** Frames that do not decode are dropped, and logged at debug level. */\n readonly schema: Schema.Codec<A, I>\n readonly openTimeout?: Duration.Input\n readonly capacity?: number\n}\n\n/**\n * The cause of a failed open is a DOM `ErrorEvent`, not an `Error`, and its\n * `message` is a prototype getter, so a structural decode does not see it.\n */\nconst describeCause = (cause: unknown): string =>\n Predicate.hasProperty(cause, \"message\") && Predicate.isString(cause.message)\n ? cause.message\n : String(cause)\n\n/**\n * Some wires authenticate with a key in the query string, and some runtimes\n * name the socket URL in their open error (Bun does). Everything taken off a\n * socket error goes through this.\n */\nexport const redactUrl = (text: string): string =>\n text.replace(/(\\b(?:wss?|https?):\\/\\/[^\\s\"']*?)\\?[^\\s\"']*/gi, \"$1?<redacted>\")\n\nconst detailOf = Match.type<Socket.SocketError[\"reason\"]>().pipe(\n Match.tag(\"SocketCloseError\", (reason) => reason.message),\n Match.orElse((reason) => describeCause(reason.cause)),\n)\n\n/** Never the `SocketError` itself: callers print `raw` with `Cause.pretty`. */\nconst rawOf = (error: Socket.SocketError): string =>\n redactUrl(`${error.reason._tag}: ${detailOf(error.reason)}`)\n\n/** A rejected WS upgrade surfaces only as prose, so the status is read from it. */\nconst isAuthRejection = (text: string): boolean => /\\b40[13]\\b/.test(text)\n\nexport const toAiError =\n (provider: string) =>\n (error: Socket.SocketError): AiError.AiError => {\n const raw = rawOf(error)\n return Match.value(error.reason).pipe(\n Match.tag(\"SocketOpenError\", (reason) =>\n Match.value(reason.kind).pipe(\n Match.when(\"Timeout\", (): AiError.AiError => new AiError.Timeout({ provider, raw })),\n Match.orElse((): AiError.AiError =>\n isAuthRejection(describeCause(reason.cause))\n ? new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n : new AiError.Unavailable({ provider, raw }),\n ),\n ),\n ),\n Match.orElse((): AiError.AiError => new AiError.Unavailable({ provider, raw })),\n )\n }\n\n/** Normal closure, going away, and no status code. */\nconst cleanCloseCodes: ReadonlySet<number> = new Set([1000, 1001, 1005])\n\n/** Effect's `Socket` fails on every close, clean or not. */\nexport const isCleanClose = (error: Socket.SocketError): boolean =>\n error.reason._tag === \"SocketCloseError\" && cleanCloseCodes.has(error.reason.code)\n\n/**\n * Connect for the lifetime of the surrounding `Scope`. The reader runs in a\n * forked fiber; a dirty close reaches the consumer as a failed queue rather\n * than a clean end.\n */\nexport const open = <A, I>(\n options: OpenOptions<A, I>,\n): Effect.Effect<WebSocketSession<A>, AiError.AiError, Scope.Scope | Socket.WebSocketConstructor> =>\n Effect.gen(function* () {\n const asAiError = toAiError(options.provider)\n const decode = Schema.decodeUnknownEffect(options.schema)\n const socket = yield* Socket.makeWebSocket(options.url, {\n ...(options.openTimeout !== undefined && { openTimeout: options.openTimeout }),\n })\n const frames = yield* Queue.bounded<A, AiError.AiError | Cause.Done>(options.capacity ?? 64)\n const { write } = yield* socket.writer\n // The writer waits for the next connection rather than failing, so a send\n // after the reader ends has to be refused here.\n const ended = yield* Ref.make(false)\n const closed = new AiError.Unavailable({ provider: options.provider, raw: \"socket closed\" })\n\n const onFrame = (raw: string) =>\n Effect.gen(function* () {\n const json = yield* JSONL.parseSafe(raw)\n if (json === undefined) return\n const decoded = yield* decode(json).pipe(Effect.option)\n if (Option.isNone(decoded)) {\n yield* Effect.logDebug(`[${options.provider}] dropped frame`, { frame: json })\n return\n }\n yield* Queue.offer(frames, decoded.value)\n })\n\n yield* Stream.fromPull(Socket.readerString(socket)).pipe(\n Stream.scoped,\n Stream.catchIf(isCleanClose, () => Stream.empty),\n Stream.runForEach(onFrame),\n Effect.mapError(asAiError),\n // Set before `frames` ends, so a send after the last frame already fails.\n Effect.ensuring(Ref.set(ended, true)),\n // `Queue.end` on a clean close so the consumer drains what is queued;\n // a failure must not arrive looking like the end of the conversation.\n Effect.matchCauseEffect({\n onFailure: (cause) =>\n Cause.hasFails(cause) ? Queue.failCause(frames, cause) : Queue.end(frames),\n onSuccess: () => Queue.end(frames),\n }),\n Effect.forkScoped,\n )\n\n const send = (frame: string): Effect.Effect<void, AiError.AiError> =>\n Effect.flatMap(Ref.get(ended), (isEnded) =>\n isEnded ? Effect.fail(closed) : Effect.mapError(write(frame), asAiError),\n )\n\n return { send, frames }\n })\n"],"mappings":"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"}