@effect-uai/core 0.5.2 → 0.6.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 (123) hide show
  1. package/README.md +1 -1
  2. package/dist/{Items-BH8xUkoR.d.mts → Items-pXR5BAgW.d.mts} +12 -12
  3. package/dist/Items-pXR5BAgW.d.mts.map +1 -0
  4. package/dist/SandboxError-BvKypkJm.d.mts +158 -0
  5. package/dist/SandboxError-BvKypkJm.d.mts.map +1 -0
  6. package/dist/{Tool-87ViKCCO.d.mts → Tool-DP-AwKOf.d.mts} +11 -11
  7. package/dist/{Tool-87ViKCCO.d.mts.map → Tool-DP-AwKOf.d.mts.map} +1 -1
  8. package/dist/domain/Items.d.mts +2 -2
  9. package/dist/domain/Items.mjs +15 -15
  10. package/dist/domain/Items.mjs.map +1 -1
  11. package/dist/domain/Turn.d.mts +388 -2
  12. package/dist/domain/Turn.d.mts.map +1 -0
  13. package/dist/domain/Turn.mjs +13 -13
  14. package/dist/domain/Turn.mjs.map +1 -1
  15. package/dist/index.d.mts +11 -6
  16. package/dist/index.mjs +8 -3
  17. package/dist/language-model/LanguageModel.d.mts +5 -5
  18. package/dist/language-model/LanguageModel.d.mts.map +1 -1
  19. package/dist/language-model/LanguageModel.mjs.map +1 -1
  20. package/dist/loop/Loop.d.mts +37 -90
  21. package/dist/loop/Loop.d.mts.map +1 -1
  22. package/dist/loop/Loop.mjs +39 -93
  23. package/dist/loop/Loop.mjs.map +1 -1
  24. package/dist/loop/Loop.test.mjs +46 -54
  25. package/dist/loop/Loop.test.mjs.map +1 -1
  26. package/dist/sandbox/Memory.d.mts +53 -0
  27. package/dist/sandbox/Memory.d.mts.map +1 -0
  28. package/dist/sandbox/Memory.mjs +68 -0
  29. package/dist/sandbox/Memory.mjs.map +1 -0
  30. package/dist/sandbox/Sandbox.d.mts +717 -0
  31. package/dist/sandbox/Sandbox.d.mts.map +1 -0
  32. package/dist/sandbox/Sandbox.mjs +192 -0
  33. package/dist/sandbox/Sandbox.mjs.map +1 -0
  34. package/dist/sandbox/SandboxError.d.mts +2 -0
  35. package/dist/sandbox/SandboxError.mjs +89 -0
  36. package/dist/sandbox/SandboxError.mjs.map +1 -0
  37. package/dist/sandbox/SandboxImage.d.mts +28 -0
  38. package/dist/sandbox/SandboxImage.d.mts.map +1 -0
  39. package/dist/sandbox/SandboxImage.mjs +32 -0
  40. package/dist/sandbox/SandboxImage.mjs.map +1 -0
  41. package/dist/sandbox/SandboxNetwork.d.mts +34 -0
  42. package/dist/sandbox/SandboxNetwork.d.mts.map +1 -0
  43. package/dist/sandbox/SandboxNetwork.mjs +36 -0
  44. package/dist/sandbox/SandboxNetwork.mjs.map +1 -0
  45. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +98 -2
  46. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -1
  47. package/dist/speech-synthesizer/SpeechSynthesizer.mjs +32 -2
  48. package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
  49. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +56 -2
  50. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -1
  51. package/dist/testing/MockProvider.d.mts +3 -3
  52. package/dist/testing/MockProvider.d.mts.map +1 -1
  53. package/dist/testing/MockProvider.mjs.map +1 -1
  54. package/dist/testing/MockSandbox.d.mts +604 -0
  55. package/dist/testing/MockSandbox.d.mts.map +1 -0
  56. package/dist/testing/MockSandbox.mjs +160 -0
  57. package/dist/testing/MockSandbox.mjs.map +1 -0
  58. package/dist/testing/MockSpeechSynthesizer.d.mts +26 -11
  59. package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -1
  60. package/dist/testing/MockSpeechSynthesizer.mjs +75 -51
  61. package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -1
  62. package/dist/tool/{Resolvers.d.mts → Approval.d.mts} +27 -27
  63. package/dist/tool/Approval.d.mts.map +1 -0
  64. package/dist/tool/{Resolvers.mjs → Approval.mjs} +18 -18
  65. package/dist/tool/Approval.mjs.map +1 -0
  66. package/dist/tool/{Resolvers.test.mjs → Approval.test.mjs} +40 -40
  67. package/dist/tool/Approval.test.mjs.map +1 -0
  68. package/dist/tool/HistoryCheck.d.mts +6 -6
  69. package/dist/tool/HistoryCheck.d.mts.map +1 -1
  70. package/dist/tool/HistoryCheck.mjs +5 -5
  71. package/dist/tool/HistoryCheck.mjs.map +1 -1
  72. package/dist/tool/Tool.d.mts +2 -2
  73. package/dist/tool/Tool.mjs +4 -4
  74. package/dist/tool/Tool.mjs.map +1 -1
  75. package/dist/tool/ToolEvent.d.mts +19 -19
  76. package/dist/tool/ToolEvent.d.mts.map +1 -1
  77. package/dist/tool/ToolEvent.mjs +8 -8
  78. package/dist/tool/ToolEvent.mjs.map +1 -1
  79. package/dist/tool/{Outcome.d.mts → ToolResult.d.mts} +29 -29
  80. package/dist/tool/ToolResult.d.mts.map +1 -0
  81. package/dist/tool/ToolResult.mjs +61 -0
  82. package/dist/tool/ToolResult.mjs.map +1 -0
  83. package/dist/tool/Toolkit.d.mts +58 -26
  84. package/dist/tool/Toolkit.d.mts.map +1 -1
  85. package/dist/tool/Toolkit.mjs +62 -34
  86. package/dist/tool/Toolkit.mjs.map +1 -1
  87. package/dist/tool/Toolkit.test.mjs +13 -13
  88. package/dist/tool/Toolkit.test.mjs.map +1 -1
  89. package/package.json +31 -7
  90. package/src/domain/Items.ts +9 -9
  91. package/src/domain/Turn.ts +16 -16
  92. package/src/index.ts +7 -2
  93. package/src/language-model/LanguageModel.ts +3 -3
  94. package/src/loop/Loop.test.ts +93 -91
  95. package/src/loop/Loop.ts +72 -170
  96. package/src/sandbox/Memory.ts +128 -0
  97. package/src/sandbox/Sandbox.ts +639 -0
  98. package/src/sandbox/SandboxError.ts +140 -0
  99. package/src/sandbox/SandboxImage.ts +26 -0
  100. package/src/sandbox/SandboxNetwork.ts +34 -0
  101. package/src/speech-synthesizer/SpeechSynthesizer.test.ts +89 -0
  102. package/src/speech-synthesizer/SpeechSynthesizer.ts +124 -0
  103. package/src/testing/MockProvider.ts +21 -20
  104. package/src/testing/MockSandbox.ts +342 -0
  105. package/src/testing/MockSpeechSynthesizer.ts +137 -77
  106. package/src/tool/{Resolvers.test.ts → Approval.test.ts} +45 -54
  107. package/src/tool/{Resolvers.ts → Approval.ts} +25 -25
  108. package/src/tool/HistoryCheck.ts +10 -13
  109. package/src/tool/Tool.ts +13 -15
  110. package/src/tool/ToolEvent.ts +7 -7
  111. package/src/tool/{Outcome.ts → ToolResult.ts} +20 -20
  112. package/src/tool/Toolkit.test.ts +13 -13
  113. package/src/tool/Toolkit.ts +97 -73
  114. package/dist/Items-BH8xUkoR.d.mts.map +0 -1
  115. package/dist/Turn-0CwCAyVe.d.mts +0 -388
  116. package/dist/Turn-0CwCAyVe.d.mts.map +0 -1
  117. package/dist/tool/Outcome.d.mts.map +0 -1
  118. package/dist/tool/Outcome.mjs +0 -61
  119. package/dist/tool/Outcome.mjs.map +0 -1
  120. package/dist/tool/Resolvers.d.mts.map +0 -1
  121. package/dist/tool/Resolvers.mjs.map +0 -1
  122. package/dist/tool/Resolvers.test.mjs.map +0 -1
  123. /package/dist/tool/{Resolvers.test.d.mts → Approval.test.d.mts} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"LanguageModel.mjs","names":["Arr","AiError.IncompleteTurn"],"sources":["../../src/language-model/LanguageModel.ts"],"sourcesContent":["import { Array as Arr, Context, Effect, Option, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { Item } from \"../domain/Items.js\"\nimport type * as StructuredFormat from \"../structured-format/StructuredFormat.js\"\nimport type { ToolDescriptor } from \"../tool/Tool.js\"\nimport { isTurnComplete, type Turn, type TurnEvent } from \"../domain/Turn.js\"\n\n/**\n * Cross-provider request shape. Every call carries its own `history` and\n * `model` - models are not bound at layer construction. Anything specific\n * to a single provider (reasoning effort, prompt caching, store flags,\n * ...) lives in that provider's own request interface, which extends this.\n */\nexport type CommonRequest = {\n readonly history: ReadonlyArray<Item>\n /**\n * Model identifier. Each provider narrows this to its typed literal union,\n * so code that yields a typed provider tag gets autocompletion.\n */\n readonly model: string\n readonly tools?: ReadonlyArray<ToolDescriptor>\n readonly toolChoice?:\n | \"auto\"\n | \"required\"\n | \"none\"\n | { readonly type: \"function\"; readonly name: string }\n readonly temperature?: number\n readonly topP?: number\n readonly maxOutputTokens?: number\n /**\n * Schema-bound JSON output. The provider constrains the wire to match the\n * schema; pair with `Turn.toStructured` for runtime validation. Supported\n * across all current providers (OpenAI Responses json_schema, Anthropic\n * `output_config`, Gemini `responseJsonSchema`).\n */\n readonly structured?: StructuredFormat.StructuredFormat<unknown>\n}\n\nexport type LanguageModelService = {\n readonly streamTurn: (request: CommonRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /**\n * Drain a single turn and return the assembled `Turn` from the\n * terminal `TurnComplete` event. Fails with `IncompleteTurn` if the\n * stream ends without one.\n *\n * Most providers derive this from `streamTurn` via\n * {@link turnFromStream}; providers with a native non-streaming\n * endpoint may override with a cheaper direct call.\n */\n readonly turn: (request: CommonRequest) => Effect.Effect<Turn, AiError.AiError>\n}\n\nexport class LanguageModel extends Context.Service<LanguageModel, LanguageModelService>()(\n \"@betalyra/effect-uai/LanguageModel\",\n) {}\n\n/**\n * Stream the deltas of a single turn.\n */\nexport const streamTurn = (\n request: CommonRequest,\n): Stream.Stream<TurnEvent, AiError.AiError, LanguageModel> =>\n Stream.unwrap(Effect.map(LanguageModel.asEffect(), (m) => m.streamTurn(request)))\n\n/**\n * Drain a single turn and return the assembled `Turn`. Delegates to the\n * service's `turn` method — providers with a native complete endpoint\n * can override; the rest get the default streamTurn-drain via\n * {@link turnFromStream}.\n */\nexport const turn = (request: CommonRequest): Effect.Effect<Turn, AiError.AiError, LanguageModel> =>\n Effect.flatMap(LanguageModel.asEffect(), (m) => m.turn(request))\n\n/**\n * Build a `turn` implementation from a `streamTurn` implementation.\n * Providers without a native non-streaming endpoint use this to\n * populate the service's `turn` field. Generic over the request type so\n * provider-typed services (with their narrowed request) can reuse it.\n */\nexport const turnFromStream =\n <Req>(streamTurn: (request: Req) => Stream.Stream<TurnEvent, AiError.AiError>) =>\n (request: Req): Effect.Effect<Turn, AiError.AiError> =>\n streamTurn(request).pipe(\n Stream.runCollect,\n Effect.flatMap((events) =>\n Arr.findLast(events, isTurnComplete).pipe(\n Option.match({\n onNone: () => Effect.fail(new AiError.IncompleteTurn({})),\n onSome: (e) => Effect.succeed(e.turn),\n }),\n ),\n ),\n )\n"],"mappings":";;;;;;;;;;;AAoDA,IAAa,gBAAb,cAAmC,QAAQ,SAA8C,CACvF,qCACD,CAAC;;;;AAKF,MAAa,cACX,YAEA,OAAO,OAAO,OAAO,IAAI,cAAc,UAAU,GAAG,MAAM,EAAE,WAAW,QAAQ,CAAC,CAAC;;;;;;;AAQnF,MAAa,QAAQ,YACnB,OAAO,QAAQ,cAAc,UAAU,GAAG,MAAM,EAAE,KAAK,QAAQ,CAAC;;;;;;;AAQlE,MAAa,kBACL,gBACL,YACC,WAAW,QAAQ,CAAC,KAClB,OAAO,YACP,OAAO,SAAS,WACdA,MAAI,SAAS,QAAQ,eAAe,CAAC,KACnC,OAAO,MAAM;CACX,cAAc,OAAO,KAAK,IAAIC,eAAuB,EAAE,CAAC,CAAC;CACzD,SAAS,MAAM,OAAO,QAAQ,EAAE,KAAK;CACtC,CAAC,CACH,CACF,CACF"}
1
+ {"version":3,"file":"LanguageModel.mjs","names":["Arr","AiError.IncompleteTurn"],"sources":["../../src/language-model/LanguageModel.ts"],"sourcesContent":["import { Array as Arr, Context, Effect, Option, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { HistoryItem } from \"../domain/Items.js\"\nimport type * as StructuredFormat from \"../structured-format/StructuredFormat.js\"\nimport type { ToolDescriptor } from \"../tool/Tool.js\"\nimport { isTurnComplete, type Turn, type TurnEvent } from \"../domain/Turn.js\"\n\n/**\n * Cross-provider request shape. Every call carries its own `history` and\n * `model` - models are not bound at layer construction. Anything specific\n * to a single provider (reasoning effort, prompt caching, store flags,\n * ...) lives in that provider's own request interface, which extends this.\n */\nexport type CommonRequest = {\n readonly history: ReadonlyArray<HistoryItem>\n /**\n * Model identifier. Each provider narrows this to its typed literal union,\n * so code that yields a typed provider tag gets autocompletion.\n */\n readonly model: string\n readonly tools?: ReadonlyArray<ToolDescriptor>\n readonly toolChoice?:\n | \"auto\"\n | \"required\"\n | \"none\"\n | { readonly type: \"function\"; readonly name: string }\n readonly temperature?: number\n readonly topP?: number\n readonly maxOutputTokens?: number\n /**\n * Schema-bound JSON output. The provider constrains the wire to match the\n * schema; pair with `Turn.decodeStructured` for runtime validation. Supported\n * across all current providers (OpenAI Responses json_schema, Anthropic\n * `output_config`, Gemini `responseJsonSchema`).\n */\n readonly structured?: StructuredFormat.StructuredFormat<unknown>\n}\n\nexport type LanguageModelService = {\n readonly streamTurn: (request: CommonRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /**\n * Drain a single turn and return the assembled `Turn` from the\n * terminal `TurnComplete` event. Fails with `IncompleteTurn` if the\n * stream ends without one.\n *\n * Most providers derive this from `streamTurn` via\n * {@link turnFromStream}; providers with a native non-streaming\n * endpoint may override with a cheaper direct call.\n */\n readonly turn: (request: CommonRequest) => Effect.Effect<Turn, AiError.AiError>\n}\n\nexport class LanguageModel extends Context.Service<LanguageModel, LanguageModelService>()(\n \"@betalyra/effect-uai/LanguageModel\",\n) {}\n\n/**\n * Stream the deltas of a single turn.\n */\nexport const streamTurn = (\n request: CommonRequest,\n): Stream.Stream<TurnEvent, AiError.AiError, LanguageModel> =>\n Stream.unwrap(Effect.map(LanguageModel.asEffect(), (m) => m.streamTurn(request)))\n\n/**\n * Drain a single turn and return the assembled `Turn`. Delegates to the\n * service's `turn` method — providers with a native complete endpoint\n * can override; the rest get the default streamTurn-drain via\n * {@link turnFromStream}.\n */\nexport const turn = (request: CommonRequest): Effect.Effect<Turn, AiError.AiError, LanguageModel> =>\n Effect.flatMap(LanguageModel.asEffect(), (m) => m.turn(request))\n\n/**\n * Build a `turn` implementation from a `streamTurn` implementation.\n * Providers without a native non-streaming endpoint use this to\n * populate the service's `turn` field. Generic over the request type so\n * provider-typed services (with their narrowed request) can reuse it.\n */\nexport const turnFromStream =\n <Req>(streamTurn: (request: Req) => Stream.Stream<TurnEvent, AiError.AiError>) =>\n (request: Req): Effect.Effect<Turn, AiError.AiError> =>\n streamTurn(request).pipe(\n Stream.runCollect,\n Effect.flatMap((events) =>\n Arr.findLast(events, isTurnComplete).pipe(\n Option.match({\n onNone: () => Effect.fail(new AiError.IncompleteTurn({})),\n onSome: (e) => Effect.succeed(e.turn),\n }),\n ),\n ),\n )\n"],"mappings":";;;;;;;;;;;AAoDA,IAAa,gBAAb,cAAmC,QAAQ,SAA8C,CACvF,qCACD,CAAC;;;;AAKF,MAAa,cACX,YAEA,OAAO,OAAO,OAAO,IAAI,cAAc,UAAU,GAAG,MAAM,EAAE,WAAW,QAAQ,CAAC,CAAC;;;;;;;AAQnF,MAAa,QAAQ,YACnB,OAAO,QAAQ,cAAc,UAAU,GAAG,MAAM,EAAE,KAAK,QAAQ,CAAC;;;;;;;AAQlE,MAAa,kBACL,gBACL,YACC,WAAW,QAAQ,CAAC,KAClB,OAAO,YACP,OAAO,SAAS,WACdA,MAAI,SAAS,QAAQ,eAAe,CAAC,KACnC,OAAO,MAAM;CACX,cAAc,OAAO,KAAK,IAAIC,eAAuB,EAAE,CAAC,CAAC;CACzD,SAAS,MAAM,OAAO,QAAQ,EAAE,KAAK;CACtC,CAAC,CACH,CACF,CACF"}
@@ -1,26 +1,26 @@
1
1
  import { l as IncompleteTurn } from "../AiError-CAX_48RU.mjs";
2
- import { i as TurnEvent, r as Turn } from "../Turn-0CwCAyVe.mjs";
2
+ import { Turn, TurnEvent } from "../domain/Turn.mjs";
3
3
  import { Data, Effect, Stream, SubscriptionRef } from "effect";
4
4
 
5
5
  //#region src/loop/Loop.d.ts
6
6
  declare namespace Loop_d_exports {
7
- export { Event, loop, loopFrom, loopWithState, next, nextAfter, nextAfterFold, onTurnComplete, stop, stopAfter, stopEvent, stopWith, stopWithAfter, value };
7
+ export { Step, loop, loopOver, loopWithState, next, onTurnComplete, stop, value };
8
8
  }
9
9
  /**
10
10
  * The tagged union a body emits per pull. `Value` carries a payload that
11
11
  * flows downstream. `Next` ends the current iteration and continues with a
12
12
  * new state. `Stop` ends the loop entirely with no carried state.
13
- * `StopWith` also ends the loop but carries a final state that `loopFrom`
13
+ * `StopWith` also ends the loop but carries a final state that `loopOver`
14
14
  * will thread to the next input and `loopWithState` will write to its
15
15
  * `SubscriptionRef` before the loop ends. Plain `loop` has no next
16
16
  * iteration to apply it to and treats `StopWith` like `Stop`.
17
17
  *
18
- * `Stop` is intentionally `{}` so the bare `stopEvent` / `stop` helpers
19
- * don't constrain `S` from a body's stream type — every body has a `Stop`
18
+ * `Stop` is intentionally `{}` so the bare `stop()` helper doesn't
19
+ * constrain `S` from a body's stream type — every body has a `Stop`
20
20
  * variant in its union, and forcing `S` to flow through it would break
21
- * inference whenever the body never uses `next` / `stopWith`.
21
+ * inference whenever the body never uses `next` / `stop(state)`.
22
22
  */
23
- type Event<A, S> = Data.TaggedEnum<{
23
+ type Step<A, S> = Data.TaggedEnum<{
24
24
  Value: {
25
25
  readonly value: A;
26
26
  };
@@ -33,80 +33,27 @@ type Event<A, S> = Data.TaggedEnum<{
33
33
  };
34
34
  }>;
35
35
  /** Wrap a value so it flows through the loop to downstream consumers. */
36
- declare const value: <A>(a: A) => Event<A, never>;
37
- /** End the current iteration and continue with a new state. */
38
- declare const next: <S>(state: S) => Event<never, S>;
36
+ declare const value: <A>(a: A) => Step<A, never>;
39
37
  /**
40
- * The terminal `Stop` event with no carried state. Use `stop` (the Stream)
41
- * to end a loop body without communicating a final state.
38
+ * End the current iteration and continue with a new state. Emits a single
39
+ * terminal `Next` step as a one-element stream, so it slots directly into a
40
+ * loop body or after a run of values: `values.pipe(Stream.concat(next(s)))`.
42
41
  */
43
- declare const stopEvent: Event<never, never>;
42
+ declare const next: <S>(state: S) => Stream.Stream<Step<never, S>>;
44
43
  /**
45
- * Terminal event that ends the loop AND carries a final state. For
46
- * `loopFrom` this is the natural "this input is done, here's the state to
47
- * carry forward to the next input" signal — symmetric with `next(s)` but
48
- * ending the inner loop instead of continuing it. For `loopWithState` the
49
- * carried state is written to the `SubscriptionRef` before the loop ends.
44
+ * End the loop. Called with no argument it emits a bare `Stop`; called with
45
+ * a state it emits `StopWith(state)` — `loopOver` threads that final state
46
+ * to the next input and `loopWithState` writes it to its `SubscriptionRef`
47
+ * before the loop ends. Either way it returns a single-element stream, so
48
+ * return it directly from a loop body.
50
49
  */
51
- declare const stopWith: <S>(state: S) => Event<never, S>;
50
+ declare const stop: <S = never>(state?: S) => Stream.Stream<Step<never, S>>;
52
51
  /**
53
- * A single-element stream that ends the loop. Return this from a body when
54
- * there's nothing else to emit; equivalent to `stopAfter(Stream.empty)` but
55
- * named for the common case.
56
- */
57
- declare const stop: Stream.Stream<Event<never, never>>;
58
- /**
59
- * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the
60
- * iteration with `next(state)`. Common shape for "stream this turn's
61
- * deltas, then continue with updated history."
62
- *
63
- * Dual: data-first `nextAfter(stream, state)` and data-last
64
- * `stream.pipe(nextAfter(state))` both work.
65
- */
66
- declare const nextAfter: {
67
- <S>(state: S): <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, S>, E, R>;
68
- <S, A, E, R>(stream: Stream.Stream<A, E, R>, state: S): Stream.Stream<Event<A, S>, E, R>;
69
- };
70
- /**
71
- * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the
72
- * loop. Common shape for "stream this turn's deltas, then we're done."
73
- *
74
- * Unary on the stream — already pipe-compatible via `stream.pipe(stopAfter)`.
75
- */
76
- declare const stopAfter: <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, never>, E, R>;
77
- /**
78
- * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate with
79
- * `stopWith(state)`. The natural "emit final outputs, advance state, end
80
- * this input's inner loop" shape for `loopFrom`.
81
- *
82
- * Dual: data-first `stopWithAfter(stream, state)` and data-last
83
- * `stream.pipe(stopWithAfter(state))` both work.
84
- */
85
- declare const stopWithAfter: {
86
- <S>(state: S): <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, S>, E, R>;
87
- <S, A, E, R>(stream: Stream.Stream<A, E, R>, state: S): Stream.Stream<Event<A, S>, E, R>;
88
- };
89
- /**
90
- * General `nextAfter` variant: drain `stream` to the consumer, fold elements
91
- * into an accumulator, and at end-of-stream emit one `next(build(finalAcc))`.
92
- *
93
- * Subsumes `nextAfter` when state is constant (`reduce: (s, _) => s`,
94
- * `build: (s) => s`). Used by `Toolkit.continueWith` to collect tool
95
- * results and build next state without exposing a Ref to recipes.
96
- *
97
- * Dual: data-first `nextAfterFold(stream, initial, reduce, build)` and
98
- * data-last `stream.pipe(nextAfterFold(initial, reduce, build))` both work.
99
- */
100
- declare const nextAfterFold: {
101
- <A, B, S>(initial: B, reduce: (acc: B, a: A) => B, build: (b: B) => S): <E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, S>, E, R>;
102
- <A, B, S, E, R>(stream: Stream.Stream<A, E, R>, initial: B, reduce: (acc: B, a: A) => B, build: (b: B) => S): Stream.Stream<Event<A, S>, E, R>;
103
- };
104
- /**
105
- * Lift a provider's `Stream<TurnEvent>` into a loop body's `Stream<Event<TurnEvent | A, S>>`.
52
+ * Lift a provider's `Stream<TurnEvent>` into a loop body's `Stream<Step<TurnEvent | A, S>>`.
106
53
  * Each delta passes through as `value(delta)` (including the terminal
107
54
  * `TurnComplete`, so the consumer sees turn boundaries naturally). Once
108
55
  * the terminal arrives, `then(turn)` runs and its returned stream of loop
109
- * events (typically tool outputs followed by `next(state)` or `stop`) is
56
+ * steps (typically tool outputs followed by `next(state)` or `stop()`) is
110
57
  * concatenated.
111
58
  *
112
59
  * Pre-pipe transforms (`Stream.tap` / `Stream.map` / `Stream.filter`) on
@@ -120,14 +67,14 @@ declare const nextAfterFold: {
120
67
  * `deltas.pipe(onTurnComplete(then))` both work.
121
68
  */
122
69
  declare const onTurnComplete: {
123
- <S, A, E2 = never, R2 = never>(then: (turn: Turn) => Effect.Effect<Stream.Stream<Event<A, S>, E2, R2>, E2, R2>): <E, R>(deltas: Stream.Stream<TurnEvent, E, R>) => Stream.Stream<Event<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>;
124
- <S, A, E, R, E2 = never, R2 = never>(deltas: Stream.Stream<TurnEvent, E, R>, then: (turn: Turn) => Effect.Effect<Stream.Stream<Event<A, S>, E2, R2>, E2, R2>): Stream.Stream<Event<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>;
70
+ <S, A, E2 = never, R2 = never>(then: (turn: Turn) => Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, E2, R2>): <E, R>(deltas: Stream.Stream<TurnEvent, E, R>) => Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>;
71
+ <S, A, E, R, E2 = never, R2 = never>(deltas: Stream.Stream<TurnEvent, E, R>, then: (turn: Turn) => Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, E2, R2>): Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>;
125
72
  };
126
- type LoopBody<S, A, E, R> = (state: S) => Stream.Stream<Event<A, S>, E, R> | Effect.Effect<Stream.Stream<Event<A, S>, E, R>, E, R>;
73
+ type LoopBody<S, A, E, R> = (state: S) => Stream.Stream<Step<A, S>, E, R> | Effect.Effect<Stream.Stream<Step<A, S>, E, R>, E, R>;
127
74
  /**
128
75
  * Drive a state-threaded loop body. Each iteration runs `body(state)` to get
129
- * a `Stream<Event<A, S>>`; values flow downstream, `next(s)` continues with
130
- * a new state, `stop` ends the loop. See the file header for the full
76
+ * a `Stream<Step<A, S>>`; values flow downstream, `next(s)` continues with
77
+ * a new state, `stop()` ends the loop. See the file header for the full
131
78
  * pull-based execution model.
132
79
  *
133
80
  * Dual: data-first `loop(initial, body)` and data-last `loop(body)(initial)`
@@ -137,40 +84,40 @@ declare const loop: {
137
84
  <S, A, E, R>(body: LoopBody<S, A, E, R>): (initial: S) => Stream.Stream<A, E, R>;
138
85
  <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>): Stream.Stream<A, E, R>;
139
86
  };
140
- type LoopFromBody<S, I, A, E, R> = (state: S, input: I) => Stream.Stream<Event<A, S>, E, R> | Effect.Effect<Stream.Stream<Event<A, S>, E, R>, E, R>;
87
+ type LoopOverBody<S, I, A, E, R> = (state: S, input: I) => Stream.Stream<Step<A, S>, E, R> | Effect.Effect<Stream.Stream<Step<A, S>, E, R>, E, R>;
141
88
  /**
142
89
  * Input-driven sibling of `loop`. For each item pulled from the input
143
90
  * stream, runs an inner seed-driven `loop` whose body is
144
91
  * `(s) => body(s, item)`. State is threaded across input items.
145
92
  *
146
- * **Per-input semantics — the body emits standard `Event<A, S>`:**
93
+ * **Per-input semantics — the body emits standard `Step<A, S>`:**
147
94
  * - `value(a)`: emit `a` downstream
148
95
  * - `next(s)`: re-run the body with the SAME input and new state `s`
149
96
  * (multi-turn within one input — e.g. multiple model turns + tool
150
97
  * calls for one document)
151
- * - `stop`: end this input's inner loop, advance to the next input
98
+ * - `stop()`: end this input's inner loop, advance to the next input
152
99
  * (state preserved)
153
- * - body stream ending without a decision: same as `stop` (advance)
100
+ * - body stream ending without a decision: same as `stop()` (advance)
154
101
  *
155
102
  * **Outer termination:** the input stream ending. To halt programmatically
156
103
  * from within, end the input stream upstream (`Stream.takeWhile`, a
157
- * `SubscriptionRef` gate, etc.). Reserving `stop` for per-item
104
+ * `SubscriptionRef` gate, etc.). Reserving `stop()` for per-item
158
105
  * advancement is what makes the common "stream of documents, multi-turn
159
106
  * conversation per document" shape readable.
160
107
  *
161
- * Dual: data-first `loopFrom(input, initial, body)` and data-last
162
- * `input.pipe(loopFrom(initial, body))` both work.
108
+ * Dual: data-first `loopOver(input, initial, body)` and data-last
109
+ * `input.pipe(loopOver(initial, body))` both work.
163
110
  */
164
- declare const loopFrom: {
165
- <S, I, A, E, R>(initial: S, body: LoopFromBody<S, I, A, E, R>): <EI, RI>(input: Stream.Stream<I, EI, RI>) => Stream.Stream<A, E | EI, R | RI>;
166
- <S, I, A, E, R, EI, RI>(input: Stream.Stream<I, EI, RI>, initial: S, body: LoopFromBody<S, I, A, E, R>): Stream.Stream<A, E | EI, R | RI>;
111
+ declare const loopOver: {
112
+ <S, I, A, E, R>(initial: S, body: LoopOverBody<S, I, A, E, R>): <EI, RI>(input: Stream.Stream<I, EI, RI>) => Stream.Stream<A, E | EI, R | RI>;
113
+ <S, I, A, E, R, EI, RI>(input: Stream.Stream<I, EI, RI>, initial: S, body: LoopOverBody<S, I, A, E, R>): Stream.Stream<A, E | EI, R | RI>;
167
114
  };
168
115
  /**
169
116
  * Like `loop`, but exposes the current loop state as a `SubscriptionRef`
170
117
  * alongside the value stream.
171
118
  *
172
119
  * Allocates one `SubscriptionRef<S>` seeded with `initial`, then runs the
173
- * loop with a wrapped body that taps every `Next(s)` event into the ref
120
+ * loop with a wrapped body that taps every `Next(s)` step into the ref
174
121
  * before forwarding it. The caller decides how to consume both channels:
175
122
  *
176
123
  * - **Final state**: drain the stream, then `SubscriptionRef.get(state)`
@@ -190,5 +137,5 @@ declare const loopWithState: <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>
190
137
  readonly state: SubscriptionRef.SubscriptionRef<S>;
191
138
  }>;
192
139
  //#endregion
193
- export { Event, loop, loopFrom, loopWithState, next, nextAfter, nextAfterFold, onTurnComplete, stop, stopAfter, stopEvent, stopWith, stopWithAfter, Loop_d_exports as t, value };
140
+ export { Step, loop, loopOver, loopWithState, next, onTurnComplete, stop, Loop_d_exports as t, value };
194
141
  //# sourceMappingURL=Loop.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Loop.d.mts","names":[],"sources":["../../src/loop/Loop.ts"],"mappings":"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"}
1
+ {"version":3,"file":"Loop.d.mts","names":[],"sources":["../../src/loop/Loop.ts"],"mappings":"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"}
@@ -6,10 +6,10 @@ import { Array, Cause, Channel, Data, Effect, Exit, Function, Match, Option, Ref
6
6
  /**
7
7
  * Pull-based `loop` for state-threaded sub-streams.
8
8
  *
9
- * Each iteration runs a body that returns a `Stream<Event<A, S>>`. The body
9
+ * Each iteration runs a body that returns a `Stream<Step<A, S>>`. The body
10
10
  * emits values via `Loop.value(a)` and signals iteration control via
11
- * `Loop.next(state)` (continue with new state) or `Loop.stop` (terminate).
12
- * The loop unwraps `Value` events back to `A` for downstream consumers, so
11
+ * `Loop.next(state)` (continue with new state) or `Loop.stop()` (terminate).
12
+ * The loop unwraps `Value` steps back to `A` for downstream consumers, so
13
13
  * the resulting stream is a plain `Stream<A>`.
14
14
  *
15
15
  * The next body stream is only pulled when downstream pulls the outer
@@ -17,98 +17,44 @@ import { Array, Cause, Channel, Data, Effect, Exit, Function, Match, Option, Ref
17
17
  * scoped resources, and backpressure stay aligned with normal Stream
18
18
  * semantics.
19
19
  *
20
- * Convention: a `Next` or `Stop` event is the terminal element of a body's
20
+ * Convention: a `Next` or `Stop` step is the terminal element of a body's
21
21
  * iteration. Values emitted in the same chunk after one are discarded
22
- * (their producing side effects may already have run). Prefer the
23
- * `Loop.nextAfter` / `Loop.stopAfter` helpers to terminate cleanly.
22
+ * (their producing side effects may already have run). The `Loop.next` /
23
+ * `Loop.stop` helpers each emit a single terminal step, so concatenate
24
+ * your values before them (`values.pipe(Stream.concat(Loop.next(state)))`).
24
25
  */
25
26
  var Loop_exports = /* @__PURE__ */ __exportAll({
26
27
  loop: () => loop,
27
- loopFrom: () => loopFrom,
28
+ loopOver: () => loopOver,
28
29
  loopWithState: () => loopWithState,
29
30
  next: () => next,
30
- nextAfter: () => nextAfter,
31
- nextAfterFold: () => nextAfterFold,
32
31
  onTurnComplete: () => onTurnComplete,
33
32
  stop: () => stop,
34
- stopAfter: () => stopAfter,
35
- stopEvent: () => stopEvent,
36
- stopWith: () => stopWith,
37
- stopWithAfter: () => stopWithAfter,
38
33
  value: () => value
39
34
  });
40
- const Event = Data.taggedEnum();
35
+ const Step = Data.taggedEnum();
41
36
  /** Wrap a value so it flows through the loop to downstream consumers. */
42
- const value = (a) => Event.Value({ value: a });
43
- /** End the current iteration and continue with a new state. */
44
- const next = (state) => Event.Next({ state });
37
+ const value = (a) => Step.Value({ value: a });
45
38
  /**
46
- * The terminal `Stop` event with no carried state. Use `stop` (the Stream)
47
- * to end a loop body without communicating a final state.
39
+ * End the current iteration and continue with a new state. Emits a single
40
+ * terminal `Next` step as a one-element stream, so it slots directly into a
41
+ * loop body or after a run of values: `values.pipe(Stream.concat(next(s)))`.
48
42
  */
49
- const stopEvent = Event.Stop();
43
+ const next = (state) => Stream.succeed(Step.Next({ state }));
50
44
  /**
51
- * Terminal event that ends the loop AND carries a final state. For
52
- * `loopFrom` this is the natural "this input is done, here's the state to
53
- * carry forward to the next input" signal — symmetric with `next(s)` but
54
- * ending the inner loop instead of continuing it. For `loopWithState` the
55
- * carried state is written to the `SubscriptionRef` before the loop ends.
45
+ * End the loop. Called with no argument it emits a bare `Stop`; called with
46
+ * a state it emits `StopWith(state)` — `loopOver` threads that final state
47
+ * to the next input and `loopWithState` writes it to its `SubscriptionRef`
48
+ * before the loop ends. Either way it returns a single-element stream, so
49
+ * return it directly from a loop body.
56
50
  */
57
- const stopWith = (state) => Event.StopWith({ state });
51
+ const stop = (state) => Stream.succeed(state === void 0 ? Step.Stop() : Step.StopWith({ state }));
58
52
  /**
59
- * A single-element stream that ends the loop. Return this from a body when
60
- * there's nothing else to emit; equivalent to `stopAfter(Stream.empty)` but
61
- * named for the common case.
62
- */
63
- const stop = Stream.succeed(stopEvent);
64
- /**
65
- * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the
66
- * iteration with `next(state)`. Common shape for "stream this turn's
67
- * deltas, then continue with updated history."
68
- *
69
- * Dual: data-first `nextAfter(stream, state)` and data-last
70
- * `stream.pipe(nextAfter(state))` both work.
71
- */
72
- const nextAfter = Function.dual(2, (stream, state) => Stream.concat(Stream.map(stream, value), Stream.fromIterable([next(state)])));
73
- /**
74
- * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the
75
- * loop. Common shape for "stream this turn's deltas, then we're done."
76
- *
77
- * Unary on the stream — already pipe-compatible via `stream.pipe(stopAfter)`.
78
- */
79
- const stopAfter = (stream) => Stream.concat(Stream.map(stream, value), Stream.fromIterable([stopEvent]));
80
- /**
81
- * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate with
82
- * `stopWith(state)`. The natural "emit final outputs, advance state, end
83
- * this input's inner loop" shape for `loopFrom`.
84
- *
85
- * Dual: data-first `stopWithAfter(stream, state)` and data-last
86
- * `stream.pipe(stopWithAfter(state))` both work.
87
- */
88
- const stopWithAfter = Function.dual(2, (stream, state) => Stream.concat(Stream.map(stream, value), Stream.fromIterable([stopWith(state)])));
89
- /**
90
- * General `nextAfter` variant: drain `stream` to the consumer, fold elements
91
- * into an accumulator, and at end-of-stream emit one `next(build(finalAcc))`.
92
- *
93
- * Subsumes `nextAfter` when state is constant (`reduce: (s, _) => s`,
94
- * `build: (s) => s`). Used by `Toolkit.continueWith` to collect tool
95
- * results and build next state without exposing a Ref to recipes.
96
- *
97
- * Dual: data-first `nextAfterFold(stream, initial, reduce, build)` and
98
- * data-last `stream.pipe(nextAfterFold(initial, reduce, build))` both work.
99
- */
100
- const nextAfterFold = Function.dual(4, (stream, initial, reduce, build) => Stream.unwrap(Effect.gen(function* () {
101
- const ref = yield* Ref.make(initial);
102
- const tapped = stream.pipe(Stream.tap((a) => Ref.update(ref, (acc) => reduce(acc, a))), Stream.map(value));
103
- const continuation = Stream.fromEffect(Ref.get(ref).pipe(Effect.map((acc) => next(build(acc)))));
104
- return tapped.pipe(Stream.concat(continuation));
105
- })));
106
- /**
107
- * Lift a provider's `Stream<TurnEvent>` into a loop body's `Stream<Event<TurnEvent | A, S>>`.
53
+ * Lift a provider's `Stream<TurnEvent>` into a loop body's `Stream<Step<TurnEvent | A, S>>`.
108
54
  * Each delta passes through as `value(delta)` (including the terminal
109
55
  * `TurnComplete`, so the consumer sees turn boundaries naturally). Once
110
56
  * the terminal arrives, `then(turn)` runs and its returned stream of loop
111
- * events (typically tool outputs followed by `next(state)` or `stop`) is
57
+ * steps (typically tool outputs followed by `next(state)` or `stop()`) is
112
58
  * concatenated.
113
59
  *
114
60
  * Pre-pipe transforms (`Stream.tap` / `Stream.map` / `Stream.filter`) on
@@ -134,22 +80,22 @@ const onTurnComplete = Function.dual(2, (deltas, then) => Stream.unwrap(Effect.g
134
80
  const isNonEmpty = (array) => array.length > 0;
135
81
  const closeBody = (current, exit) => Scope.close(current.scope, exit);
136
82
  /**
137
- * Walk a chunk of `Event<A, S>` until a terminal `Next` or `Stop` is found.
83
+ * Walk a chunk of `Step<A, S>` until a terminal `Next` or `Stop` is found.
138
84
  * Returns the unwrapped values seen so far and (optionally) the terminal
139
- * event. Anything in the chunk after the terminal is discarded - its
85
+ * step. Anything in the chunk after the terminal is discarded - its
140
86
  * producing side effects may have run, but downstream never sees it.
141
87
  */
142
88
  const partitionChunk = (chunk) => {
143
- const [valueEvents, rest] = Array.span(chunk, (e) => e._tag === "Value");
89
+ const [valueSteps, rest] = Array.span(chunk, (e) => e._tag === "Value");
144
90
  return {
145
- values: valueEvents.map((e) => e.value),
91
+ values: valueSteps.map((e) => e.value),
146
92
  decision: Array.head(rest)
147
93
  };
148
94
  };
149
95
  /**
150
96
  * Drive a state-threaded loop body. Each iteration runs `body(state)` to get
151
- * a `Stream<Event<A, S>>`; values flow downstream, `next(s)` continues with
152
- * a new state, `stop` ends the loop. See the file header for the full
97
+ * a `Stream<Step<A, S>>`; values flow downstream, `next(s)` continues with
98
+ * a new state, `stop()` ends the loop. See the file header for the full
153
99
  * pull-based execution model.
154
100
  *
155
101
  * Dual: data-first `loop(initial, body)` and data-last `loop(body)(initial)`
@@ -198,31 +144,31 @@ const loop = Function.dual(2, (initial, body) => Stream.scoped(Stream.fromPull(E
198
144
  * stream, runs an inner seed-driven `loop` whose body is
199
145
  * `(s) => body(s, item)`. State is threaded across input items.
200
146
  *
201
- * **Per-input semantics — the body emits standard `Event<A, S>`:**
147
+ * **Per-input semantics — the body emits standard `Step<A, S>`:**
202
148
  * - `value(a)`: emit `a` downstream
203
149
  * - `next(s)`: re-run the body with the SAME input and new state `s`
204
150
  * (multi-turn within one input — e.g. multiple model turns + tool
205
151
  * calls for one document)
206
- * - `stop`: end this input's inner loop, advance to the next input
152
+ * - `stop()`: end this input's inner loop, advance to the next input
207
153
  * (state preserved)
208
- * - body stream ending without a decision: same as `stop` (advance)
154
+ * - body stream ending without a decision: same as `stop()` (advance)
209
155
  *
210
156
  * **Outer termination:** the input stream ending. To halt programmatically
211
157
  * from within, end the input stream upstream (`Stream.takeWhile`, a
212
- * `SubscriptionRef` gate, etc.). Reserving `stop` for per-item
158
+ * `SubscriptionRef` gate, etc.). Reserving `stop()` for per-item
213
159
  * advancement is what makes the common "stream of documents, multi-turn
214
160
  * conversation per document" shape readable.
215
161
  *
216
- * Dual: data-first `loopFrom(input, initial, body)` and data-last
217
- * `input.pipe(loopFrom(initial, body))` both work.
162
+ * Dual: data-first `loopOver(input, initial, body)` and data-last
163
+ * `input.pipe(loopOver(initial, body))` both work.
218
164
  */
219
- const loopFrom = Function.dual(3, (input, initial, body) => Stream.unwrap(Effect.gen(function* () {
165
+ const loopOver = Function.dual(3, (input, initial, body) => Stream.unwrap(Effect.gen(function* () {
220
166
  const stateRef = yield* Ref.make(initial);
221
167
  return input.pipe(Stream.flatMap((item) => Stream.unwrap(Effect.gen(function* () {
222
168
  const state = yield* Ref.get(stateRef);
223
169
  const wrappedBody = (s) => {
224
170
  const result = body(s, item);
225
- return (Effect.isEffect(result) ? Stream.unwrap(result) : result).pipe(Stream.tap((event) => Match.value(event).pipe(Match.tags({
171
+ return (Effect.isEffect(result) ? Stream.unwrap(result) : result).pipe(Stream.tap((step) => Match.value(step).pipe(Match.tags({
226
172
  Next: (e) => Ref.set(stateRef, e.state),
227
173
  StopWith: (e) => Ref.set(stateRef, e.state)
228
174
  }), Match.orElse(() => Effect.void))));
@@ -235,7 +181,7 @@ const loopFrom = Function.dual(3, (input, initial, body) => Stream.unwrap(Effect
235
181
  * alongside the value stream.
236
182
  *
237
183
  * Allocates one `SubscriptionRef<S>` seeded with `initial`, then runs the
238
- * loop with a wrapped body that taps every `Next(s)` event into the ref
184
+ * loop with a wrapped body that taps every `Next(s)` step into the ref
239
185
  * before forwarding it. The caller decides how to consume both channels:
240
186
  *
241
187
  * - **Final state**: drain the stream, then `SubscriptionRef.get(state)`
@@ -252,7 +198,7 @@ const loopFrom = Function.dual(3, (input, initial, body) => Stream.unwrap(Effect
252
198
  */
253
199
  const loopWithState = (initial, body) => Effect.gen(function* () {
254
200
  const stateRef = yield* SubscriptionRef.make(initial);
255
- const tap = (stream) => stream.pipe(Stream.tap((event) => Match.value(event).pipe(Match.tags({
201
+ const tap = (stream) => stream.pipe(Stream.tap((step) => Match.value(step).pipe(Match.tags({
256
202
  Next: (e) => SubscriptionRef.set(stateRef, e.state),
257
203
  StopWith: (e) => SubscriptionRef.set(stateRef, e.state)
258
204
  }), Match.orElse(() => Effect.void))));
@@ -266,6 +212,6 @@ const loopWithState = (initial, body) => Effect.gen(function* () {
266
212
  };
267
213
  });
268
214
  //#endregion
269
- export { loop, loopFrom, loopWithState, next, nextAfter, nextAfterFold, onTurnComplete, stop, stopAfter, stopEvent, stopWith, stopWithAfter, Loop_exports as t, value };
215
+ export { loop, loopOver, loopWithState, next, onTurnComplete, stop, Loop_exports as t, value };
270
216
 
271
217
  //# sourceMappingURL=Loop.mjs.map
@@ -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<Event<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` events 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` event 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). Prefer the\n * `Loop.nextAfter` / `Loop.stopAfter` helpers to terminate cleanly.\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 Result,\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// Event 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 `loopFrom`\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 `stopEvent` / `stop` helpers\n * don't 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` / `stopWith`.\n */\nexport type Event<A, S> = Data.TaggedEnum<{\n Value: { readonly value: A }\n Next: { readonly state: S }\n Stop: {}\n StopWith: { readonly state: S }\n}>\n\ninterface EventDef extends Data.TaggedEnum.WithGenerics<2> {\n readonly taggedEnum: Event<this[\"A\"], this[\"B\"]>\n}\n\nconst Event = Data.taggedEnum<EventDef>()\n\n/** Wrap a value so it flows through the loop to downstream consumers. */\nexport const value = <A>(a: A): Event<A, never> => Event.Value({ value: a })\n\n/** End the current iteration and continue with a new state. */\nexport const next = <S>(state: S): Event<never, S> => Event.Next({ state })\n\n/**\n * The terminal `Stop` event with no carried state. Use `stop` (the Stream)\n * to end a loop body without communicating a final state.\n */\nexport const stopEvent: Event<never, never> = Event.Stop()\n\n/**\n * Terminal event that ends the loop AND carries a final state. For\n * `loopFrom` this is the natural \"this input is done, here's the state to\n * carry forward to the next input\" signal — symmetric with `next(s)` but\n * ending the inner loop instead of continuing it. For `loopWithState` the\n * carried state is written to the `SubscriptionRef` before the loop ends.\n */\nexport const stopWith = <S>(state: S): Event<never, S> => Event.StopWith({ state })\n\n/**\n * A single-element stream that ends the loop. Return this from a body when\n * there's nothing else to emit; equivalent to `stopAfter(Stream.empty)` but\n * named for the common case.\n */\nexport const stop: Stream.Stream<Event<never, never>> = Stream.succeed(stopEvent)\n\n/**\n * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the\n * iteration with `next(state)`. Common shape for \"stream this turn's\n * deltas, then continue with updated history.\"\n *\n * Dual: data-first `nextAfter(stream, state)` and data-last\n * `stream.pipe(nextAfter(state))` both work.\n */\nexport const nextAfter: {\n <S>(state: S): <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, S>, E, R>\n <S, A, E, R>(stream: Stream.Stream<A, E, R>, state: S): Stream.Stream<Event<A, S>, E, R>\n} = Function.dual(\n 2,\n <S, A, E, R>(stream: Stream.Stream<A, E, R>, state: S): Stream.Stream<Event<A, S>, E, R> =>\n Stream.concat(Stream.map(stream, value), Stream.fromIterable([next(state)])),\n)\n\n/**\n * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the\n * loop. Common shape for \"stream this turn's deltas, then we're done.\"\n *\n * Unary on the stream — already pipe-compatible via `stream.pipe(stopAfter)`.\n */\nexport const stopAfter = <A, E, R>(\n stream: Stream.Stream<A, E, R>,\n): Stream.Stream<Event<A, never>, E, R> =>\n Stream.concat(Stream.map(stream, value), Stream.fromIterable([stopEvent]))\n\n/**\n * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate with\n * `stopWith(state)`. The natural \"emit final outputs, advance state, end\n * this input's inner loop\" shape for `loopFrom`.\n *\n * Dual: data-first `stopWithAfter(stream, state)` and data-last\n * `stream.pipe(stopWithAfter(state))` both work.\n */\nexport const stopWithAfter: {\n <S>(state: S): <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, S>, E, R>\n <S, A, E, R>(stream: Stream.Stream<A, E, R>, state: S): Stream.Stream<Event<A, S>, E, R>\n} = Function.dual(\n 2,\n <S, A, E, R>(stream: Stream.Stream<A, E, R>, state: S): Stream.Stream<Event<A, S>, E, R> =>\n Stream.concat(Stream.map(stream, value), Stream.fromIterable([stopWith(state)])),\n)\n\n/**\n * General `nextAfter` variant: drain `stream` to the consumer, fold elements\n * into an accumulator, and at end-of-stream emit one `next(build(finalAcc))`.\n *\n * Subsumes `nextAfter` when state is constant (`reduce: (s, _) => s`,\n * `build: (s) => s`). Used by `Toolkit.continueWith` to collect tool\n * results and build next state without exposing a Ref to recipes.\n *\n * Dual: data-first `nextAfterFold(stream, initial, reduce, build)` and\n * data-last `stream.pipe(nextAfterFold(initial, reduce, build))` both work.\n */\nexport const nextAfterFold: {\n <A, B, S>(\n initial: B,\n reduce: (acc: B, a: A) => B,\n build: (b: B) => S,\n ): <E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, S>, E, R>\n <A, B, S, E, R>(\n stream: Stream.Stream<A, E, R>,\n initial: B,\n reduce: (acc: B, a: A) => B,\n build: (b: B) => S,\n ): Stream.Stream<Event<A, S>, E, R>\n} = Function.dual(\n 4,\n <A, B, S, E, R>(\n stream: Stream.Stream<A, E, R>,\n initial: B,\n reduce: (acc: B, a: A) => B,\n build: (b: B) => S,\n ): Stream.Stream<Event<A, S>, E, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const ref = yield* Ref.make(initial)\n const tapped = stream.pipe(\n Stream.tap((a) => Ref.update(ref, (acc) => reduce(acc, a))),\n Stream.map(value),\n )\n const continuation = Stream.fromEffect(\n Ref.get(ref).pipe(Effect.map((acc) => next(build(acc)))),\n )\n return tapped.pipe(Stream.concat(continuation))\n }),\n ),\n)\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<Event<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 * events (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 * 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 */\nexport const onTurnComplete: {\n <S, A, E2 = never, R2 = never>(\n then: (turn: Turn) => Effect.Effect<Stream.Stream<Event<A, S>, E2, R2>, E2, R2>,\n ): <E, R>(\n deltas: Stream.Stream<TurnEvent, E, R>,\n ) => Stream.Stream<Event<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) => Effect.Effect<Stream.Stream<Event<A, S>, E2, R2>, E2, R2>,\n ): Stream.Stream<Event<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) => Effect.Effect<Stream.Stream<Event<A, S>, E2, R2>, E2, R2>,\n ): Stream.Stream<Event<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<Event<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 return yield* then(opt.value)\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<Event<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 `Event<A, S>` until a terminal `Next` or `Stop` is found.\n * Returns the unwrapped values seen so far and (optionally) the terminal\n * event. 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<Event<A, S>>,\n): {\n readonly values: ReadonlyArray<A>\n readonly decision: Option.Option<Event<A, S>>\n} => {\n const [valueEvents, rest] = Arr.span(\n chunk,\n (e): e is Event<A, S> & { _tag: \"Value\" } => e._tag === \"Value\",\n )\n return {\n values: valueEvents.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<Event<A, S>, E, R> | Effect.Effect<Stream.Stream<Event<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<Event<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(\n 2,\n <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<Event<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 `loopFrom`'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 event 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 return pull\n }),\n ),\n ),\n)\n\n// ---------------------------------------------------------------------------\n// loopFrom - stream-driven sibling of loop. One input item runs a full\n// multi-turn inner loop.\n// ---------------------------------------------------------------------------\n\ntype LoopFromBody<S, I, A, E, R> = (\n state: S,\n input: I,\n) => Stream.Stream<Event<A, S>, E, R> | Effect.Effect<Stream.Stream<Event<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 `Event<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 `loopFrom(input, initial, body)` and data-last\n * `input.pipe(loopFrom(initial, body))` both work.\n */\nexport const loopFrom: {\n <S, I, A, E, R>(\n initial: S,\n body: LoopFromBody<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: LoopFromBody<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: LoopFromBody<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 stopWith'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((event) =>\n Match.value(event).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)` event 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<Event<A, S>, E, R>): Stream.Stream<Event<A, S>, E, R> =>\n stream.pipe(\n Stream.tap((event) =>\n Match.value(event).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
+ {"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/** 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 * 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 */\nexport const onTurnComplete: {\n <S, A, E2 = never, R2 = never>(\n then: (turn: Turn) => Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, 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) => Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, 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) => Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, 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 return yield* then(opt.value)\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(\n 2,\n <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 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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"}