@effect-uai/core 0.1.0 → 0.2.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 (39) hide show
  1. package/README.md +1 -1
  2. package/dist/{Outcome-C2JYknCu.d.mts → Outcome-GiaNvt7i.d.mts} +2 -10
  3. package/dist/Outcome-GiaNvt7i.d.mts.map +1 -0
  4. package/dist/{ToolEvent-B2N10hr3.d.mts → ToolEvent-wTMgb2GO.d.mts} +2 -2
  5. package/dist/{ToolEvent-B2N10hr3.d.mts.map → ToolEvent-wTMgb2GO.d.mts.map} +1 -1
  6. package/dist/{Turn-rlTfuHaQ.d.mts → Turn-Bi83du4I.d.mts} +8 -19
  7. package/dist/{Turn-rlTfuHaQ.d.mts.map → Turn-Bi83du4I.d.mts.map} +1 -1
  8. package/dist/domain/Turn.d.mts +2 -2
  9. package/dist/domain/Turn.mjs +11 -7
  10. package/dist/domain/Turn.mjs.map +1 -1
  11. package/dist/index.d.mts +2 -2
  12. package/dist/language-model/LanguageModel.d.mts +1 -1
  13. package/dist/loop/Loop.d.mts +87 -2
  14. package/dist/loop/Loop.d.mts.map +1 -0
  15. package/dist/testing/MockProvider.d.mts +1 -1
  16. package/dist/tool/HistoryCheck.d.mts +1 -1
  17. package/dist/tool/Outcome.d.mts +2 -2
  18. package/dist/tool/Outcome.mjs +4 -10
  19. package/dist/tool/Outcome.mjs.map +1 -1
  20. package/dist/tool/Resolvers.d.mts +24 -22
  21. package/dist/tool/Resolvers.d.mts.map +1 -1
  22. package/dist/tool/Resolvers.mjs +45 -44
  23. package/dist/tool/Resolvers.mjs.map +1 -1
  24. package/dist/tool/ToolEvent.d.mts +1 -1
  25. package/dist/tool/ToolEvent.mjs.map +1 -1
  26. package/dist/tool/Toolkit.d.mts +9 -9
  27. package/dist/tool/Toolkit.d.mts.map +1 -1
  28. package/dist/tool/Toolkit.mjs +11 -10
  29. package/dist/tool/Toolkit.mjs.map +1 -1
  30. package/package.json +1 -1
  31. package/src/domain/Turn.ts +7 -17
  32. package/src/tool/Outcome.ts +3 -17
  33. package/src/tool/Resolvers.test.ts +39 -86
  34. package/src/tool/Resolvers.ts +74 -93
  35. package/src/tool/ToolEvent.ts +2 -2
  36. package/src/tool/Toolkit.ts +13 -39
  37. package/dist/Loop-CzSJo1h8.d.mts +0 -87
  38. package/dist/Loop-CzSJo1h8.d.mts.map +0 -1
  39. package/dist/Outcome-C2JYknCu.d.mts.map +0 -1
package/README.md CHANGED
@@ -35,7 +35,7 @@ ESM-only. Requires `effect@4.x` as a peer.
35
35
 
36
36
  Full docs: <https://effect-uai.betalyra.com>
37
37
 
38
- Start with the [Quickstart](https://effect-uai.betalyra.com/start/getting-started/)
38
+ Start with [One turn is a stream](https://effect-uai.betalyra.com/start/getting-started/)
39
39
  and then [Basic usage](https://effect-uai.betalyra.com/recipes/basic-usage/).
40
40
 
41
41
  ## License
@@ -19,14 +19,6 @@ declare const isValue: (r: ToolResult) => r is Extract<ToolResult, {
19
19
  declare const isFailure: (r: ToolResult) => r is Extract<ToolResult, {
20
20
  _tag: "Failure";
21
21
  }>;
22
- type ToolDecision = {
23
- readonly _tag: "Execute";
24
- } | {
25
- readonly _tag: "Reject";
26
- readonly result: ToolResult;
27
- };
28
- declare const execute: ToolDecision;
29
- declare const reject: (result: ToolResult) => ToolDecision;
30
22
  declare const rejected: (call: FunctionCall, kind: string, reason?: string) => ToolResult;
31
23
  /** Explicit user/policy rejection. */
32
24
  declare const denied: (call: FunctionCall, reason?: string) => ToolResult;
@@ -36,5 +28,5 @@ declare const cancelled: (call: FunctionCall, reason?: string) => ToolResult;
36
28
  declare const executionError: (call: FunctionCall, reason: string) => ToolResult;
37
29
  declare const toFunctionCallOutput: (r: ToolResult) => FunctionCallOutput;
38
30
  //#endregion
39
- export { execute as a, isValue as c, toFunctionCallOutput as d, denied as i, reject as l, ToolResult as n, executionError as o, cancelled as r, isFailure as s, ToolDecision as t, rejected as u };
40
- //# sourceMappingURL=Outcome-C2JYknCu.d.mts.map
31
+ export { isFailure as a, toFunctionCallOutput as c, executionError as i, cancelled as n, isValue as o, denied as r, rejected as s, ToolResult as t };
32
+ //# sourceMappingURL=Outcome-GiaNvt7i.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Outcome-GiaNvt7i.d.mts","names":[],"sources":["../src/tool/Outcome.ts"],"mappings":";;;KAqBY,UAAA;EAAA,SAEG,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,KAAA;AAAA;EAAA,SAGA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,IAAA;EAAA,SACA,MAAA;AAAA;AAAA,cAGF,OAAA,GAAW,CAAA,EAAG,UAAA,KAAa,CAAA,IAAK,OAAA,CAAQ,UAAA;EAAc,IAAA;AAAA;AAAA,cAGtD,SAAA,GAAa,CAAA,EAAG,UAAA,KAAa,CAAA,IAAK,OAAA,CAAQ,UAAA;EAAc,IAAA;AAAA;AAAA,cAOxD,QAAA,GACX,IAAA,EAAM,YAAA,EACN,IAAA,UACA,MAAA,cACC,UAAA;;cASU,MAAA,GAAU,IAAA,EAAM,YAAA,EAAc,MAAA,cAAkB,UAAA;;cAIhD,SAAA,GAAa,IAAA,EAAM,YAAA,EAAc,MAAA,cAAkB,UAAA;;cAInD,cAAA,GAAkB,IAAA,EAAM,YAAA,EAAc,MAAA,aAAiB,UAAA;AAAA,cAOvD,oBAAA,GAAwB,CAAA,EAAG,UAAA,KAAa,kBAAA"}
@@ -1,4 +1,4 @@
1
- import { n as ToolResult } from "./Outcome-C2JYknCu.mjs";
1
+ import { t as ToolResult } from "./Outcome-GiaNvt7i.mjs";
2
2
 
3
3
  //#region src/tool/ToolEvent.d.ts
4
4
  type ToolEvent = {
@@ -26,4 +26,4 @@ declare const isOutput: (e: ToolEvent) => e is Extract<ToolEvent, {
26
26
  }>;
27
27
  //#endregion
28
28
  export { isOutput as i, isApprovalRequested as n, isIntermediate as r, ToolEvent as t };
29
- //# sourceMappingURL=ToolEvent-B2N10hr3.d.mts.map
29
+ //# sourceMappingURL=ToolEvent-wTMgb2GO.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ToolEvent-B2N10hr3.d.mts","names":[],"sources":["../src/tool/ToolEvent.ts"],"mappings":";;;KAYY,SAAA;EAAA,SAEG,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,SAAA;AAAA;EAAA,SAGA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,IAAA;AAAA;EAAA,SAEA,IAAA;EAAA,SAAyB,MAAA,EAAQ,UAAA;AAAA;AAAA,cAEnC,mBAAA,GACX,CAAA,EAAG,SAAA,KACF,CAAA,IAAK,OAAA,CAAQ,SAAA;EAAa,IAAA;AAAA;AAAA,cAEhB,cAAA,GACX,CAAA,EAAG,SAAA,KACF,CAAA,IAAK,OAAA,CAAQ,SAAA;EAAa,IAAA;AAAA;AAAA,cAEhB,QAAA,GAAY,CAAA,EAAG,SAAA,KAAY,CAAA,IAAK,OAAA,CAAQ,SAAA;EAAa,IAAA;AAAA"}
1
+ {"version":3,"file":"ToolEvent-wTMgb2GO.d.mts","names":[],"sources":["../src/tool/ToolEvent.ts"],"mappings":";;;KAYY,SAAA;EAAA,SAEG,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,SAAA;AAAA;EAAA,SAGA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,IAAA;AAAA;EAAA,SAEA,IAAA;EAAA,SAAyB,MAAA,EAAQ,UAAA;AAAA;AAAA,cAEnC,mBAAA,GACX,CAAA,EAAG,SAAA,KACF,CAAA,IAAK,OAAA,CAAQ,SAAA;EAAa,IAAA;AAAA;AAAA,cAEhB,cAAA,GACX,CAAA,EAAG,SAAA,KACF,CAAA,IAAK,OAAA,CAAQ,SAAA;EAAa,IAAA;AAAA;AAAA,cAEhB,QAAA,GAAY,CAAA,EAAG,SAAA,KAAY,CAAA,IAAK,OAAA,CAAQ,SAAA;EAAa,IAAA;AAAA"}
@@ -6,7 +6,7 @@ import * as _$effect_Cause0 from "effect/Cause";
6
6
 
7
7
  //#region src/domain/Turn.d.ts
8
8
  declare namespace Turn_d_exports {
9
- export { Cursor, InteractionEvent, RefusalRejected, Turn, TurnEvent, assistantMessages, cursor, functionCalls, isTurnComplete, reasonings, textDeltas, toStructured };
9
+ export { InteractionEvent, RefusalRejected, Turn, TurnEvent, appendTurn, assistantMessages, functionCalls, isTurnComplete, reasonings, textDeltas, toStructured };
10
10
  }
11
11
  /**
12
12
  * The result of a single LLM generation. A turn produces zero or more items
@@ -160,24 +160,13 @@ declare const functionCalls: (turn: Turn) => ReadonlyArray<FunctionCall>;
160
160
  declare const reasonings: (turn: Turn) => ReadonlyArray<Reasoning>;
161
161
  declare const assistantMessages: (turn: Turn) => ReadonlyArray<Message>;
162
162
  /**
163
- * State stamped with the just-completed `Turn`. Recipes use this as the
164
- * intermediate value between "turn lands" and "compute next state": extend
165
- * `state.history` with the turn's items, and keep the assembled turn
166
- * around for stop-reason / usage / function-call inspection.
167
- *
168
- * Generic over the recipe's state shape - any record carrying a
169
- * `history: ReadonlyArray<Item>` field works.
170
- */
171
- type Cursor<S> = S & {
172
- readonly turn: Turn;
173
- };
174
- /**
175
- * Build a `Cursor<S>` from a state record and the just-completed turn.
176
- * Extends `state.history` with `turn.items` and stamps the turn.
163
+ * Append a completed turn and optional follow-up items to a state record's
164
+ * history. Recipes use this at the point where structured tool results are
165
+ * converted to model-facing `FunctionCallOutput`s.
177
166
  */
178
- declare const cursor: <S extends {
167
+ declare const appendTurn: <S extends {
179
168
  readonly history: ReadonlyArray<Item>;
180
- }>(state: S, turn: Turn) => Cursor<S>;
169
+ }>(state: S, turn: Turn, items?: ReadonlyArray<Item>) => S;
181
170
  /**
182
171
  * Project a `TurnEvent` stream onto its `text_delta` payloads. Other
183
172
  * variants are dropped. Composes with `Lines.lines` +
@@ -207,5 +196,5 @@ declare class RefusalRejected extends RefusalRejected_base<{
207
196
  */
208
197
  declare const toStructured: <A>(turn: Turn, format: StructuredFormat<A>) => Effect.Effect<A, RefusalRejected | JsonParseError | StructuredDecodeError>;
209
198
  //#endregion
210
- export { TurnEvent as a, cursor as c, reasonings as d, textDeltas as f, Turn as i, functionCalls as l, InteractionEvent as n, Turn_d_exports as o, toStructured as p, RefusalRejected as r, assistantMessages as s, Cursor as t, isTurnComplete as u };
211
- //# sourceMappingURL=Turn-rlTfuHaQ.d.mts.map
199
+ export { Turn_d_exports as a, functionCalls as c, textDeltas as d, toStructured as f, TurnEvent as i, isTurnComplete as l, RefusalRejected as n, appendTurn as o, Turn as r, assistantMessages as s, InteractionEvent as t, reasonings as u };
200
+ //# sourceMappingURL=Turn-Bi83du4I.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Turn-rlTfuHaQ.d.mts","names":[],"sources":["../src/domain/Turn.ts"],"mappings":";;;;;;;;;;;;;;;cAmBa,IAAA,EAAI,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAKL,IAAA,UAAc,IAAA,CAAK,IAAA;;;;;;;KAQnB,SAAA;EAAA,SACG,IAAA;EAAA,SAA6B,IAAA;AAAA;EAAA,SAE7B,IAAA;EAAA,SACA,IAAA;;;;;;;;WAQA,IAAA;AAAA;;;;;;;;;WASA,IAAA;EAAA,SAAgC,IAAA;AAAA;EAAA,SAChC,IAAA;EAAA,SAAkC,OAAA;EAAA,SAA0B,IAAA;AAAA;EAAA,SAC5D,IAAA;EAAA,SAAuC,OAAA;EAAA,SAA0B,KAAA;AAAA;;;;;;;;;;WASjE,IAAA;EAAA,SAA+B,KAAA,EAAO,KAAA;AAAA;EAAA,SACtC,IAAA;EAAA,SAAgC,IAAA,EAAM,IAAA;AAAA;;;;;;;KAQzC,gBAAA,GAAmB,SAAA,GAAY,kBAAA;AAAA,cAE9B,cAAA,GAAkB,CAAA,EAAG,SAAA,KAAY,CAAA,IAAK,OAAA,CAAQ,SAAA;EAAa,IAAA;AAAA;AAAA,cAG3D,aAAA,GAAiB,IAAA,EAAM,IAAA,KAAO,aAAA,CAAc,YAAA;AAAA,cAG5C,UAAA,GAAc,IAAA,EAAM,IAAA,KAAO,aAAA,CAAc,SAAA;AAAA,cAGzC,iBAAA,GAAqB,IAAA,EAAM,IAAA,KAAO,aAAA,CAAc,OAAA;;;;;;;;;;KAYjD,MAAA,MAAY,CAAA;EAAA,SAAe,IAAA,EAAM,IAAA;AAAA;;;;;cAMhC,MAAA;EAAA,SAA+B,OAAA,EAAS,aAAA,CAAc,IAAA;AAAA,GACjE,KAAA,EAAO,CAAA,EACP,IAAA,EAAM,IAAA,KACL,MAAA,CAAO,CAAA;;;;;;cAeG,UAAA,SACX,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA,MACjC,MAAA,CAAO,MAAA,SAAe,CAAA,EAAG,CAAA;AAAA,cAKzB,oBAAA;;;;;;;;cAWU,eAAA,SAAwB,oBAAA;EAAA,SAC1B,IAAA,EAAM,IAAA;AAAA;;;;;;;;;;;cAyBJ,YAAA,MACX,IAAA,EAAM,IAAA,EACN,MAAA,EAAQ,gBAAA,CAAkC,CAAA,MACzC,MAAA,CAAO,MAAA,CACR,CAAA,EACA,eAAA,GAAkB,cAAA,GAAkC,qBAAA"}
1
+ {"version":3,"file":"Turn-Bi83du4I.d.mts","names":[],"sources":["../src/domain/Turn.ts"],"mappings":";;;;;;;;;;;;;;;cAmBa,IAAA,EAAI,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAKL,IAAA,UAAc,IAAA,CAAK,IAAA;;;;;;;KAQnB,SAAA;EAAA,SACG,IAAA;EAAA,SAA6B,IAAA;AAAA;EAAA,SAE7B,IAAA;EAAA,SACA,IAAA;;;;;;;;WAQA,IAAA;AAAA;;;;;;;;;WASA,IAAA;EAAA,SAAgC,IAAA;AAAA;EAAA,SAChC,IAAA;EAAA,SAAkC,OAAA;EAAA,SAA0B,IAAA;AAAA;EAAA,SAC5D,IAAA;EAAA,SAAuC,OAAA;EAAA,SAA0B,KAAA;AAAA;;;;;;;;;;WASjE,IAAA;EAAA,SAA+B,KAAA,EAAO,KAAA;AAAA;EAAA,SACtC,IAAA;EAAA,SAAgC,IAAA,EAAM,IAAA;AAAA;;;;;;;KAQzC,gBAAA,GAAmB,SAAA,GAAY,kBAAA;AAAA,cAE9B,cAAA,GAAkB,CAAA,EAAG,SAAA,KAAY,CAAA,IAAK,OAAA,CAAQ,SAAA;EAAa,IAAA;AAAA;AAAA,cAG3D,aAAA,GAAiB,IAAA,EAAM,IAAA,KAAO,aAAA,CAAc,YAAA;AAAA,cAG5C,UAAA,GAAc,IAAA,EAAM,IAAA,KAAO,aAAA,CAAc,SAAA;AAAA,cAGzC,iBAAA,GAAqB,IAAA,EAAM,IAAA,KAAO,aAAA,CAAc,OAAA;;;;;;cAQhD,UAAA;EAAA,SAAmC,OAAA,EAAS,aAAA,CAAc,IAAA;AAAA,GACrE,KAAA,EAAO,CAAA,EACP,IAAA,EAAM,IAAA,EACN,KAAA,GAAO,aAAA,CAAc,IAAA,MACpB,CAAA;;;;;;cAcU,UAAA,SACX,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA,MACjC,MAAA,CAAO,MAAA,SAAe,CAAA,EAAG,CAAA;AAAA,cAKzB,oBAAA;;;;;;;;cAWU,eAAA,SAAwB,oBAAA;EAAA,SAC1B,IAAA,EAAM,IAAA;AAAA;;;;;;;;;;;cAyBJ,YAAA,MACX,IAAA,EAAM,IAAA,EACN,MAAA,EAAQ,gBAAA,CAAkC,CAAA,MACzC,MAAA,CAAO,MAAA,CACR,CAAA,EACA,eAAA,GAAkB,cAAA,GAAkC,qBAAA"}
@@ -1,2 +1,2 @@
1
- import { a as TurnEvent, c as cursor, d as reasonings, f as textDeltas, i as Turn, l as functionCalls, n as InteractionEvent, p as toStructured, r as RefusalRejected, s as assistantMessages, t as Cursor, u as isTurnComplete } from "../Turn-rlTfuHaQ.mjs";
2
- export { Cursor, InteractionEvent, RefusalRejected, Turn, TurnEvent, assistantMessages, cursor, functionCalls, isTurnComplete, reasonings, textDeltas, toStructured };
1
+ import { c as functionCalls, d as textDeltas, f as toStructured, i as TurnEvent, l as isTurnComplete, n as RefusalRejected, o as appendTurn, r as Turn, s as assistantMessages, t as InteractionEvent, u as reasonings } from "../Turn-Bi83du4I.mjs";
2
+ export { InteractionEvent, RefusalRejected, Turn, TurnEvent, appendTurn, assistantMessages, functionCalls, isTurnComplete, reasonings, textDeltas, toStructured };
@@ -6,8 +6,8 @@ import { Data, Effect, Result, Schema, Stream, pipe } from "effect";
6
6
  var Turn_exports = /* @__PURE__ */ __exportAll({
7
7
  RefusalRejected: () => RefusalRejected,
8
8
  Turn: () => Turn,
9
+ appendTurn: () => appendTurn,
9
10
  assistantMessages: () => assistantMessages,
10
- cursor: () => cursor,
11
11
  functionCalls: () => functionCalls,
12
12
  isTurnComplete: () => isTurnComplete,
13
13
  reasonings: () => reasonings,
@@ -29,13 +29,17 @@ const functionCalls = (turn) => turn.items.filter((i) => i.type === "function_ca
29
29
  const reasonings = (turn) => turn.items.filter((i) => i.type === "reasoning");
30
30
  const assistantMessages = (turn) => turn.items.filter((i) => i.type === "message" && i.role === "assistant");
31
31
  /**
32
- * Build a `Cursor<S>` from a state record and the just-completed turn.
33
- * Extends `state.history` with `turn.items` and stamps the turn.
32
+ * Append a completed turn and optional follow-up items to a state record's
33
+ * history. Recipes use this at the point where structured tool results are
34
+ * converted to model-facing `FunctionCallOutput`s.
34
35
  */
35
- const cursor = (state, turn) => ({
36
+ const appendTurn = (state, turn, items = []) => ({
36
37
  ...state,
37
- history: [...state.history, ...turn.items],
38
- turn
38
+ history: [
39
+ ...state.history,
40
+ ...turn.items,
41
+ ...items
42
+ ]
39
43
  });
40
44
  /**
41
45
  * Project a `TurnEvent` stream onto its `text_delta` payloads. Other
@@ -77,6 +81,6 @@ const lastAssistantContent = (turn) => {
77
81
  */
78
82
  const toStructured = (turn, format) => pipe(lastAssistantContent(turn), ({ text, refused }) => refused ? Effect.fail(new RefusalRejected({ turn })) : parseJson(format)(text));
79
83
  //#endregion
80
- export { RefusalRejected, Turn, assistantMessages, cursor, functionCalls, isTurnComplete, reasonings, Turn_exports as t, textDeltas, toStructured };
84
+ export { RefusalRejected, Turn, appendTurn, assistantMessages, functionCalls, isTurnComplete, reasonings, Turn_exports as t, textDeltas, toStructured };
81
85
 
82
86
  //# sourceMappingURL=Turn.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Turn.mjs","names":["StructuredFormat.parseJson"],"sources":["../../src/domain/Turn.ts"],"sourcesContent":["import { Data, Effect, Result, Schema, Stream, pipe } from \"effect\"\nimport * as StructuredFormat from \"../structured-format/StructuredFormat.js\"\nimport {\n FunctionCall,\n FunctionCallOutput,\n Item,\n isOutputText,\n isRefusal,\n Message,\n Reasoning,\n StopReason,\n Usage,\n} from \"./Items.js\"\n\n/**\n * The result of a single LLM generation. A turn produces zero or more items\n * (typically one assistant message and zero or more function_call items)\n * and reports usage + a stop reason.\n */\nexport const Turn = Schema.Struct({\n items: Schema.Array(Item),\n usage: Usage,\n stop_reason: StopReason,\n})\nexport type Turn = typeof Turn.Type\n\n/**\n * Canonical events emitted while a single turn is being generated. Most\n * variants are streaming deltas (text, reasoning, tool-call args); the\n * terminal `turn_complete` carries the assembled `Turn`. Lifecycle members\n * aren't deltas, hence the union name.\n */\nexport type TurnEvent =\n | { readonly type: \"text_delta\"; readonly text: string }\n | {\n readonly type: \"reasoning_delta\"\n readonly text: string\n /**\n * `trace` is the model's raw chain-of-thought; `summary` is a\n * model-written summary intended for display. OpenAI Responses emits\n * both as separate wire events; Anthropic and Gemini only emit\n * `trace`. Consumers who just want any reasoning text match once;\n * those who want only summaries filter `kind === \"summary\"`.\n */\n readonly kind: \"trace\" | \"summary\"\n }\n /**\n * The model declined to answer. `text` is the (streamed) explanation.\n * Distinct from the failure channel: a refusal is normal model output and\n * the stream still completes with `turn_complete`. OpenAI Responses emits\n * this; Anthropic surfaces refusals via `stop_reason`, and Gemini collapses\n * them into `finishReason: SAFETY` - both go without a `refusal_delta`.\n */\n | { readonly type: \"refusal_delta\"; readonly text: string }\n | { readonly type: \"tool_call_start\"; readonly call_id: string; readonly name: string }\n | { readonly type: \"tool_call_args_delta\"; readonly call_id: string; readonly delta: string }\n /**\n * Mid-stream cumulative usage. Carries the full `Usage` (including cache\n * token fields when the provider surfaces them) so consumers can drive\n * live budget / cost tracking without waiting for `turn_complete`.\n * Anthropic emits this on `message_start` and `message_delta`; other\n * providers may not emit any `usage_update` and only deliver usage via\n * `turn_complete.turn.usage`.\n */\n | { readonly type: \"usage_update\"; readonly usage: Usage }\n | { readonly type: \"turn_complete\"; readonly turn: Turn }\n\n/**\n * What flows out of an agent loop body to its consumer per turn: every\n * `TurnEvent` the provider emits (including the terminal `turn_complete`\n * carrying the assembled `Turn`), plus the output of any tool the loop ran.\n * Both variants carry a `type` discriminator.\n */\nexport type InteractionEvent = TurnEvent | FunctionCallOutput\n\nexport const isTurnComplete = (d: TurnEvent): d is Extract<TurnEvent, { type: \"turn_complete\" }> =>\n d.type === \"turn_complete\"\n\nexport const functionCalls = (turn: Turn): ReadonlyArray<FunctionCall> =>\n turn.items.filter((i): i is FunctionCall => i.type === \"function_call\")\n\nexport const reasonings = (turn: Turn): ReadonlyArray<Reasoning> =>\n turn.items.filter((i): i is Reasoning => i.type === \"reasoning\")\n\nexport const assistantMessages = (turn: Turn): ReadonlyArray<Message> =>\n turn.items.filter((i): i is Message => i.type === \"message\" && i.role === \"assistant\")\n\n/**\n * State stamped with the just-completed `Turn`. Recipes use this as the\n * intermediate value between \"turn lands\" and \"compute next state\": extend\n * `state.history` with the turn's items, and keep the assembled turn\n * around for stop-reason / usage / function-call inspection.\n *\n * Generic over the recipe's state shape - any record carrying a\n * `history: ReadonlyArray<Item>` field works.\n */\nexport type Cursor<S> = S & { readonly turn: Turn }\n\n/**\n * Build a `Cursor<S>` from a state record and the just-completed turn.\n * Extends `state.history` with `turn.items` and stamps the turn.\n */\nexport const cursor = <S extends { readonly history: ReadonlyArray<Item> }>(\n state: S,\n turn: Turn,\n): Cursor<S> => ({\n ...state,\n history: [...state.history, ...turn.items],\n turn,\n})\n\n// ---------------------------------------------------------------------------\n// Stream operators\n// ---------------------------------------------------------------------------\n\n/**\n * Project a `TurnEvent` stream onto its `text_delta` payloads. Other\n * variants are dropped. Composes with `Lines.lines` +\n * `decodeJsonLines` for prompted-JSONL streaming.\n */\nexport const textDeltas = <E, R>(\n self: Stream.Stream<TurnEvent, E, R>,\n): Stream.Stream<string, E, R> =>\n self.pipe(\n Stream.filterMap((ev) =>\n ev.type === \"text_delta\" ? Result.succeed(ev.text) : Result.failVoid,\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Structured-output integration\n// ---------------------------------------------------------------------------\n\n/**\n * The assistant message on the just-completed turn was a refusal block,\n * not an `output_text` payload. Returned by `toStructured` to short-circuit\n * decoding before `JSON.parse` / schema validation runs.\n */\nexport class RefusalRejected extends Data.TaggedError(\"RefusalRejected\")<{\n readonly turn: Turn\n}> {}\n\nconst lastAssistantContent = (turn: Turn): { readonly text: string; readonly refused: boolean } => {\n const assistants = assistantMessages(turn)\n const last = assistants[assistants.length - 1]\n if (last === undefined) return { text: \"\", refused: false }\n if (last.content.some(isRefusal)) return { text: \"\", refused: true }\n const text = last.content\n .filter(isOutputText)\n .map((b) => b.text)\n .join(\"\")\n return { text, refused: false }\n}\n\n/**\n * Validate a completed `Turn` against a `StructuredFormat`. Concatenates\n * `output_text` blocks on the last assistant message, then runs\n * `JSON.parse` + the format's schema validation.\n *\n * Three failure modes:\n * - `RefusalRejected` — the assistant emitted a refusal block.\n * - `JsonParseError` — the assembled text wasn't valid JSON.\n * - `StructuredDecodeError` — the JSON didn't match the schema.\n */\nexport const toStructured = <A>(\n turn: Turn,\n format: StructuredFormat.StructuredFormat<A>,\n): Effect.Effect<\n A,\n RefusalRejected | StructuredFormat.JsonParseError | StructuredFormat.StructuredDecodeError\n> =>\n pipe(lastAssistantContent(turn), ({ text, refused }) =>\n refused ? Effect.fail(new RefusalRejected({ turn })) : StructuredFormat.parseJson(format)(text),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAmBA,MAAa,OAAO,OAAO,OAAO;CAChC,OAAO,OAAO,MAAM,KAAK;CACzB,OAAO;CACP,aAAa;CACd,CAAC;AAoDF,MAAa,kBAAkB,MAC7B,EAAE,SAAS;AAEb,MAAa,iBAAiB,SAC5B,KAAK,MAAM,QAAQ,MAAyB,EAAE,SAAS,gBAAgB;AAEzE,MAAa,cAAc,SACzB,KAAK,MAAM,QAAQ,MAAsB,EAAE,SAAS,YAAY;AAElE,MAAa,qBAAqB,SAChC,KAAK,MAAM,QAAQ,MAAoB,EAAE,SAAS,aAAa,EAAE,SAAS,YAAY;;;;;AAiBxF,MAAa,UACX,OACA,UACe;CACf,GAAG;CACH,SAAS,CAAC,GAAG,MAAM,SAAS,GAAG,KAAK,MAAM;CAC1C;CACD;;;;;;AAWD,MAAa,cACX,SAEA,KAAK,KACH,OAAO,WAAW,OAChB,GAAG,SAAS,eAAe,OAAO,QAAQ,GAAG,KAAK,GAAG,OAAO,SAC7D,CACF;;;;;;AAWH,IAAa,kBAAb,cAAqC,KAAK,YAAY,kBAAkB,CAErE;AAEH,MAAM,wBAAwB,SAAqE;CACjG,MAAM,aAAa,kBAAkB,KAAK;CAC1C,MAAM,OAAO,WAAW,WAAW,SAAS;AAC5C,KAAI,SAAS,KAAA,EAAW,QAAO;EAAE,MAAM;EAAI,SAAS;EAAO;AAC3D,KAAI,KAAK,QAAQ,KAAK,UAAU,CAAE,QAAO;EAAE,MAAM;EAAI,SAAS;EAAM;AAKpE,QAAO;EAAE,MAJI,KAAK,QACf,OAAO,aAAa,CACpB,KAAK,MAAM,EAAE,KAAK,CAClB,KAAK,GACK;EAAE,SAAS;EAAO;;;;;;;;;;;;AAajC,MAAa,gBACX,MACA,WAKA,KAAK,qBAAqB,KAAK,GAAG,EAAE,MAAM,cACxC,UAAU,OAAO,KAAK,IAAI,gBAAgB,EAAE,MAAM,CAAC,CAAC,GAAGA,UAA2B,OAAO,CAAC,KAAK,CAChG"}
1
+ {"version":3,"file":"Turn.mjs","names":["StructuredFormat.parseJson"],"sources":["../../src/domain/Turn.ts"],"sourcesContent":["import { Data, Effect, Result, Schema, Stream, pipe } from \"effect\"\nimport * as StructuredFormat from \"../structured-format/StructuredFormat.js\"\nimport {\n FunctionCall,\n FunctionCallOutput,\n Item,\n isOutputText,\n isRefusal,\n Message,\n Reasoning,\n StopReason,\n Usage,\n} from \"./Items.js\"\n\n/**\n * The result of a single LLM generation. A turn produces zero or more items\n * (typically one assistant message and zero or more function_call items)\n * and reports usage + a stop reason.\n */\nexport const Turn = Schema.Struct({\n items: Schema.Array(Item),\n usage: Usage,\n stop_reason: StopReason,\n})\nexport type Turn = typeof Turn.Type\n\n/**\n * Canonical events emitted while a single turn is being generated. Most\n * variants are streaming deltas (text, reasoning, tool-call args); the\n * terminal `turn_complete` carries the assembled `Turn`. Lifecycle members\n * aren't deltas, hence the union name.\n */\nexport type TurnEvent =\n | { readonly type: \"text_delta\"; readonly text: string }\n | {\n readonly type: \"reasoning_delta\"\n readonly text: string\n /**\n * `trace` is the model's raw chain-of-thought; `summary` is a\n * model-written summary intended for display. OpenAI Responses emits\n * both as separate wire events; Anthropic and Gemini only emit\n * `trace`. Consumers who just want any reasoning text match once;\n * those who want only summaries filter `kind === \"summary\"`.\n */\n readonly kind: \"trace\" | \"summary\"\n }\n /**\n * The model declined to answer. `text` is the (streamed) explanation.\n * Distinct from the failure channel: a refusal is normal model output and\n * the stream still completes with `turn_complete`. OpenAI Responses emits\n * this; Anthropic surfaces refusals via `stop_reason`, and Gemini collapses\n * them into `finishReason: SAFETY` - both go without a `refusal_delta`.\n */\n | { readonly type: \"refusal_delta\"; readonly text: string }\n | { readonly type: \"tool_call_start\"; readonly call_id: string; readonly name: string }\n | { readonly type: \"tool_call_args_delta\"; readonly call_id: string; readonly delta: string }\n /**\n * Mid-stream cumulative usage. Carries the full `Usage` (including cache\n * token fields when the provider surfaces them) so consumers can drive\n * live budget / cost tracking without waiting for `turn_complete`.\n * Anthropic emits this on `message_start` and `message_delta`; other\n * providers may not emit any `usage_update` and only deliver usage via\n * `turn_complete.turn.usage`.\n */\n | { readonly type: \"usage_update\"; readonly usage: Usage }\n | { readonly type: \"turn_complete\"; readonly turn: Turn }\n\n/**\n * What flows out of an agent loop body to its consumer per turn: every\n * `TurnEvent` the provider emits (including the terminal `turn_complete`\n * carrying the assembled `Turn`), plus the output of any tool the loop ran.\n * Both variants carry a `type` discriminator.\n */\nexport type InteractionEvent = TurnEvent | FunctionCallOutput\n\nexport const isTurnComplete = (d: TurnEvent): d is Extract<TurnEvent, { type: \"turn_complete\" }> =>\n d.type === \"turn_complete\"\n\nexport const functionCalls = (turn: Turn): ReadonlyArray<FunctionCall> =>\n turn.items.filter((i): i is FunctionCall => i.type === \"function_call\")\n\nexport const reasonings = (turn: Turn): ReadonlyArray<Reasoning> =>\n turn.items.filter((i): i is Reasoning => i.type === \"reasoning\")\n\nexport const assistantMessages = (turn: Turn): ReadonlyArray<Message> =>\n turn.items.filter((i): i is Message => i.type === \"message\" && i.role === \"assistant\")\n\n/**\n * Append a completed turn and optional follow-up items to a state record's\n * history. Recipes use this at the point where structured tool results are\n * converted to model-facing `FunctionCallOutput`s.\n */\nexport const appendTurn = <S extends { readonly history: ReadonlyArray<Item> }>(\n state: S,\n turn: Turn,\n items: ReadonlyArray<Item> = [],\n): S => ({\n ...state,\n history: [...state.history, ...turn.items, ...items],\n})\n\n// ---------------------------------------------------------------------------\n// Stream operators\n// ---------------------------------------------------------------------------\n\n/**\n * Project a `TurnEvent` stream onto its `text_delta` payloads. Other\n * variants are dropped. Composes with `Lines.lines` +\n * `decodeJsonLines` for prompted-JSONL streaming.\n */\nexport const textDeltas = <E, R>(\n self: Stream.Stream<TurnEvent, E, R>,\n): Stream.Stream<string, E, R> =>\n self.pipe(\n Stream.filterMap((ev) =>\n ev.type === \"text_delta\" ? Result.succeed(ev.text) : Result.failVoid,\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Structured-output integration\n// ---------------------------------------------------------------------------\n\n/**\n * The assistant message on the just-completed turn was a refusal block,\n * not an `output_text` payload. Returned by `toStructured` to short-circuit\n * decoding before `JSON.parse` / schema validation runs.\n */\nexport class RefusalRejected extends Data.TaggedError(\"RefusalRejected\")<{\n readonly turn: Turn\n}> {}\n\nconst lastAssistantContent = (turn: Turn): { readonly text: string; readonly refused: boolean } => {\n const assistants = assistantMessages(turn)\n const last = assistants[assistants.length - 1]\n if (last === undefined) return { text: \"\", refused: false }\n if (last.content.some(isRefusal)) return { text: \"\", refused: true }\n const text = last.content\n .filter(isOutputText)\n .map((b) => b.text)\n .join(\"\")\n return { text, refused: false }\n}\n\n/**\n * Validate a completed `Turn` against a `StructuredFormat`. Concatenates\n * `output_text` blocks on the last assistant message, then runs\n * `JSON.parse` + the format's schema validation.\n *\n * Three failure modes:\n * - `RefusalRejected` — the assistant emitted a refusal block.\n * - `JsonParseError` — the assembled text wasn't valid JSON.\n * - `StructuredDecodeError` — the JSON didn't match the schema.\n */\nexport const toStructured = <A>(\n turn: Turn,\n format: StructuredFormat.StructuredFormat<A>,\n): Effect.Effect<\n A,\n RefusalRejected | StructuredFormat.JsonParseError | StructuredFormat.StructuredDecodeError\n> =>\n pipe(lastAssistantContent(turn), ({ text, refused }) =>\n refused ? Effect.fail(new RefusalRejected({ turn })) : StructuredFormat.parseJson(format)(text),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAmBA,MAAa,OAAO,OAAO,OAAO;CAChC,OAAO,OAAO,MAAM,KAAK;CACzB,OAAO;CACP,aAAa;CACd,CAAC;AAoDF,MAAa,kBAAkB,MAC7B,EAAE,SAAS;AAEb,MAAa,iBAAiB,SAC5B,KAAK,MAAM,QAAQ,MAAyB,EAAE,SAAS,gBAAgB;AAEzE,MAAa,cAAc,SACzB,KAAK,MAAM,QAAQ,MAAsB,EAAE,SAAS,YAAY;AAElE,MAAa,qBAAqB,SAChC,KAAK,MAAM,QAAQ,MAAoB,EAAE,SAAS,aAAa,EAAE,SAAS,YAAY;;;;;;AAOxF,MAAa,cACX,OACA,MACA,QAA6B,EAAE,MACxB;CACP,GAAG;CACH,SAAS;EAAC,GAAG,MAAM;EAAS,GAAG,KAAK;EAAO,GAAG;EAAM;CACrD;;;;;;AAWD,MAAa,cACX,SAEA,KAAK,KACH,OAAO,WAAW,OAChB,GAAG,SAAS,eAAe,OAAO,QAAQ,GAAG,KAAK,GAAG,OAAO,SAC7D,CACF;;;;;;AAWH,IAAa,kBAAb,cAAqC,KAAK,YAAY,kBAAkB,CAErE;AAEH,MAAM,wBAAwB,SAAqE;CACjG,MAAM,aAAa,kBAAkB,KAAK;CAC1C,MAAM,OAAO,WAAW,WAAW,SAAS;AAC5C,KAAI,SAAS,KAAA,EAAW,QAAO;EAAE,MAAM;EAAI,SAAS;EAAO;AAC3D,KAAI,KAAK,QAAQ,KAAK,UAAU,CAAE,QAAO;EAAE,MAAM;EAAI,SAAS;EAAM;AAKpE,QAAO;EAAE,MAJI,KAAK,QACf,OAAO,aAAa,CACpB,KAAK,MAAM,EAAE,KAAK,CAClB,KAAK,GACK;EAAE,SAAS;EAAO;;;;;;;;;;;;AAajC,MAAa,gBACX,MACA,WAKA,KAAK,qBAAqB,KAAK,GAAG,EAAE,MAAM,cACxC,UAAU,OAAO,KAAK,IAAI,gBAAgB,EAAE,MAAM,CAAC,CAAC,GAAGA,UAA2B,OAAO,CAAC,KAAK,CAChG"}
package/dist/index.d.mts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { n as AiError_d_exports } from "./AiError-CqmYjXyx.mjs";
2
2
  import { h as Items_d_exports } from "./Items-D1C2686t.mjs";
3
3
  import { a as StructuredFormat_d_exports } from "./StructuredFormat-B5ueioNr.mjs";
4
- import { o as Turn_d_exports } from "./Turn-rlTfuHaQ.mjs";
4
+ import { a as Turn_d_exports } from "./Turn-Bi83du4I.mjs";
5
5
  import { l as Tool_d_exports } from "./Tool-5wxOCuOh.mjs";
6
6
  import { t as LanguageModel_d_exports } from "./language-model/LanguageModel.mjs";
7
- import { n as Loop_d_exports } from "./Loop-CzSJo1h8.mjs";
7
+ import { t as Loop_d_exports } from "./loop/Loop.mjs";
8
8
  import { t as Match_d_exports } from "./match/Match.mjs";
9
9
  import { t as Toolkit_d_exports } from "./tool/Toolkit.mjs";
10
10
  import { t as JSONL_d_exports } from "./streaming/JSONL.mjs";
@@ -1,7 +1,7 @@
1
1
  import { t as AiError } from "../AiError-CqmYjXyx.mjs";
2
2
  import { m as Item } from "../Items-D1C2686t.mjs";
3
3
  import { i as StructuredFormat } from "../StructuredFormat-B5ueioNr.mjs";
4
- import { a as TurnEvent, i as Turn } from "../Turn-rlTfuHaQ.mjs";
4
+ import { i as TurnEvent, r as Turn } from "../Turn-Bi83du4I.mjs";
5
5
  import { o as ToolDescriptor } from "../Tool-5wxOCuOh.mjs";
6
6
  import { Context, Effect, Stream } from "effect";
7
7
 
@@ -1,2 +1,87 @@
1
- import { a as nextAfter, c as stopAfter, d as value, i as next, l as stopEvent, o as nextAfterFold, r as loop, s as stop, t as Event, u as streamUntilComplete } from "../Loop-CzSJo1h8.mjs";
2
- export { Event, loop, next, nextAfter, nextAfterFold, stop, stopAfter, stopEvent, streamUntilComplete, value };
1
+ import { l as IncompleteTurn } from "../AiError-CqmYjXyx.mjs";
2
+ import { i as TurnEvent, r as Turn } from "../Turn-Bi83du4I.mjs";
3
+ import { Data, Effect, Stream } from "effect";
4
+
5
+ //#region src/loop/Loop.d.ts
6
+ declare namespace Loop_d_exports {
7
+ export { Event, loop, next, nextAfter, nextAfterFold, stop, stopAfter, stopEvent, streamUntilComplete, value };
8
+ }
9
+ /**
10
+ * The tagged union a body emits per pull. `Value` carries a payload that
11
+ * flows downstream. `Next` ends the current iteration and continues with a
12
+ * new state. `Stop` ends the loop entirely.
13
+ */
14
+ type Event<A, S> = Data.TaggedEnum<{
15
+ Value: {
16
+ readonly value: A;
17
+ };
18
+ Next: {
19
+ readonly state: S;
20
+ };
21
+ Stop: {};
22
+ }>;
23
+ /** Wrap a value so it flows through the loop to downstream consumers. */
24
+ declare const value: <A>(a: A) => Event<A, never>;
25
+ /** End the current iteration and continue with a new state. */
26
+ declare const next: <S>(state: S) => Event<never, S>;
27
+ /** The terminal `Stop` event. Use `stop` (the Stream) to end a loop body. */
28
+ declare const stopEvent: Event<never, never>;
29
+ /**
30
+ * A single-element stream that ends the loop. Return this from a body when
31
+ * there's nothing else to emit; equivalent to `stopAfter(Stream.empty)` but
32
+ * named for the common case.
33
+ */
34
+ declare const stop: Stream.Stream<Event<never, never>>;
35
+ /**
36
+ * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the
37
+ * iteration with `next(state)`. Common shape for "stream this turn's
38
+ * deltas, then continue with updated history."
39
+ */
40
+ declare const nextAfter: <S, A, E, R>(stream: Stream.Stream<A, E, R>, state: S) => Stream.Stream<Event<A, S>, E, R>;
41
+ /**
42
+ * Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the
43
+ * loop. Common shape for "stream this turn's deltas, then we're done."
44
+ */
45
+ declare const stopAfter: <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, never>, E, R>;
46
+ /**
47
+ * General `nextAfter` variant: drain `stream` to the consumer, fold elements
48
+ * into an accumulator, and at end-of-stream emit one `next(build(finalAcc))`.
49
+ *
50
+ * Subsumes `nextAfter` when state is constant (`reduce: (s, _) => s`,
51
+ * `build: (s) => s`). Used by `Toolkit.nextStateFrom` to collect tool
52
+ * results and build next state without exposing a Ref to recipes.
53
+ */
54
+ declare const nextAfterFold: <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>;
55
+ /**
56
+ * Lift a provider's `Stream<TurnEvent>` into a loop body's `Stream<Event<TurnEvent | A, S>>`.
57
+ * Each delta passes through as `value(delta)` (including the terminal
58
+ * `turn_complete`, so the consumer sees turn boundaries naturally). Once
59
+ * the terminal arrives, `then(turn)` runs and its returned stream of loop
60
+ * events (typically tool outputs followed by `next(state)` or `stop`) is
61
+ * concatenated.
62
+ *
63
+ * Pre-pipe transforms (`Stream.tap` / `Stream.map` / `Stream.filter`) on
64
+ * the raw delta stream cover anything an `emit`-style callback would do.
65
+ *
66
+ * If the upstream ends without a `turn_complete`, the resulting stream
67
+ * fails with `AiError.IncompleteTurn`. Catch it via `Stream.catchTag` if
68
+ * you want to recover.
69
+ */
70
+ declare const streamUntilComplete: <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>;
71
+ 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>;
72
+ /**
73
+ * Drive a state-threaded loop body. Each iteration runs `body(state)` to get
74
+ * a `Stream<Event<A, S>>`; values flow downstream, `next(s)` continues with
75
+ * a new state, `stop` ends the loop. See the file header for the full
76
+ * pull-based execution model.
77
+ *
78
+ * Dual: data-first `loop(initial, body)` and data-last `loop(body)(initial)`
79
+ * (or `pipe(initial, loop(body))`) both work.
80
+ */
81
+ declare const loop: {
82
+ <S, A, E, R>(body: LoopBody<S, A, E, R>): (initial: S) => Stream.Stream<A, E, R>;
83
+ <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>): Stream.Stream<A, E, R>;
84
+ };
85
+ //#endregion
86
+ export { Event, loop, next, nextAfter, nextAfterFold, stop, stopAfter, stopEvent, streamUntilComplete, Loop_d_exports as t, value };
87
+ //# sourceMappingURL=Loop.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Loop.d.mts","names":[],"sources":["../../src/loop/Loop.ts"],"mappings":";;;;;;;;;;;;;KAgCY,KAAA,SAAc,IAAA,CAAK,UAAA;EAC7B,KAAA;IAAA,SAAkB,KAAA,EAAO,CAAA;EAAA;EACzB,IAAA;IAAA,SAAiB,KAAA,EAAO,CAAA;EAAA;EACxB,IAAA;AAAA;;cAUW,KAAA,MAAY,CAAA,EAAG,CAAA,KAAI,KAAA,CAAM,CAAA;;cAGzB,IAAA,MAAW,KAAA,EAAO,CAAA,KAAI,KAAA,QAAa,CAAA;;cAGnC,SAAA,EAAW,KAAA;;;;;;cAOX,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,KAAA;;;;;;cAOpB,SAAA,eACX,MAAA,EAAQ,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,CAAA,GAC5B,KAAA,EAAO,CAAA,KACN,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA;AApBjC;;;;AAAA,cA2Ba,SAAA,YACX,MAAA,EAAQ,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,CAAA,MAC3B,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,CAAA,UAAW,CAAA,EAAG,CAAA;;;;;;;;;cAWxB,aAAA,kBACX,MAAA,EAAQ,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,CAAA,GAC5B,OAAA,EAAS,CAAA,EACT,MAAA,GAAS,GAAA,EAAK,CAAA,EAAG,CAAA,EAAG,CAAA,KAAM,CAAA,EAC1B,KAAA,GAAQ,CAAA,EAAG,CAAA,KAAM,CAAA,KAChB,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA;;AA1CjC;;;;;AAOA;;;;;;;;;cAqEa,mBAAA,iCAET,IAAA,GAAO,IAAA,EAAM,IAAA,KAAS,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,GAAI,EAAA,EAAI,EAAA,GAAK,EAAA,EAAI,EAAA,aAG5E,MAAA,EAAQ,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA,MACnC,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,SAAA,GAAY,CAAA,EAAG,CAAA,GAAI,CAAA,GAAI,EAAA,GAAK,cAAA,EAAgB,CAAA,GAAI,EAAA;AAAA,KAkEpE,QAAA,gBACH,KAAA,EAAO,CAAA,KACJ,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,IAAK,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA;;;;;;;;;;cAW9E,IAAA;EAAA,aACE,IAAA,EAAM,QAAA,CAAS,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,KAAM,OAAA,EAAS,CAAA,KAAM,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,CAAA;EAAA,aACjE,OAAA,EAAS,CAAA,EAAG,IAAA,EAAM,QAAA,CAAS,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,IAAK,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,CAAA;AAAA"}
@@ -1,5 +1,5 @@
1
1
  import { m as Item } from "../Items-D1C2686t.mjs";
2
- import { i as Turn } from "../Turn-rlTfuHaQ.mjs";
2
+ import { r as Turn } from "../Turn-Bi83du4I.mjs";
3
3
  import { LanguageModel, LanguageModelService } from "../language-model/LanguageModel.mjs";
4
4
  import { Duration, Effect, Layer } from "effect";
5
5
 
@@ -1,5 +1,5 @@
1
1
  import { m as Item, o as FunctionCall } from "../Items-D1C2686t.mjs";
2
- import { n as ToolResult } from "../Outcome-C2JYknCu.mjs";
2
+ import { t as ToolResult } from "../Outcome-GiaNvt7i.mjs";
3
3
 
4
4
  //#region src/tool/HistoryCheck.d.ts
5
5
  /**
@@ -1,2 +1,2 @@
1
- import { a as execute, c as isValue, d as toFunctionCallOutput, i as denied, l as reject, n as ToolResult, o as executionError, r as cancelled, s as isFailure, t as ToolDecision, u as rejected } from "../Outcome-C2JYknCu.mjs";
2
- export { ToolDecision, ToolResult, cancelled, denied, execute, executionError, isFailure, isValue, reject, rejected, toFunctionCallOutput };
1
+ import { a as isFailure, c as toFunctionCallOutput, i as executionError, n as cancelled, o as isValue, r as denied, s as rejected, t as ToolResult } from "../Outcome-GiaNvt7i.mjs";
2
+ export { ToolResult, cancelled, denied, executionError, isFailure, isValue, rejected, toFunctionCallOutput };
@@ -2,11 +2,10 @@ import { functionCallOutput } from "../domain/Items.mjs";
2
2
  import { Match } from "effect";
3
3
  //#region src/tool/Outcome.ts
4
4
  /**
5
- * Pre-execution decision (`ToolDecision`) and post-execution result
6
- * (`ToolResult`) for the resolver-based executor.
5
+ * Post-execution and synthetic tool results.
7
6
  *
8
- * - Resolver returns ToolDecision (Execute | Reject(result)) per call.
9
- * - Executor emits ToolResult (Value | Failure) per call.
7
+ * - Executed tools emit ToolResult.Value.
8
+ * - Approval/cancellation policy emits synthetic ToolResult.Failure.
10
9
  *
11
10
  * Wire conversion stays at the recipe boundary via `toFunctionCallOutput`
12
11
  * so recipes can inspect, redact, or audit values before serialization.
@@ -17,11 +16,6 @@ import { Match } from "effect";
17
16
  */
18
17
  const isValue = (r) => r._tag === "Value";
19
18
  const isFailure = (r) => r._tag === "Failure";
20
- const execute = { _tag: "Execute" };
21
- const reject = (result) => ({
22
- _tag: "Reject",
23
- result
24
- });
25
19
  const rejected = (call, kind, reason) => ({
26
20
  _tag: "Failure",
27
21
  call_id: call.call_id,
@@ -40,6 +34,6 @@ const toFunctionCallOutput = (r) => Match.value(r).pipe(Match.tag("Value", (v) =
40
34
  reason: f.reason
41
35
  } : { kind: f.kind }))), Match.exhaustive);
42
36
  //#endregion
43
- export { cancelled, denied, execute, executionError, isFailure, isValue, reject, rejected, toFunctionCallOutput };
37
+ export { cancelled, denied, executionError, isFailure, isValue, rejected, toFunctionCallOutput };
44
38
 
45
39
  //# sourceMappingURL=Outcome.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Outcome.mjs","names":[],"sources":["../../src/tool/Outcome.ts"],"sourcesContent":["/**\n * Pre-execution decision (`ToolDecision`) and post-execution result\n * (`ToolResult`) for the resolver-based executor.\n *\n * - Resolver returns ToolDecision (Execute | Reject(result)) per call.\n * - Executor emits ToolResult (Value | Failure) per call.\n *\n * Wire conversion stays at the recipe boundary via `toFunctionCallOutput`\n * so recipes can inspect, redact, or audit values before serialization.\n *\n * `output` and `reason` are `string`, not `unknown`: the wire wants strings,\n * and `unknown` would invite non-serializable values (Date, Map, BigInt,\n * fn). Recipes that want structured detail JSON.stringify themselves.\n */\nimport { Match } from \"effect\"\nimport type { FunctionCall, FunctionCallOutput } from \"../domain/Items.js\"\nimport { functionCallOutput } from \"../domain/Items.js\"\n\n// ---------------------------------------------------------------------------\n// ToolResult\n// ---------------------------------------------------------------------------\n\nexport type ToolResult =\n | {\n readonly _tag: \"Value\"\n readonly call_id: string\n readonly tool: string\n readonly value: unknown\n }\n | {\n readonly _tag: \"Failure\"\n readonly call_id: string\n readonly tool: string\n readonly kind: string\n readonly reason?: string\n }\n\nexport const isValue = (r: ToolResult): r is Extract<ToolResult, { _tag: \"Value\" }> =>\n r._tag === \"Value\"\n\nexport const isFailure = (r: ToolResult): r is Extract<ToolResult, { _tag: \"Failure\" }> =>\n r._tag === \"Failure\"\n\n// ---------------------------------------------------------------------------\n// ToolDecision\n// ---------------------------------------------------------------------------\n\nexport type ToolDecision =\n | { readonly _tag: \"Execute\" }\n | { readonly _tag: \"Reject\"; readonly result: ToolResult }\n\nexport const execute: ToolDecision = { _tag: \"Execute\" }\n\nexport const reject = (result: ToolResult): ToolDecision => ({ _tag: \"Reject\", result })\n\n// ---------------------------------------------------------------------------\n// Synthesizers. `denied` and `cancelled` are operationally distinct;\n// anything else is just a recipe-chosen `kind` via `rejected`.\n// ---------------------------------------------------------------------------\n\nexport const rejected = (\n call: FunctionCall,\n kind: string,\n reason?: string,\n): ToolResult => ({\n _tag: \"Failure\",\n call_id: call.call_id,\n tool: call.name,\n kind,\n ...(reason !== undefined ? { reason } : {}),\n})\n\n/** Explicit user/policy rejection. */\nexport const denied = (call: FunctionCall, reason?: string): ToolResult =>\n rejected(call, \"denied\", reason)\n\n/** Implicit non-answer (follow-up, inactivity, abort). */\nexport const cancelled = (call: FunctionCall, reason?: string): ToolResult =>\n rejected(call, \"cancelled\", reason)\n\n/** Tool's own execution failed (parse error, schema, runtime crash). */\nexport const executionError = (call: FunctionCall, reason: string): ToolResult =>\n rejected(call, \"execution_error\", reason)\n\n// ---------------------------------------------------------------------------\n// Wire conversion - the one place structured → string happens.\n// ---------------------------------------------------------------------------\n\nexport const toFunctionCallOutput = (r: ToolResult): FunctionCallOutput =>\n Match.value(r).pipe(\n Match.tag(\"Value\", (v) => functionCallOutput(v.call_id, JSON.stringify(v.value))),\n Match.tag(\"Failure\", (f) =>\n functionCallOutput(\n f.call_id,\n JSON.stringify(\n f.reason !== undefined ? { kind: f.kind, reason: f.reason } : { kind: f.kind },\n ),\n ),\n ),\n Match.exhaustive,\n )\n"],"mappings":";;;;;;;;;;;;;;;;;AAqCA,MAAa,WAAW,MACtB,EAAE,SAAS;AAEb,MAAa,aAAa,MACxB,EAAE,SAAS;AAUb,MAAa,UAAwB,EAAE,MAAM,WAAW;AAExD,MAAa,UAAU,YAAsC;CAAE,MAAM;CAAU;CAAQ;AAOvF,MAAa,YACX,MACA,MACA,YACgB;CAChB,MAAM;CACN,SAAS,KAAK;CACd,MAAM,KAAK;CACX;CACA,GAAI,WAAW,KAAA,IAAY,EAAE,QAAQ,GAAG,EAAE;CAC3C;;AAGD,MAAa,UAAU,MAAoB,WACzC,SAAS,MAAM,UAAU,OAAO;;AAGlC,MAAa,aAAa,MAAoB,WAC5C,SAAS,MAAM,aAAa,OAAO;;AAGrC,MAAa,kBAAkB,MAAoB,WACjD,SAAS,MAAM,mBAAmB,OAAO;AAM3C,MAAa,wBAAwB,MACnC,MAAM,MAAM,EAAE,CAAC,KACb,MAAM,IAAI,UAAU,MAAM,mBAAmB,EAAE,SAAS,KAAK,UAAU,EAAE,MAAM,CAAC,CAAC,EACjF,MAAM,IAAI,YAAY,MACpB,mBACE,EAAE,SACF,KAAK,UACH,EAAE,WAAW,KAAA,IAAY;CAAE,MAAM,EAAE;CAAM,QAAQ,EAAE;CAAQ,GAAG,EAAE,MAAM,EAAE,MAAM,CAC/E,CACF,CACF,EACD,MAAM,WACP"}
1
+ {"version":3,"file":"Outcome.mjs","names":[],"sources":["../../src/tool/Outcome.ts"],"sourcesContent":["/**\n * Post-execution and synthetic tool results.\n *\n * - Executed tools emit ToolResult.Value.\n * - Approval/cancellation policy emits synthetic ToolResult.Failure.\n *\n * Wire conversion stays at the recipe boundary via `toFunctionCallOutput`\n * so recipes can inspect, redact, or audit values before serialization.\n *\n * `output` and `reason` are `string`, not `unknown`: the wire wants strings,\n * and `unknown` would invite non-serializable values (Date, Map, BigInt,\n * fn). Recipes that want structured detail JSON.stringify themselves.\n */\nimport { Match } from \"effect\"\nimport type { FunctionCall, FunctionCallOutput } from \"../domain/Items.js\"\nimport { functionCallOutput } from \"../domain/Items.js\"\n\n// ---------------------------------------------------------------------------\n// ToolResult\n// ---------------------------------------------------------------------------\n\nexport type ToolResult =\n | {\n readonly _tag: \"Value\"\n readonly call_id: string\n readonly tool: string\n readonly value: unknown\n }\n | {\n readonly _tag: \"Failure\"\n readonly call_id: string\n readonly tool: string\n readonly kind: string\n readonly reason?: string\n }\n\nexport const isValue = (r: ToolResult): r is Extract<ToolResult, { _tag: \"Value\" }> =>\n r._tag === \"Value\"\n\nexport const isFailure = (r: ToolResult): r is Extract<ToolResult, { _tag: \"Failure\" }> =>\n r._tag === \"Failure\"\n\n// Synthesizers. `denied` and `cancelled` are operationally distinct;\n// anything else is just a recipe-chosen `kind` via `rejected`.\n// ---------------------------------------------------------------------------\n\nexport const rejected = (\n call: FunctionCall,\n kind: string,\n reason?: string,\n): ToolResult => ({\n _tag: \"Failure\",\n call_id: call.call_id,\n tool: call.name,\n kind,\n ...(reason !== undefined ? { reason } : {}),\n})\n\n/** Explicit user/policy rejection. */\nexport const denied = (call: FunctionCall, reason?: string): ToolResult =>\n rejected(call, \"denied\", reason)\n\n/** Implicit non-answer (follow-up, inactivity, abort). */\nexport const cancelled = (call: FunctionCall, reason?: string): ToolResult =>\n rejected(call, \"cancelled\", reason)\n\n/** Tool's own execution failed (parse error, schema, runtime crash). */\nexport const executionError = (call: FunctionCall, reason: string): ToolResult =>\n rejected(call, \"execution_error\", reason)\n\n// ---------------------------------------------------------------------------\n// Wire conversion - the one place structured → string happens.\n// ---------------------------------------------------------------------------\n\nexport const toFunctionCallOutput = (r: ToolResult): FunctionCallOutput =>\n Match.value(r).pipe(\n Match.tag(\"Value\", (v) => functionCallOutput(v.call_id, JSON.stringify(v.value))),\n Match.tag(\"Failure\", (f) =>\n functionCallOutput(\n f.call_id,\n JSON.stringify(\n f.reason !== undefined ? { kind: f.kind, reason: f.reason } : { kind: f.kind },\n ),\n ),\n ),\n Match.exhaustive,\n )\n"],"mappings":";;;;;;;;;;;;;;;;AAoCA,MAAa,WAAW,MACtB,EAAE,SAAS;AAEb,MAAa,aAAa,MACxB,EAAE,SAAS;AAMb,MAAa,YACX,MACA,MACA,YACgB;CAChB,MAAM;CACN,SAAS,KAAK;CACd,MAAM,KAAK;CACX;CACA,GAAI,WAAW,KAAA,IAAY,EAAE,QAAQ,GAAG,EAAE;CAC3C;;AAGD,MAAa,UAAU,MAAoB,WACzC,SAAS,MAAM,UAAU,OAAO;;AAGlC,MAAa,aAAa,MAAoB,WAC5C,SAAS,MAAM,aAAa,OAAO;;AAGrC,MAAa,kBAAkB,MAAoB,WACjD,SAAS,MAAM,mBAAmB,OAAO;AAM3C,MAAa,wBAAwB,MACnC,MAAM,MAAM,EAAE,CAAC,KACb,MAAM,IAAI,UAAU,MAAM,mBAAmB,EAAE,SAAS,KAAK,UAAU,EAAE,MAAM,CAAC,CAAC,EACjF,MAAM,IAAI,YAAY,MACpB,mBACE,EAAE,SACF,KAAK,UACH,EAAE,WAAW,KAAA,IAAY;CAAE,MAAM,EAAE;CAAM,QAAQ,EAAE;CAAQ,GAAG,EAAE,MAAM,EAAE,MAAM,CAC/E,CACF,CACF,EACD,MAAM,WACP"}
@@ -1,23 +1,37 @@
1
1
  import { o as FunctionCall } from "../Items-D1C2686t.mjs";
2
- import { n as ToolResult, t as ToolDecision } from "../Outcome-C2JYknCu.mjs";
3
- import { t as ToolEvent } from "../ToolEvent-B2N10hr3.mjs";
4
- import { Resolver } from "./Toolkit.mjs";
2
+ import { t as ToolResult } from "../Outcome-GiaNvt7i.mjs";
3
+ import { t as ToolEvent } from "../ToolEvent-wTMgb2GO.mjs";
5
4
  import { Effect, Queue, Scope, Stream } from "effect";
6
5
 
7
6
  //#region src/tool/Resolvers.d.ts
7
+ interface ToolCallPlan {
8
+ readonly approved: ReadonlyArray<FunctionCall>;
9
+ readonly rejected: ReadonlyArray<ToolResult>;
10
+ }
11
+ type ToolCallDecision = {
12
+ readonly _tag: "Approved";
13
+ readonly call: FunctionCall;
14
+ } | {
15
+ readonly _tag: "Rejected";
16
+ readonly result: ToolResult;
17
+ };
18
+ declare const approve: (call: FunctionCall) => ToolCallDecision;
19
+ declare const reject: (result: ToolResult) => ToolCallDecision;
20
+ declare const splitToolCallDecisions: (decisions: ReadonlyArray<ToolCallDecision>) => ToolCallPlan;
21
+ declare const approvalRequested: (call: FunctionCall) => ToolEvent;
8
22
  interface Verdict {
9
23
  readonly call_id: string;
10
24
  readonly decision: "approve" | "deny";
11
25
  readonly reason?: string;
12
26
  }
13
27
  /**
14
- * Queue-backed resolver. The router fiber drains verdicts and resolves
15
- * pre-registered Deferreds keyed by `call_id`. Returns the resolver and
16
- * a stream of `ApprovalRequested` events for the gated calls; the recipe
17
- * merges the announce stream into its consumer view.
28
+ * Queue-backed approval planner. Safe calls are returned immediately in
29
+ * `approved`; gated calls emit `ApprovalRequested` events and later produce
30
+ * one `ToolCallDecision` when their matching verdict arrives.
18
31
  */
19
32
  declare const fromVerdictQueue: (predicate: (call: FunctionCall) => boolean, verdicts: Queue.Dequeue<Verdict>) => (calls: ReadonlyArray<FunctionCall>) => Effect.Effect<{
20
- readonly resolve: Resolver;
33
+ readonly approved: ReadonlyArray<FunctionCall>;
34
+ readonly decisions: Stream.Stream<ToolCallDecision>;
21
35
  readonly announce: Stream.Stream<ToolEvent>;
22
36
  }, never, Scope.Scope>;
23
37
  type ApprovalMapEntry = {
@@ -26,19 +40,7 @@ type ApprovalMapEntry = {
26
40
  readonly decision: "deny";
27
41
  readonly reason?: string;
28
42
  };
29
- declare const fromApprovalMap: (predicate: (call: FunctionCall) => boolean, approvals: ReadonlyMap<string, ApprovalMapEntry>) => Resolver;
30
- /**
31
- * Authz gate. `canApprove` runs BEFORE the inner resolver; failures
32
- * short-circuit to a `permission_denied` rejection. Override `onForbidden`
33
- * if your audit format wants a different kind or reason.
34
- */
35
- declare const withPermissions: (inner: Resolver, canApprove: (call: FunctionCall) => Effect.Effect<boolean>, onForbidden?: (call: FunctionCall) => ToolResult) => Resolver;
36
- /**
37
- * Fallback gate. If `inner` returns a Reject whose result matches the
38
- * `recoverable` predicate, run `fallback(call)` instead and use that
39
- * decision. Otherwise pass the original Reject through.
40
- */
41
- declare const withFallback: (inner: Resolver, recoverable: (result: ToolResult) => boolean, fallback: (call: FunctionCall) => Effect.Effect<ToolDecision>) => Resolver;
43
+ declare const fromApprovalMap: (predicate: (call: FunctionCall) => boolean, approvals: ReadonlyMap<string, ApprovalMapEntry>) => (calls: ReadonlyArray<FunctionCall>) => ToolCallPlan;
42
44
  //#endregion
43
- export { ApprovalMapEntry, Verdict, fromApprovalMap, fromVerdictQueue, withFallback, withPermissions };
45
+ export { ApprovalMapEntry, ToolCallDecision, ToolCallPlan, Verdict, approvalRequested, approve, fromApprovalMap, fromVerdictQueue, reject, splitToolCallDecisions };
44
46
  //# sourceMappingURL=Resolvers.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Resolvers.d.mts","names":[],"sources":["../../src/tool/Resolvers.ts"],"mappings":";;;;;;;UA8BiB,OAAA;EAAA,SACN,OAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;;;;;;;cASE,gBAAA,GAET,SAAA,GAAY,IAAA,EAAM,YAAA,cAClB,QAAA,EAAU,KAAA,CAAM,OAAA,CAAQ,OAAA,OAGxB,KAAA,EAAO,aAAA,CAAc,YAAA,MACpB,MAAA,CAAO,MAAA;EAAA,SAEG,OAAA,EAAS,QAAA;EAAA,SACT,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,SAAA;AAAA,UAGnC,KAAA,CAAM,KAAA;AAAA,KAkDE,gBAAA;EAAA,SACG,QAAA;AAAA;EAAA,SACA,QAAA;EAAA,SAA2B,MAAA;AAAA;AAAA,cAE7B,eAAA,GAET,SAAA,GAAY,IAAA,EAAM,YAAA,cAClB,SAAA,EAAW,WAAA,SAAoB,gBAAA,MAC9B,QAAA;;;;;;cAmBQ,eAAA,GAET,KAAA,EAAO,QAAA,EACP,UAAA,GAAa,IAAA,EAAM,YAAA,KAAiB,MAAA,CAAO,MAAA,WAC3C,WAAA,IAAc,IAAA,EAAM,YAAA,KAAiB,UAAA,KAEpC,QAAA;;;;;;cAaQ,YAAA,GAET,KAAA,EAAO,QAAA,EACP,WAAA,GAAc,MAAA,EAAQ,UAAA,cACtB,QAAA,GAAW,IAAA,EAAM,YAAA,KAAiB,MAAA,CAAO,MAAA,CAAO,YAAA,MAC/C,QAAA"}
1
+ {"version":3,"file":"Resolvers.d.mts","names":[],"sources":["../../src/tool/Resolvers.ts"],"mappings":";;;;;;UAYiB,YAAA;EAAA,SACN,QAAA,EAAU,aAAA,CAAc,YAAA;EAAA,SACxB,QAAA,EAAU,aAAA,CAAc,UAAA;AAAA;AAAA,KAGvB,gBAAA;EAAA,SACG,IAAA;EAAA,SAA2B,IAAA,EAAM,YAAA;AAAA;EAAA,SACjC,IAAA;EAAA,SAA2B,MAAA,EAAQ,UAAA;AAAA;AAAA,cAErC,OAAA,GAAW,IAAA,EAAM,YAAA,KAAe,gBAAA;AAAA,cAKhC,MAAA,GAAU,MAAA,EAAQ,UAAA,KAAa,gBAAA;AAAA,cAK/B,sBAAA,GACX,SAAA,EAAW,aAAA,CAAc,gBAAA,MACxB,YAAA;AAAA,cASU,iBAAA,GAAqB,IAAA,EAAM,YAAA,KAAe,SAAA;AAAA,UAWtC,OAAA;EAAA,SACN,OAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;;;;;AAnCX;cA2Ca,gBAAA,GAET,SAAA,GAAY,IAAA,EAAM,YAAA,cAClB,QAAA,EAAU,KAAA,CAAM,OAAA,CAAQ,OAAA,OAGxB,KAAA,EAAO,aAAA,CAAc,YAAA,MACpB,MAAA,CAAO,MAAA;EAAA,SAEG,QAAA,EAAU,aAAA,CAAc,YAAA;EAAA,SACxB,SAAA,EAAW,MAAA,CAAO,MAAA,CAAO,gBAAA;EAAA,SACzB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,SAAA;AAAA,UAGnC,KAAA,CAAM,KAAA;AAAA,KAkDE,gBAAA;EAAA,SACG,QAAA;AAAA;EAAA,SACA,QAAA;EAAA,SAA2B,MAAA;AAAA;AAAA,cAE7B,eAAA,GAET,SAAA,GAAY,IAAA,EAAM,YAAA,cAClB,SAAA,EAAW,WAAA,SAAoB,gBAAA,OAEhC,KAAA,EAAO,aAAA,CAAc,YAAA,MAAgB,YAAA"}
@@ -1,26 +1,45 @@
1
- import { cancelled, denied, execute, reject, rejected } from "./Outcome.mjs";
1
+ import { cancelled, denied } from "./Outcome.mjs";
2
2
  import { Deferred, Effect, Queue, Stream } from "effect";
3
3
  //#region src/tool/Resolvers.ts
4
4
  /**
5
- * Ready-made `Resolver`s for the two transport flavors plus combinators
6
- * for layering policy on top.
5
+ * Approval helpers for the two transport flavors.
7
6
  *
8
- * - `fromVerdictQueue` : long-lived channel (WebSocket / SSE).
9
- * - `fromApprovalMap` : request-shaped (HTTP chat).
10
- * - `withPermissions` : authz wrapper.
11
- * - `withFallback` : recovery wrapper.
12
- *
13
- * None of these know about the executor's stream shape; they just produce
14
- * `Effect<ToolDecision>`s a `Resolver` can return.
7
+ * These helpers only decide which calls are approved and which synthetic
8
+ * results must be returned to the model. Tool execution stays explicit at
9
+ * the recipe boundary via `Toolkit.executeAll`.
15
10
  */
11
+ const approve = (call) => ({
12
+ _tag: "Approved",
13
+ call
14
+ });
15
+ const reject = (result) => ({
16
+ _tag: "Rejected",
17
+ result
18
+ });
19
+ const splitToolCallDecisions = (decisions) => decisions.reduce((acc, decision) => decision._tag === "Approved" ? {
20
+ ...acc,
21
+ approved: [...acc.approved, decision.call]
22
+ } : {
23
+ ...acc,
24
+ rejected: [...acc.rejected, decision.result]
25
+ }, {
26
+ approved: [],
27
+ rejected: []
28
+ });
29
+ const approvalRequested = (call) => ({
30
+ _tag: "ApprovalRequested",
31
+ call_id: call.call_id,
32
+ tool: call.name,
33
+ arguments: call.arguments
34
+ });
16
35
  /**
17
- * Queue-backed resolver. The router fiber drains verdicts and resolves
18
- * pre-registered Deferreds keyed by `call_id`. Returns the resolver and
19
- * a stream of `ApprovalRequested` events for the gated calls; the recipe
20
- * merges the announce stream into its consumer view.
36
+ * Queue-backed approval planner. Safe calls are returned immediately in
37
+ * `approved`; gated calls emit `ApprovalRequested` events and later produce
38
+ * one `ToolCallDecision` when their matching verdict arrives.
21
39
  */
22
40
  const fromVerdictQueue = (predicate, verdicts) => (calls) => Effect.gen(function* () {
23
41
  const gated = calls.filter(predicate);
42
+ const approved = calls.filter((call) => !predicate(call));
24
43
  const entries = yield* Effect.forEach(gated, (call) => Deferred.make().pipe(Effect.map((d) => [call.call_id, d])));
25
44
  const deferreds = new Map(entries);
26
45
  yield* Effect.forkScoped(Effect.forever(Effect.gen(function* () {
@@ -28,40 +47,22 @@ const fromVerdictQueue = (predicate, verdicts) => (calls) => Effect.gen(function
28
47
  const d = deferreds.get(v.call_id);
29
48
  if (d !== void 0) yield* Deferred.succeed(d, v);
30
49
  })));
31
- const resolve = (call) => {
32
- if (!predicate(call)) return Effect.succeed(execute);
33
- const d = deferreds.get(call.call_id);
34
- return Deferred.await(d).pipe(Effect.map((v) => v.decision === "approve" ? execute : reject(denied(call, v.reason))));
35
- };
36
50
  return {
37
- resolve,
38
- announce: Stream.fromIterable(gated.map((call) => ({
39
- _tag: "ApprovalRequested",
40
- call_id: call.call_id,
41
- tool: call.name,
42
- arguments: call.arguments
43
- })))
51
+ approved,
52
+ decisions: Stream.fromIterable(gated).pipe(Stream.flatMap((call) => {
53
+ const d = deferreds.get(call.call_id);
54
+ return Stream.fromEffect(Deferred.await(d).pipe(Effect.map((v) => v.decision === "approve" ? approve(call) : reject(denied(call, v.reason)))));
55
+ }, { concurrency: "unbounded" })),
56
+ announce: Stream.fromIterable(gated.map(approvalRequested))
44
57
  };
45
58
  });
46
- const fromApprovalMap = (predicate, approvals) => (call) => {
47
- if (!predicate(call)) return Effect.succeed(execute);
59
+ const fromApprovalMap = (predicate, approvals) => (calls) => splitToolCallDecisions(calls.map((call) => {
60
+ if (!predicate(call)) return approve(call);
48
61
  const v = approvals.get(call.call_id);
49
- if (v === void 0) return Effect.succeed(reject(cancelled(call)));
50
- return Effect.succeed(v.decision === "approve" ? execute : reject(denied(call, v.reason)));
51
- };
52
- /**
53
- * Authz gate. `canApprove` runs BEFORE the inner resolver; failures
54
- * short-circuit to a `permission_denied` rejection. Override `onForbidden`
55
- * if your audit format wants a different kind or reason.
56
- */
57
- const withPermissions = (inner, canApprove, onForbidden = (call) => rejected(call, "permission_denied", "missing permissions")) => (call) => canApprove(call).pipe(Effect.flatMap((allowed) => allowed ? inner(call) : Effect.succeed(reject(onForbidden(call)))));
58
- /**
59
- * Fallback gate. If `inner` returns a Reject whose result matches the
60
- * `recoverable` predicate, run `fallback(call)` instead and use that
61
- * decision. Otherwise pass the original Reject through.
62
- */
63
- const withFallback = (inner, recoverable, fallback) => (call) => inner(call).pipe(Effect.flatMap((decision) => decision._tag === "Reject" && recoverable(decision.result) ? fallback(call) : Effect.succeed(decision)));
62
+ if (v === void 0) return reject(cancelled(call));
63
+ return v.decision === "approve" ? approve(call) : reject(denied(call, v.reason));
64
+ }));
64
65
  //#endregion
65
- export { fromApprovalMap, fromVerdictQueue, withFallback, withPermissions };
66
+ export { approvalRequested, approve, fromApprovalMap, fromVerdictQueue, reject, splitToolCallDecisions };
66
67
 
67
68
  //# sourceMappingURL=Resolvers.mjs.map