@effect-uai/core 0.18.0 → 0.19.0-dev-20261109183959

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 (162) hide show
  1. package/README.md +4 -4
  2. package/dist/Content-D_Qfn8Ja.d.mts +48 -0
  3. package/dist/Content-D_Qfn8Ja.d.mts.map +1 -0
  4. package/dist/File-B923KvZ9.d.mts +46 -0
  5. package/dist/File-B923KvZ9.d.mts.map +1 -0
  6. package/dist/{Image-Cv6iBLTy.d.mts → Image-Bzc1H_UK.d.mts} +19 -8
  7. package/dist/Image-Bzc1H_UK.d.mts.map +1 -0
  8. package/dist/{Items-C7MyrYPX.d.mts → Items-Bvx8s9QH.d.mts} +109 -16
  9. package/dist/Items-Bvx8s9QH.d.mts.map +1 -0
  10. package/dist/{Loop-ChpaO-ya.d.mts → Loop-CeA0AelN.d.mts} +7 -6
  11. package/dist/Loop-CeA0AelN.d.mts.map +1 -0
  12. package/dist/Tool-Bkryw6-h.d.mts +206 -0
  13. package/dist/Tool-Bkryw6-h.d.mts.map +1 -0
  14. package/dist/ToolEvent-9inLx5cI.d.mts +22 -0
  15. package/dist/ToolEvent-9inLx5cI.d.mts.map +1 -0
  16. package/dist/ToolOutput-BNTIQgGB.d.mts +151 -0
  17. package/dist/ToolOutput-BNTIQgGB.d.mts.map +1 -0
  18. package/dist/Toolkit-D2VGeD5R.d.mts +212 -0
  19. package/dist/Toolkit-D2VGeD5R.d.mts.map +1 -0
  20. package/dist/browser/BrowserTool.d.mts +84 -22
  21. package/dist/browser/BrowserTool.d.mts.map +1 -1
  22. package/dist/browser/BrowserTool.mjs +5 -0
  23. package/dist/browser/BrowserTool.mjs.map +1 -1
  24. package/dist/capabilities/Capabilities.d.mts +1 -1
  25. package/dist/decision-model/Decision.test.mjs +1 -1
  26. package/dist/{dist-C8RmtoR8.mjs → dist-B5K-y4Be.mjs} +51 -32
  27. package/dist/dist-B5K-y4Be.mjs.map +1 -0
  28. package/dist/domain/File.d.mts +2 -0
  29. package/dist/domain/File.mjs +51 -0
  30. package/dist/domain/File.mjs.map +1 -0
  31. package/dist/domain/Image.d.mts +2 -2
  32. package/dist/domain/Image.mjs +12 -2
  33. package/dist/domain/Image.mjs.map +1 -1
  34. package/dist/domain/Items.d.mts +2 -2
  35. package/dist/domain/Items.mjs +65 -4
  36. package/dist/domain/Items.mjs.map +1 -1
  37. package/dist/domain/Realtime.d.mts +72 -490
  38. package/dist/domain/Realtime.d.mts.map +1 -1
  39. package/dist/domain/Research.d.mts +1 -1
  40. package/dist/domain/Transcript.test.mjs +1 -1
  41. package/dist/domain/Turn.d.mts +57 -262
  42. package/dist/domain/Turn.d.mts.map +1 -1
  43. package/dist/domain/Turn.mjs +1 -1
  44. package/dist/embedding-model/Embedding.d.mts +1 -1
  45. package/dist/image-generator/ImageGenerator.d.mts +10 -66
  46. package/dist/image-generator/ImageGenerator.d.mts.map +1 -1
  47. package/dist/index.d.mts +10 -9
  48. package/dist/index.mjs +7 -6
  49. package/dist/language-model/LanguageModel.d.mts +2 -2
  50. package/dist/loop/Loop.d.mts +1 -1
  51. package/dist/loop/Loop.mjs.map +1 -1
  52. package/dist/messenger/Messenger.d.mts +43 -289
  53. package/dist/messenger/Messenger.d.mts.map +1 -1
  54. package/dist/messenger/MessengerAdapter.test.mjs +1 -1
  55. package/dist/observability/Metrics.d.mts +1 -1
  56. package/dist/observability/Metrics.test.mjs +1 -1
  57. package/dist/observability/Telemetry.test.mjs +1 -1
  58. package/dist/realtime/RealtimeSession.d.mts +1 -1
  59. package/dist/realtime/RealtimeSession.test.mjs +1 -1
  60. package/dist/sandbox/Sandbox.d.mts +32 -237
  61. package/dist/sandbox/Sandbox.d.mts.map +1 -1
  62. package/dist/streaming/Multipart.mjs +2 -1
  63. package/dist/streaming/Multipart.mjs.map +1 -1
  64. package/dist/streaming/Multipart.test.mjs +28 -0
  65. package/dist/streaming/Multipart.test.mjs.map +1 -0
  66. package/dist/streaming/Settle.test.mjs +1 -1
  67. package/dist/streaming/WebSocketSession.test.mjs +1 -1
  68. package/dist/testing/MockMessenger.d.mts +19 -134
  69. package/dist/testing/MockMessenger.d.mts.map +1 -1
  70. package/dist/testing/MockProvider.d.mts +1 -1
  71. package/dist/testing/MockSandbox.d.mts +65 -446
  72. package/dist/testing/MockSandbox.d.mts.map +1 -1
  73. package/dist/tool/Content.d.mts +2 -0
  74. package/dist/tool/Content.mjs +46 -0
  75. package/dist/tool/Content.mjs.map +1 -0
  76. package/dist/tool/Content.test.d.mts +1 -0
  77. package/dist/tool/Content.test.mjs +48 -0
  78. package/dist/tool/Content.test.mjs.map +1 -0
  79. package/dist/tool/HistoryCheck.d.mts +6 -15
  80. package/dist/tool/HistoryCheck.d.mts.map +1 -1
  81. package/dist/tool/HistoryCheck.mjs +10 -24
  82. package/dist/tool/HistoryCheck.mjs.map +1 -1
  83. package/dist/tool/Tool.d.mts +2 -2
  84. package/dist/tool/Tool.mjs +55 -130
  85. package/dist/tool/Tool.mjs.map +1 -1
  86. package/dist/tool/Tool.test.mjs +220 -176
  87. package/dist/tool/Tool.test.mjs.map +1 -1
  88. package/dist/tool/ToolDecision.d.mts +164 -0
  89. package/dist/tool/ToolDecision.d.mts.map +1 -0
  90. package/dist/tool/ToolDecision.mjs +146 -0
  91. package/dist/tool/ToolDecision.mjs.map +1 -0
  92. package/dist/tool/ToolDecision.test.d.mts +1 -0
  93. package/dist/tool/ToolDecision.test.mjs +330 -0
  94. package/dist/tool/ToolDecision.test.mjs.map +1 -0
  95. package/dist/tool/ToolEvent.d.mts +2 -2
  96. package/dist/tool/ToolEvent.mjs +2 -34
  97. package/dist/tool/ToolEvent.mjs.map +1 -1
  98. package/dist/tool/ToolOutput.d.mts +2 -0
  99. package/dist/tool/ToolOutput.mjs +128 -0
  100. package/dist/tool/ToolOutput.mjs.map +1 -0
  101. package/dist/tool/ToolOutput.test.d.mts +1 -0
  102. package/dist/tool/ToolOutput.test.mjs +153 -0
  103. package/dist/tool/ToolOutput.test.mjs.map +1 -0
  104. package/dist/tool/Toolkit.d.mts +2 -2
  105. package/dist/tool/Toolkit.mjs +230 -165
  106. package/dist/tool/Toolkit.mjs.map +1 -1
  107. package/dist/tool/Toolkit.test.mjs +431 -389
  108. package/dist/tool/Toolkit.test.mjs.map +1 -1
  109. package/dist/web-read/WebReadTool.d.mts +2 -2
  110. package/dist/web-read/WebReadTool.mjs +1 -0
  111. package/dist/web-read/WebReadTool.mjs.map +1 -1
  112. package/dist/web-search/WebSearchTool.d.mts +2 -2
  113. package/dist/web-search/WebSearchTool.mjs +1 -0
  114. package/dist/web-search/WebSearchTool.mjs.map +1 -1
  115. package/package.json +17 -9
  116. package/src/browser/BrowserTool.ts +5 -0
  117. package/src/domain/File.ts +79 -0
  118. package/src/domain/Image.ts +14 -4
  119. package/src/domain/Items.ts +39 -3
  120. package/src/index.ts +3 -2
  121. package/src/loop/Loop.ts +24 -13
  122. package/src/streaming/Multipart.test.ts +26 -0
  123. package/src/streaming/Multipart.ts +5 -4
  124. package/src/tool/Content.test.ts +40 -0
  125. package/src/tool/Content.ts +38 -0
  126. package/src/tool/HistoryCheck.ts +21 -26
  127. package/src/tool/Tool.test.ts +240 -241
  128. package/src/tool/Tool.ts +203 -308
  129. package/src/tool/ToolDecision.test.ts +301 -0
  130. package/src/tool/ToolDecision.ts +360 -0
  131. package/src/tool/ToolEvent.ts +21 -41
  132. package/src/tool/ToolOutput.test.ts +132 -0
  133. package/src/tool/ToolOutput.ts +198 -0
  134. package/src/tool/Toolkit.test.ts +594 -454
  135. package/src/tool/Toolkit.ts +631 -356
  136. package/src/web-read/WebReadTool.ts +2 -1
  137. package/src/web-search/WebSearchTool.ts +2 -1
  138. package/dist/Image-Cv6iBLTy.d.mts.map +0 -1
  139. package/dist/Items-C7MyrYPX.d.mts.map +0 -1
  140. package/dist/Loop-ChpaO-ya.d.mts.map +0 -1
  141. package/dist/Tool-BF78QWtn.d.mts +0 -314
  142. package/dist/Tool-BF78QWtn.d.mts.map +0 -1
  143. package/dist/ToolEvent-Di4a723B.d.mts +0 -144
  144. package/dist/ToolEvent-Di4a723B.d.mts.map +0 -1
  145. package/dist/ToolResult-BsYQRjmW.d.mts +0 -152
  146. package/dist/ToolResult-BsYQRjmW.d.mts.map +0 -1
  147. package/dist/Toolkit-tlL0F2YR.d.mts +0 -193
  148. package/dist/Toolkit-tlL0F2YR.d.mts.map +0 -1
  149. package/dist/dist-C8RmtoR8.mjs.map +0 -1
  150. package/dist/tool/Approval.d.mts +0 -113
  151. package/dist/tool/Approval.d.mts.map +0 -1
  152. package/dist/tool/Approval.mjs +0 -80
  153. package/dist/tool/Approval.mjs.map +0 -1
  154. package/dist/tool/Approval.test.mjs +0 -371
  155. package/dist/tool/Approval.test.mjs.map +0 -1
  156. package/dist/tool/ToolResult.d.mts +0 -2
  157. package/dist/tool/ToolResult.mjs +0 -85
  158. package/dist/tool/ToolResult.mjs.map +0 -1
  159. package/src/tool/Approval.test.ts +0 -400
  160. package/src/tool/Approval.ts +0 -181
  161. package/src/tool/ToolResult.ts +0 -114
  162. /package/dist/{tool/Approval.test.d.mts → streaming/Multipart.test.d.mts} +0 -0
package/src/loop/Loop.ts CHANGED
@@ -125,26 +125,27 @@ export const stop = <S = never>(state?: S): Stream.Stream<Step<never, S>> =>
125
125
  * Dual: data-first `onTurnComplete(deltas, then)` and data-last
126
126
  * `deltas.pipe(onTurnComplete(then))` both work.
127
127
  */
128
- type TurnContinuation<S, A, E2, R2> =
128
+ // The Effect and the stream it returns infer their errors separately.
129
+ type TurnContinuation<S, A, E2, R2, E3, R3> =
129
130
  | Stream.Stream<Step<A, S>, E2, R2>
130
- | Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, E2, R2>
131
+ | Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, E3, R3>
131
132
 
132
133
  export const onTurnComplete: {
133
- <S, A, E2 = never, R2 = never>(
134
- then: (turn: Turn) => TurnContinuation<S, A, E2, R2>,
134
+ <S, A, E2 = never, R2 = never, E3 = never, R3 = never>(
135
+ then: (turn: Turn) => TurnContinuation<S, A, E2, R2, E3, R3>,
135
136
  ): <E, R>(
136
137
  deltas: Stream.Stream<TurnEvent, E, R>,
137
- ) => Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>
138
- <S, A, E, R, E2 = never, R2 = never>(
138
+ ) => Stream.Stream<Step<TurnEvent | A, S>, E | E2 | E3 | IncompleteTurn, R | R2 | R3>
139
+ <S, A, E, R, E2 = never, R2 = never, E3 = never, R3 = never>(
139
140
  deltas: Stream.Stream<TurnEvent, E, R>,
140
- then: (turn: Turn) => TurnContinuation<S, A, E2, R2>,
141
- ): Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>
141
+ then: (turn: Turn) => TurnContinuation<S, A, E2, R2, E3, R3>,
142
+ ): Stream.Stream<Step<TurnEvent | A, S>, E | E2 | E3 | IncompleteTurn, R | R2 | R3>
142
143
  } = Function.dual(
143
144
  2,
144
- <S, A, E, R, E2, R2>(
145
+ <S, A, E, R, E2, R2, E3, R3>(
145
146
  deltas: Stream.Stream<TurnEvent, E, R>,
146
- then: (turn: Turn) => TurnContinuation<S, A, E2, R2>,
147
- ): Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2> =>
147
+ then: (turn: Turn) => TurnContinuation<S, A, E2, R2, E3, R3>,
148
+ ): Stream.Stream<Step<TurnEvent | A, S>, E | E2 | E3 | IncompleteTurn, R | R2 | R3> =>
148
149
  Stream.unwrap(
149
150
  Effect.gen(function* () {
150
151
  const turnRef = yield* Ref.make<Option.Option<Turn>>(Option.none())
@@ -217,6 +218,11 @@ type LoopBody<S, A, E, R> = (
217
218
  state: S,
218
219
  ) => Stream.Stream<Step<A, S>, E, R> | Effect.Effect<Stream.Stream<Step<A, S>, E, R>, E, R>
219
220
 
221
+ // What callers pass: the Effect and its stream infer errors and requirements separately.
222
+ type LoopBodyIn<S, A, E, R, E2, R2> = (
223
+ state: S,
224
+ ) => Stream.Stream<Step<A, S>, E, R> | Effect.Effect<Stream.Stream<Step<A, S>, E, R>, E2, R2>
225
+
220
226
  /**
221
227
  * Drive a state-threaded loop body. Each iteration runs `body(state)` to get
222
228
  * a `Stream<Step<A, S>>`; values flow downstream, `next(s)` continues with
@@ -227,8 +233,13 @@ type LoopBody<S, A, E, R> = (
227
233
  * (or `pipe(initial, loop(body))`) both work.
228
234
  */
229
235
  export const loop: {
230
- <S, A, E, R>(body: LoopBody<S, A, E, R>): (initial: S) => Stream.Stream<A, E, R>
231
- <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>): Stream.Stream<A, E, R>
236
+ <S, A, E, R, E2 = never, R2 = never>(
237
+ body: LoopBodyIn<S, A, E, R, E2, R2>,
238
+ ): (initial: S) => Stream.Stream<A, E | E2, R | R2>
239
+ <S, A, E, R, E2 = never, R2 = never>(
240
+ initial: S,
241
+ body: LoopBodyIn<S, A, E, R, E2, R2>,
242
+ ): Stream.Stream<A, E | E2, R | R2>
232
243
  } = Function.dual(2, <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>): Stream.Stream<A, E, R> =>
233
244
  Stream.scoped(
234
245
  Stream.fromPull(
@@ -0,0 +1,26 @@
1
+ import { describe, it } from "@effect/vitest"
2
+ import { Effect } from "effect"
3
+ import { HttpClientRequest } from "effect/http"
4
+ import { expect } from "vitest"
5
+ import { bodyMultipart } from "./Multipart.js"
6
+
7
+ describe("bodyMultipart", () => {
8
+ it.effect("sends the boundary the encoded parts are delimited by", () =>
9
+ Effect.gen(function* () {
10
+ const form = new FormData()
11
+ form.set("file", new Blob([new Uint8Array([1, 2, 3])], { type: "audio/ogg" }), "audio.ogg")
12
+ form.set("model", "whisper-1")
13
+
14
+ const request = HttpClientRequest.post("https://example.test/upload").pipe(
15
+ yield* bodyMultipart(form),
16
+ )
17
+ const contentType = request.headers["content-type"] ?? ""
18
+ const boundary = /boundary=(.+)$/.exec(contentType)?.[1]
19
+
20
+ expect(boundary).toBeDefined()
21
+ const body =
22
+ request.body._tag === "Uint8Array" ? new TextDecoder().decode(request.body.body) : ""
23
+ expect(body.startsWith(`--${boundary}\r\n`)).toBe(true)
24
+ }),
25
+ )
26
+ })
@@ -21,9 +21,10 @@ export const bodyMultipart = (
21
21
  > =>
22
22
  Effect.gen(function* () {
23
23
  const encoded = new Response(form)
24
+ // Read before the body: once it is consumed, Bun answers `null` for this
25
+ // header (Node keeps it), which would drop the boundary and leave the
26
+ // server unable to parse the parts.
27
+ const contentType = encoded.headers.get("content-type") ?? "multipart/form-data"
24
28
  const buffer = yield* Effect.tryPromise(() => encoded.arrayBuffer())
25
- return HttpClientRequest.bodyUint8Array(
26
- new Uint8Array(buffer),
27
- encoded.headers.get("content-type") ?? "multipart/form-data",
28
- )
29
+ return HttpClientRequest.bodyUint8Array(new Uint8Array(buffer), contentType)
29
30
  })
@@ -0,0 +1,40 @@
1
+ import { describe, expect, expectTypeOf, it } from "@effect/vitest"
2
+ import { Effect, Schema } from "effect"
3
+ import { imageBytes } from "../domain/Image.js"
4
+ import * as Content from "./Content.js"
5
+
6
+ const bytes = new Uint8Array([137, 80, 78, 71])
7
+ const encode = Schema.encodeUnknownEffect(Content.Value)
8
+ const decode = Schema.decodeUnknownEffect(Content.Value)
9
+
10
+ describe("Content.Value", () => {
11
+ it("encodes to JSON", () => {
12
+ expectTypeOf<typeof Content.Value.Encoded>().toExtend<Schema.Json>()
13
+ })
14
+
15
+ it.effect("round-trips image bytes through JSON as base64", () =>
16
+ Effect.gen(function* () {
17
+ const encoded = yield* encode(Content.image(imageBytes(bytes, "image/png")))
18
+ expect(encoded).toEqual({
19
+ _tag: "Image",
20
+ source: { _tag: "bytes", bytes: "iVBORw==", mimeType: "image/png" },
21
+ })
22
+
23
+ const decoded = yield* decode(JSON.parse(JSON.stringify(encoded)))
24
+ expect(decoded).toBeInstanceOf(Content.Image)
25
+ expect(decoded).toEqual(Content.image(imageBytes(bytes, "image/png")))
26
+ }),
27
+ )
28
+
29
+ it.effect("keeps plain objects plain and media as classes in a mixed array", () =>
30
+ Effect.gen(function* () {
31
+ const value = [
32
+ { title: "Home" },
33
+ Content.file({ _tag: "url", url: "https://x/a.pdf" }, "a.pdf"),
34
+ ]
35
+ const decoded = yield* decode(yield* encode(value))
36
+ expect(decoded).toEqual(value)
37
+ expect(Array.isArray(decoded) && decoded.map(Content.isMedia)).toEqual([false, true])
38
+ }),
39
+ )
40
+ })
@@ -0,0 +1,38 @@
1
+ /**
2
+ * What a tool's `run` may return without an `outputSchema`. Plain values stay
3
+ * plain (a string is text, anything else JSON); only media is marked, by class,
4
+ * so it is told apart from JSON by `instanceof`, never by shape.
5
+ */
6
+ import { Schema } from "effect"
7
+ import { type FileSource, FileSourceJson } from "../domain/File.js"
8
+ import { type ImageSource, ImageSourceJson } from "../domain/Image.js"
9
+
10
+ export class Image extends Schema.TaggedClass<Image>()("Image", { source: ImageSourceJson }) {}
11
+
12
+ export class File extends Schema.TaggedClass<File>()("File", {
13
+ source: FileSourceJson,
14
+ filename: Schema.optionalKey(Schema.String),
15
+ }) {}
16
+
17
+ export const Media = Schema.Union([Image, File])
18
+ export type Media = typeof Media.Type
19
+
20
+ export const image = (source: ImageSource): Image => new Image({ source })
21
+
22
+ export const file = (source: FileSource, filename?: string): File =>
23
+ new File(filename === undefined ? { source } : { source, filename })
24
+
25
+ export const isMedia = (u: unknown): u is Media => u instanceof Image || u instanceof File
26
+
27
+ /**
28
+ * Text, JSON, one medium, or a top-level array mixing JSON and media. Encodes to
29
+ * JSON (bytes as base64), so it also serves as the UI codec for tools without
30
+ * an `outputSchema`.
31
+ */
32
+ export const Value = Schema.Union([
33
+ Schema.String,
34
+ Media,
35
+ Schema.Array(Schema.Union([Media, Schema.Json])),
36
+ Schema.Json,
37
+ ])
38
+ export type Value = typeof Value.Type
@@ -1,23 +1,20 @@
1
1
  /**
2
- * History-consistency primitives. Useful even WITHOUT HITL.
3
- *
4
- * Every provider rejects a new request if any prior `function_call` lacks
5
- * a matching `function_call_output`. Multi-turn flows that can be
6
- * interrupted, restarted, or branched (HITL, mid-stream abort, persisted
7
- * checkpoints, stateless HTTP servers) need to detect orphans and
8
- * synthesize closing outputs before submitting.
9
- *
10
- * Recipe author calls these at known transition points (right before the
11
- * next provider request). Not invoked from inside the loop.
2
+ * History consistency. Providers reject a request when a `function_call` has no
3
+ * matching `function_call_output`; flows that can be interrupted, restarted or
4
+ * branched (approvals, aborts, persisted checkpoints) close orphans with these
5
+ * before the next model call.
12
6
  */
13
- import { type ToolCall, type HistoryItem, isToolCall, isToolCallOutput } from "../domain/Items.js"
14
- import { type ToolResult, cancelled } from "./ToolResult.js"
7
+ import { Effect, type Schema } from "effect"
8
+ import {
9
+ type HistoryItem,
10
+ isToolCall,
11
+ isToolCallOutput,
12
+ type ToolCall,
13
+ type ToolCallOutput,
14
+ } from "../domain/Items.js"
15
+ import * as ToolOutput from "./ToolOutput.js"
15
16
 
16
- /**
17
- * Return every `function_call` in `history` that does not have a matching
18
- * `function_call_output` later in `history` (correlated by `call_id`).
19
- * Empty result = history is provider-submittable from this invariant.
20
- */
17
+ /** Every `function_call` in `history` without a `function_call_output` (by `call_id`). */
21
18
  export const findUnansweredCalls = (
22
19
  history: ReadonlyArray<HistoryItem>,
23
20
  ): ReadonlyArray<ToolCall> => {
@@ -25,19 +22,17 @@ export const findUnansweredCalls = (
25
22
  return history.filter(isToolCall).filter((c) => !answered.has(c.call_id))
26
23
  }
27
24
 
28
- /** Cheap predicate: is this history submittable to a provider? */
29
25
  export const isReconciled = (history: ReadonlyArray<HistoryItem>): boolean =>
30
26
  findUnansweredCalls(history).length === 0
31
27
 
32
28
  /**
33
- * Synthesize cancellation results for every unanswered call. Caller maps
34
- * via `toToolCallOutput` and appends to history before submitting.
35
- *
36
- * Use when: a new user message arrives mid-approval; an approval timer
37
- * fires; a persisted checkpoint contains orphans (crash recovery); a
38
- * stateless HTTP server reconstructed history from a stale checkpoint.
29
+ * A `Cancelled` output for every unanswered call, to append before the next
30
+ * model call. For your own error, use `findUnansweredCalls` + `ToolOutput.failure`.
39
31
  */
40
32
  export const cancelAllPending = (
41
33
  history: ReadonlyArray<HistoryItem>,
42
- reason?: string,
43
- ): ReadonlyArray<ToolResult> => findUnansweredCalls(history).map((call) => cancelled(call, reason))
34
+ reason = "The call was cancelled before it ran.",
35
+ ): Effect.Effect<ReadonlyArray<ToolCallOutput>, Schema.SchemaError> =>
36
+ Effect.forEach(findUnansweredCalls(history), (call) =>
37
+ ToolOutput.failure(call, ToolOutput.Cancelled, new ToolOutput.Cancelled({ message: reason })),
38
+ )