@effect-uai/core 0.5.2 → 0.7.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 (175) hide show
  1. package/README.md +1 -1
  2. package/dist/{Audio-BfCTGnH3.d.mts → Audio-DPlTsNdz.d.mts} +3 -2
  3. package/dist/Audio-DPlTsNdz.d.mts.map +1 -0
  4. package/dist/{Items-BH8xUkoR.d.mts → Items-D8UaXQx_.d.mts} +15 -15
  5. package/dist/Items-D8UaXQx_.d.mts.map +1 -0
  6. package/dist/SandboxError-BvKypkJm.d.mts +158 -0
  7. package/dist/SandboxError-BvKypkJm.d.mts.map +1 -0
  8. package/dist/{Tool-87ViKCCO.d.mts → Tool-CiFXYjmR.d.mts} +11 -11
  9. package/dist/{Tool-87ViKCCO.d.mts.map → Tool-CiFXYjmR.d.mts.map} +1 -1
  10. package/dist/capabilities/Capabilities.d.mts +78 -0
  11. package/dist/capabilities/Capabilities.d.mts.map +1 -0
  12. package/dist/capabilities/Capabilities.mjs +65 -0
  13. package/dist/capabilities/Capabilities.mjs.map +1 -0
  14. package/dist/domain/Audio.d.mts +1 -1
  15. package/dist/domain/Audio.mjs.map +1 -1
  16. package/dist/domain/Items.d.mts +2 -2
  17. package/dist/domain/Items.mjs +15 -15
  18. package/dist/domain/Items.mjs.map +1 -1
  19. package/dist/domain/Music.d.mts +131 -72
  20. package/dist/domain/Music.d.mts.map +1 -1
  21. package/dist/domain/Music.mjs +36 -9
  22. package/dist/domain/Music.mjs.map +1 -1
  23. package/dist/domain/Transcript.d.mts +8 -2
  24. package/dist/domain/Transcript.d.mts.map +1 -1
  25. package/dist/domain/Transcript.mjs.map +1 -1
  26. package/dist/domain/Turn.d.mts +388 -2
  27. package/dist/domain/Turn.d.mts.map +1 -0
  28. package/dist/domain/Turn.mjs +13 -13
  29. package/dist/domain/Turn.mjs.map +1 -1
  30. package/dist/embedding-model/Embedding.d.mts +12 -2
  31. package/dist/embedding-model/Embedding.d.mts.map +1 -1
  32. package/dist/embedding-model/Embedding.mjs.map +1 -1
  33. package/dist/embedding-model/EmbeddingModel.d.mts +36 -23
  34. package/dist/embedding-model/EmbeddingModel.d.mts.map +1 -1
  35. package/dist/embedding-model/EmbeddingModel.mjs +20 -1
  36. package/dist/embedding-model/EmbeddingModel.mjs.map +1 -1
  37. package/dist/embedding-model/EmbeddingModel.test.mjs +15 -3
  38. package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
  39. package/dist/index.d.mts +13 -7
  40. package/dist/index.mjs +9 -3
  41. package/dist/language-model/LanguageModel.d.mts +5 -5
  42. package/dist/language-model/LanguageModel.d.mts.map +1 -1
  43. package/dist/language-model/LanguageModel.mjs.map +1 -1
  44. package/dist/loop/Loop.d.mts +37 -90
  45. package/dist/loop/Loop.d.mts.map +1 -1
  46. package/dist/loop/Loop.mjs +39 -93
  47. package/dist/loop/Loop.mjs.map +1 -1
  48. package/dist/loop/Loop.test.mjs +46 -54
  49. package/dist/loop/Loop.test.mjs.map +1 -1
  50. package/dist/music-generator/MusicGenerator.d.mts +48 -25
  51. package/dist/music-generator/MusicGenerator.d.mts.map +1 -1
  52. package/dist/music-generator/MusicGenerator.mjs +17 -9
  53. package/dist/music-generator/MusicGenerator.mjs.map +1 -1
  54. package/dist/music-generator/MusicGenerator.test.mjs +81 -56
  55. package/dist/music-generator/MusicGenerator.test.mjs.map +1 -1
  56. package/dist/sandbox/Memory.d.mts +53 -0
  57. package/dist/sandbox/Memory.d.mts.map +1 -0
  58. package/dist/sandbox/Memory.mjs +68 -0
  59. package/dist/sandbox/Memory.mjs.map +1 -0
  60. package/dist/sandbox/Sandbox.d.mts +717 -0
  61. package/dist/sandbox/Sandbox.d.mts.map +1 -0
  62. package/dist/sandbox/Sandbox.mjs +192 -0
  63. package/dist/sandbox/Sandbox.mjs.map +1 -0
  64. package/dist/sandbox/SandboxError.d.mts +2 -0
  65. package/dist/sandbox/SandboxError.mjs +89 -0
  66. package/dist/sandbox/SandboxError.mjs.map +1 -0
  67. package/dist/sandbox/SandboxImage.d.mts +28 -0
  68. package/dist/sandbox/SandboxImage.d.mts.map +1 -0
  69. package/dist/sandbox/SandboxImage.mjs +32 -0
  70. package/dist/sandbox/SandboxImage.mjs.map +1 -0
  71. package/dist/sandbox/SandboxNetwork.d.mts +34 -0
  72. package/dist/sandbox/SandboxNetwork.d.mts.map +1 -0
  73. package/dist/sandbox/SandboxNetwork.mjs +36 -0
  74. package/dist/sandbox/SandboxNetwork.mjs.map +1 -0
  75. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +84 -3
  76. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -1
  77. package/dist/speech-synthesizer/SpeechSynthesizer.mjs +32 -2
  78. package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
  79. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +59 -5
  80. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -1
  81. package/dist/testing/MockMusicGenerator.d.mts +12 -7
  82. package/dist/testing/MockMusicGenerator.d.mts.map +1 -1
  83. package/dist/testing/MockMusicGenerator.mjs +4 -3
  84. package/dist/testing/MockMusicGenerator.mjs.map +1 -1
  85. package/dist/testing/MockProvider.d.mts +3 -3
  86. package/dist/testing/MockProvider.d.mts.map +1 -1
  87. package/dist/testing/MockProvider.mjs.map +1 -1
  88. package/dist/testing/MockSandbox.d.mts +604 -0
  89. package/dist/testing/MockSandbox.d.mts.map +1 -0
  90. package/dist/testing/MockSandbox.mjs +160 -0
  91. package/dist/testing/MockSandbox.mjs.map +1 -0
  92. package/dist/testing/MockSpeechSynthesizer.d.mts +27 -12
  93. package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -1
  94. package/dist/testing/MockSpeechSynthesizer.mjs +75 -51
  95. package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -1
  96. package/dist/tool/{Resolvers.d.mts → Approval.d.mts} +27 -27
  97. package/dist/tool/Approval.d.mts.map +1 -0
  98. package/dist/tool/{Resolvers.mjs → Approval.mjs} +18 -18
  99. package/dist/tool/Approval.mjs.map +1 -0
  100. package/dist/tool/{Resolvers.test.mjs → Approval.test.mjs} +40 -40
  101. package/dist/tool/Approval.test.mjs.map +1 -0
  102. package/dist/tool/HistoryCheck.d.mts +6 -6
  103. package/dist/tool/HistoryCheck.d.mts.map +1 -1
  104. package/dist/tool/HistoryCheck.mjs +5 -5
  105. package/dist/tool/HistoryCheck.mjs.map +1 -1
  106. package/dist/tool/Tool.d.mts +2 -2
  107. package/dist/tool/Tool.mjs +4 -4
  108. package/dist/tool/Tool.mjs.map +1 -1
  109. package/dist/tool/ToolEvent.d.mts +19 -19
  110. package/dist/tool/ToolEvent.d.mts.map +1 -1
  111. package/dist/tool/ToolEvent.mjs +8 -8
  112. package/dist/tool/ToolEvent.mjs.map +1 -1
  113. package/dist/tool/{Outcome.d.mts → ToolResult.d.mts} +29 -29
  114. package/dist/tool/ToolResult.d.mts.map +1 -0
  115. package/dist/tool/ToolResult.mjs +61 -0
  116. package/dist/tool/ToolResult.mjs.map +1 -0
  117. package/dist/tool/Toolkit.d.mts +58 -26
  118. package/dist/tool/Toolkit.d.mts.map +1 -1
  119. package/dist/tool/Toolkit.mjs +62 -34
  120. package/dist/tool/Toolkit.mjs.map +1 -1
  121. package/dist/tool/Toolkit.test.mjs +13 -13
  122. package/dist/tool/Toolkit.test.mjs.map +1 -1
  123. package/dist/transcriber/Transcriber.d.mts +11 -10
  124. package/dist/transcriber/Transcriber.d.mts.map +1 -1
  125. package/dist/transcriber/Transcriber.mjs.map +1 -1
  126. package/dist/transcriber/Transcriber.test.mjs +3 -3
  127. package/dist/transcriber/Transcriber.test.mjs.map +1 -1
  128. package/package.json +35 -7
  129. package/src/capabilities/Capabilities.ts +81 -0
  130. package/src/domain/Audio.ts +2 -1
  131. package/src/domain/Items.ts +9 -9
  132. package/src/domain/Music.ts +142 -68
  133. package/src/domain/Transcript.ts +7 -1
  134. package/src/domain/Turn.ts +16 -16
  135. package/src/embedding-model/Embedding.ts +11 -0
  136. package/src/embedding-model/EmbeddingModel.test.ts +15 -4
  137. package/src/embedding-model/EmbeddingModel.ts +48 -21
  138. package/src/index.ts +8 -2
  139. package/src/language-model/LanguageModel.ts +3 -3
  140. package/src/loop/Loop.test.ts +93 -91
  141. package/src/loop/Loop.ts +72 -170
  142. package/src/music-generator/MusicGenerator.test.ts +78 -53
  143. package/src/music-generator/MusicGenerator.ts +65 -24
  144. package/src/sandbox/Memory.ts +128 -0
  145. package/src/sandbox/Sandbox.ts +639 -0
  146. package/src/sandbox/SandboxError.ts +140 -0
  147. package/src/sandbox/SandboxImage.ts +26 -0
  148. package/src/sandbox/SandboxNetwork.ts +34 -0
  149. package/src/speech-synthesizer/SpeechSynthesizer.test.ts +92 -3
  150. package/src/speech-synthesizer/SpeechSynthesizer.ts +109 -0
  151. package/src/testing/MockMusicGenerator.ts +28 -18
  152. package/src/testing/MockProvider.ts +21 -20
  153. package/src/testing/MockSandbox.ts +342 -0
  154. package/src/testing/MockSpeechSynthesizer.ts +137 -77
  155. package/src/tool/{Resolvers.test.ts → Approval.test.ts} +45 -54
  156. package/src/tool/{Resolvers.ts → Approval.ts} +25 -25
  157. package/src/tool/HistoryCheck.ts +10 -13
  158. package/src/tool/Tool.ts +13 -15
  159. package/src/tool/ToolEvent.ts +7 -7
  160. package/src/tool/{Outcome.ts → ToolResult.ts} +20 -20
  161. package/src/tool/Toolkit.test.ts +13 -13
  162. package/src/tool/Toolkit.ts +97 -73
  163. package/src/transcriber/Transcriber.test.ts +3 -3
  164. package/src/transcriber/Transcriber.ts +10 -7
  165. package/dist/Audio-BfCTGnH3.d.mts.map +0 -1
  166. package/dist/Items-BH8xUkoR.d.mts.map +0 -1
  167. package/dist/Turn-0CwCAyVe.d.mts +0 -388
  168. package/dist/Turn-0CwCAyVe.d.mts.map +0 -1
  169. package/dist/tool/Outcome.d.mts.map +0 -1
  170. package/dist/tool/Outcome.mjs +0 -61
  171. package/dist/tool/Outcome.mjs.map +0 -1
  172. package/dist/tool/Resolvers.d.mts.map +0 -1
  173. package/dist/tool/Resolvers.mjs.map +0 -1
  174. package/dist/tool/Resolvers.test.mjs.map +0 -1
  175. /package/dist/tool/{Resolvers.test.d.mts → Approval.test.d.mts} +0 -0
@@ -1,12 +1,12 @@
1
1
  import { Context, Effect, Layer, Schema, Stream } from "effect"
2
2
  import { describe, expect, expectTypeOf, it } from "vitest"
3
- import type { FunctionCall } from "../domain/Items.js"
3
+ import type { ToolCall } from "../domain/Items.js"
4
4
  import { isOutput } from "./ToolEvent.js"
5
- import { isValue } from "./Outcome.js"
5
+ import { isOk } from "./ToolResult.js"
6
6
  import * as Tool from "./Tool.js"
7
7
  import * as Toolkit from "./Toolkit.js"
8
8
 
9
- describe("Toolkit.toDescriptors", () => {
9
+ describe("Tool.toDescriptors", () => {
10
10
  const GetWeatherInput = Schema.Struct({ city: Schema.String })
11
11
 
12
12
  const getWeather = Tool.make({
@@ -17,7 +17,7 @@ describe("Toolkit.toDescriptors", () => {
17
17
  })
18
18
 
19
19
  it("renders the input schema as a JSON Schema document", () => {
20
- const [desc] = Toolkit.toDescriptors(Toolkit.make([getWeather]))
20
+ const [desc] = Tool.toDescriptors([getWeather])
21
21
  expect(desc?.name).toBe("get_weather")
22
22
  expect(desc?.description).toBe("Look up the current temperature for a city.")
23
23
  expect(desc?.inputSchema).toMatchObject({
@@ -41,16 +41,16 @@ describe("Toolkit.toDescriptors", () => {
41
41
  inputSchema: Tool.fromEffectSchema(GetWeatherInput),
42
42
  run: () => Effect.succeed({}),
43
43
  })
44
- const [s, l] = Toolkit.toDescriptors(Toolkit.make([strictTool, looseTool]))
44
+ const [s, l] = Tool.toDescriptors([strictTool, looseTool])
45
45
  expect(s?.strict).toBe(true)
46
46
  expect(l).not.toHaveProperty("strict")
47
47
  })
48
48
  })
49
49
 
50
- describe("Toolkit.executeAll - tools with R requirements", () => {
50
+ describe("Toolkit.run - tools with R requirements", () => {
51
51
  // Two distinct services, modelling the "typed per-tool context" use case
52
52
  // (cf. AI SDK 7's `toolsContext`). In Effect each tool declares its R, the
53
- // compiler enforces it, and `executeAll` surfaces the union for the caller
53
+ // compiler enforces it, and `run` surfaces the union for the caller
54
54
  // to provide via Layer.
55
55
  type WeatherApiKeyShape = { readonly key: string }
56
56
  class WeatherApiKey extends Context.Service<WeatherApiKey, WeatherApiKeyShape>()(
@@ -84,7 +84,7 @@ describe("Toolkit.executeAll - tools with R requirements", () => {
84
84
  }),
85
85
  })
86
86
 
87
- const call = (name: string, id: string): FunctionCall => ({
87
+ const call = (name: string, id: string): ToolCall => ({
88
88
  type: "function_call",
89
89
  call_id: id,
90
90
  name,
@@ -92,7 +92,7 @@ describe("Toolkit.executeAll - tools with R requirements", () => {
92
92
  })
93
93
 
94
94
  it("propagates each tool's R into the resulting Stream's requirements", () => {
95
- const stream = Toolkit.executeAll([getWeather, getCoords], [])
95
+ const stream = Toolkit.run([getWeather, getCoords], [])
96
96
  expectTypeOf(stream).toEqualTypeOf<
97
97
  Stream.Stream<import("./ToolEvent.js").ToolEvent, never, WeatherApiKey | GeoApiKey>
98
98
  >()
@@ -104,7 +104,7 @@ describe("Toolkit.executeAll - tools with R requirements", () => {
104
104
  Layer.succeed(GeoApiKey, { key: "geo-456" }),
105
105
  )
106
106
 
107
- const program = Toolkit.executeAll(
107
+ const program = Toolkit.run(
108
108
  [getWeather, getCoords],
109
109
  [call("get_weather", "c1"), call("get_coords", "c2")],
110
110
  ).pipe(Stream.runCollect, Effect.provide(layer))
@@ -115,11 +115,11 @@ describe("Toolkit.executeAll - tools with R requirements", () => {
115
115
 
116
116
  const w = byCall.get("c1")
117
117
  const g = byCall.get("c2")
118
- expect(w !== undefined && isValue(w) && w.value).toEqual({
118
+ expect(w !== undefined && isOk(w) && w.value).toEqual({
119
119
  source: "weather",
120
120
  key: "weather-123",
121
121
  })
122
- expect(g !== undefined && isValue(g) && g.value).toEqual({
122
+ expect(g !== undefined && isOk(g) && g.value).toEqual({
123
123
  source: "geo",
124
124
  key: "geo-456",
125
125
  })
@@ -132,7 +132,7 @@ describe("Toolkit.executeAll - tools with R requirements", () => {
132
132
  inputSchema: Tool.fromEffectSchema(Empty),
133
133
  run: () => Effect.succeed(0),
134
134
  })
135
- const stream = Toolkit.executeAll([plain], [])
135
+ const stream = Toolkit.run([plain], [])
136
136
  expectTypeOf(stream).toEqualTypeOf<
137
137
  Stream.Stream<import("./ToolEvent.js").ToolEvent, never, never>
138
138
  >()
@@ -1,66 +1,31 @@
1
1
  import { Array as Arr, Effect, Function, Ref, Schema, Stream } from "effect"
2
2
  import * as Loop from "../loop/Loop.js"
3
- import type { FunctionCall } from "../domain/Items.js"
3
+ import type { ToolCall, HistoryItem } from "../domain/Items.js"
4
+ import { appendToHistory, type Turn } from "../domain/Turn.js"
4
5
  import {
5
- type AnyKindTool,
6
+ type AnyTool,
6
7
  type AnyPlainTool,
7
8
  type AnyStreamingTool,
8
9
  isStreamingTool,
9
10
  type StreamingTool,
10
11
  type Tool,
11
- type ToolDescriptor,
12
12
  } from "./Tool.js"
13
- import { ToolResult, executionError, rejected } from "./Outcome.js"
13
+ import { ToolResult, executionError, failed, toToolCallOutput } from "./ToolResult.js"
14
14
  import { ToolEvent } from "./ToolEvent.js"
15
15
  import { isOutput } from "./ToolEvent.js"
16
16
 
17
- export type AnyTool = Tool<string, any, any, any>
18
-
19
- export type Toolkit<Tools extends ReadonlyArray<AnyTool>> = {
20
- readonly tools: Tools
21
- }
22
-
23
- export type ToolsR<Tools extends ReadonlyArray<AnyTool>> =
24
- Tools[number] extends Tool<any, any, any, infer R> ? R : never
25
-
26
17
  /**
27
18
  * Union of every tool's `R` requirements in a mixed plain + streaming array.
28
- * Used by `executeAll` to surface the services tools need at the recipe
29
- * level, so the loop's stream type carries them through to `Effect.provide`.
19
+ * Used by `run` to surface the services tools need at the recipe level, so
20
+ * the loop's stream type carries them through to `Effect.provide`.
30
21
  */
31
- export type ToolKindR<Tools extends ReadonlyArray<AnyKindTool<any>>> =
22
+ export type ToolKindR<Tools extends ReadonlyArray<AnyTool<any>>> =
32
23
  Tools[number] extends StreamingTool<any, any, any, any, infer R>
33
24
  ? R
34
25
  : Tools[number] extends Tool<any, any, any, infer R>
35
26
  ? R
36
27
  : never
37
28
 
38
- export const make = <const Tools extends ReadonlyArray<AnyTool>>(tools: Tools): Toolkit<Tools> => ({
39
- tools,
40
- })
41
-
42
- /**
43
- * Render every tool in a toolkit to a provider-agnostic descriptor.
44
- * `inputSchema` is the JSON Schema document produced by the tool's
45
- * Standard Schema converter (draft 2020-12).
46
- */
47
- export const toDescriptors = <Tools extends ReadonlyArray<AnyTool>>(
48
- toolkit: Toolkit<Tools>,
49
- ): ReadonlyArray<ToolDescriptor> =>
50
- toolkit.tools.map((tool) => {
51
- const inputSchema = tool.inputSchema["~standard"].jsonSchema.input({
52
- target: "draft-2020-12",
53
- })
54
- return tool.strict !== undefined
55
- ? {
56
- name: tool.name,
57
- description: tool.description,
58
- inputSchema,
59
- strict: tool.strict,
60
- }
61
- : { name: tool.name, description: tool.description, inputSchema }
62
- })
63
-
64
29
  // ---------------------------------------------------------------------------
65
30
  // Tool executor. Streams `ToolEvent`s in real time and dispatches streaming
66
31
  // and plain tools uniformly. Policy stays outside this module: callers pass
@@ -72,9 +37,9 @@ export type ExecuteOptions = {
72
37
  }
73
38
 
74
39
  /** Execute every provided call. Approval/rejection policy belongs upstream. */
75
- export const executeAll = <Tools extends ReadonlyArray<AnyKindTool<any>>>(
40
+ export const run = <Tools extends ReadonlyArray<AnyTool<any>>>(
76
41
  tools: Tools,
77
- calls: ReadonlyArray<FunctionCall>,
42
+ calls: ReadonlyArray<ToolCall>,
78
43
  options?: ExecuteOptions,
79
44
  ): Stream.Stream<ToolEvent, never, ToolKindR<Tools>> =>
80
45
  Stream.fromIterable(calls).pipe(
@@ -83,16 +48,16 @@ export const executeAll = <Tools extends ReadonlyArray<AnyKindTool<any>>>(
83
48
  }),
84
49
  )
85
50
 
86
- const valueResult = (call: FunctionCall, tool: string, value: unknown): ToolResult =>
87
- ToolResult.Value({
51
+ const okResult = (call: ToolCall, tool: string, value: unknown): ToolResult =>
52
+ ToolResult.Ok({
88
53
  call_id: call.call_id,
89
54
  tool,
90
55
  value,
91
56
  })
92
57
 
93
58
  const runOne = <R>(
94
- tools: ReadonlyArray<AnyKindTool<R>>,
95
- call: FunctionCall,
59
+ tools: ReadonlyArray<AnyTool<R>>,
60
+ call: ToolCall,
96
61
  ): Stream.Stream<ToolEvent, never, R> => {
97
62
  const tool = tools.find((t) => t.name === call.name)
98
63
  if (tool === undefined) {
@@ -100,7 +65,7 @@ const runOne = <R>(
100
65
  // still execute. LLMs hallucinate tool names; MCP tools come and go.
101
66
  return Stream.succeed(
102
67
  ToolEvent.Output({
103
- result: rejected(call, "unknown_tool", `No tool registered with name "${call.name}"`),
68
+ result: failed(call, "unknown_tool", `No tool registered with name "${call.name}"`),
104
69
  }),
105
70
  )
106
71
  }
@@ -110,10 +75,7 @@ const runOne = <R>(
110
75
 
111
76
  const parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))
112
77
 
113
- const runPlain = <R>(
114
- tool: AnyPlainTool<R>,
115
- call: FunctionCall,
116
- ): Stream.Stream<ToolEvent, never, R> =>
78
+ const runPlain = <R>(tool: AnyPlainTool<R>, call: ToolCall): Stream.Stream<ToolEvent, never, R> =>
117
79
  Stream.fromEffect(
118
80
  Effect.gen(function* () {
119
81
  const parsed = yield* parseJsonUnknown(call.arguments).pipe(
@@ -127,7 +89,7 @@ const runPlain = <R>(
127
89
  return executionError(call, "Tool input failed schema validation")
128
90
  }
129
91
  const output = yield* tool.run(validated.value)
130
- return valueResult(call, tool.name, output)
92
+ return okResult(call, tool.name, output)
131
93
  }).pipe(
132
94
  Effect.catchCause(() => Effect.succeed(executionError(call, "Tool execution failed"))),
133
95
  Effect.map((result) => ToolEvent.Output({ result })),
@@ -136,7 +98,7 @@ const runPlain = <R>(
136
98
 
137
99
  const runStreaming = <R>(
138
100
  tool: AnyStreamingTool<R>,
139
- call: FunctionCall,
101
+ call: ToolCall,
140
102
  ): Stream.Stream<ToolEvent, never, R> =>
141
103
  Stream.unwrap(
142
104
  Effect.gen(function* () {
@@ -156,13 +118,13 @@ const runStreaming = <R>(
156
118
  }
157
119
 
158
120
  // Real-time: tap each event into a Ref as it flows; emit one
159
- // Intermediate per event; then concat one synthetic Output element
121
+ // Progress per event; then concat one synthetic Output element
160
122
  // built from the accumulated Ref via `finalize`.
161
123
  const ref = yield* Ref.make<Array<unknown>>([])
162
- const intermediates = tool.run(validated.value).pipe(
124
+ const progress = tool.run(validated.value).pipe(
163
125
  Stream.tap((event) => Ref.update(ref, Arr.append(event))),
164
126
  Stream.map((data) =>
165
- ToolEvent.Intermediate({
127
+ ToolEvent.Progress({
166
128
  call_id: call.call_id,
167
129
  tool: tool.name,
168
130
  data,
@@ -173,12 +135,12 @@ const runStreaming = <R>(
173
135
  Ref.get(ref).pipe(
174
136
  Effect.map((events) =>
175
137
  ToolEvent.Output({
176
- result: valueResult(call, tool.name, tool.finalize(events)),
138
+ result: okResult(call, tool.name, tool.finalize(events)),
177
139
  }),
178
140
  ),
179
141
  ),
180
142
  )
181
- return intermediates.pipe(Stream.concat(output))
143
+ return progress.pipe(Stream.concat(output))
182
144
  }),
183
145
  ).pipe(
184
146
  Stream.catchCause(() =>
@@ -191,35 +153,97 @@ const runStreaming = <R>(
191
153
  )
192
154
 
193
155
  // ---------------------------------------------------------------------------
194
- // `continueWith` - bridge from a `Stream<ToolEvent>` to the loop's emit
195
- // shape. Drains the stream to the consumer in real-time, taps every
156
+ // `continueWithResults` - bridge from a `Stream<ToolEvent>` to the loop's
157
+ // emit shape. Drains the stream to the consumer in real-time, taps every
196
158
  // `Output` into an internal Ref, and at end-of-stream emits
197
159
  // `Loop.next(build(results))`. Recipe never sees the Ref.
198
160
  //
199
- // Dual: data-first `continueWith(stream, build)` and data-last
200
- // `stream.pipe(continueWith(build))` both work.
161
+ // Dual: data-first `continueWithResults(stream, build)` and data-last
162
+ // `stream.pipe(continueWithResults(build))` both work.
201
163
  // ---------------------------------------------------------------------------
202
164
 
203
- export const continueWith: {
165
+ /**
166
+ * Append a completed turn plus tool results to a state's history, converting
167
+ * the results to wire-format `ToolCallOutput`s. Curried so it slots directly
168
+ * into `continueWithResults`:
169
+ *
170
+ * Toolkit.run(tools, calls).pipe(
171
+ * Toolkit.continueWithResults(Toolkit.appendToolResults(state, turn)),
172
+ * )
173
+ *
174
+ * Equivalent to `appendToHistory(state, turn, results.map(toToolCallOutput))`
175
+ * — the helper just hides the wire-conversion step.
176
+ */
177
+ export const appendToolResults =
178
+ <S extends { readonly history: ReadonlyArray<HistoryItem> }>(state: S, turn: Turn) =>
179
+ (results: ReadonlyArray<ToolResult>): S =>
180
+ appendToHistory(state, turn, results.map(toToolCallOutput))
181
+
182
+ /**
183
+ * Drain a `Stream<ToolEvent>` and return the accumulated `ToolResult`s
184
+ * from every `Output` event. One-shot — the type is `Effect<results>`,
185
+ * not a one-element stream — so it composes naturally with `Effect.map`
186
+ * to build state.
187
+ *
188
+ * Counterpart to `continueWithResults`: `continueWithResults` bundles
189
+ * drain + emit Next into one bridge; this one is the right arm of a
190
+ * broadcast/fork-and-merge for callers that want to vary the left arm
191
+ * (e.g., filter/tap ToolEvents before forwarding).
192
+ */
193
+ export const collectResults = <E, R>(
194
+ stream: Stream.Stream<ToolEvent, E, R>,
195
+ ): Effect.Effect<ReadonlyArray<ToolResult>, E, R> =>
196
+ stream.pipe(
197
+ Stream.filter(isOutput),
198
+ Stream.map((e) => e.result),
199
+ Stream.runCollect,
200
+ )
201
+
202
+ /**
203
+ * Bridge from a `Stream<ToolEvent>` to the loop's emit shape. Forwards
204
+ * every ToolEvent downstream as a `Loop.value` and at end-of-stream emits
205
+ * one `Loop.next(build(results))` carrying the accumulated `ToolResult`s
206
+ * from terminal `Output` events.
207
+ *
208
+ * Conceptually a broadcast fork (one arm passes events through, the other
209
+ * drains them into a state). Implemented as a single-pass Ref tap + concat
210
+ * for zero buffering — the broadcast version is observationally equivalent
211
+ * but holds events in a PubSub until both arms drain. The public primitives
212
+ * (`Loop.value`, `collectResults`, `Loop.next`) compose the same pattern
213
+ * when you need to vary an arm.
214
+ *
215
+ * Dual: data-first `continueWithResults(stream, build)` and data-last
216
+ * `stream.pipe(continueWithResults(build))` both work.
217
+ */
218
+ export const continueWithResults: {
204
219
  <S>(
205
220
  build: (results: ReadonlyArray<ToolResult>) => S,
206
221
  ): <R>(
207
222
  stream: Stream.Stream<ToolEvent, never, R>,
208
- ) => Stream.Stream<Loop.Event<ToolEvent, S>, never, R>
223
+ ) => Stream.Stream<Loop.Step<ToolEvent, S>, never, R>
209
224
  <S, R>(
210
225
  stream: Stream.Stream<ToolEvent, never, R>,
211
226
  build: (results: ReadonlyArray<ToolResult>) => S,
212
- ): Stream.Stream<Loop.Event<ToolEvent, S>, never, R>
227
+ ): Stream.Stream<Loop.Step<ToolEvent, S>, never, R>
213
228
  } = Function.dual(
214
229
  2,
215
230
  <S, R>(
216
231
  stream: Stream.Stream<ToolEvent, never, R>,
217
232
  build: (results: ReadonlyArray<ToolResult>) => S,
218
- ): Stream.Stream<Loop.Event<ToolEvent, S>, never, R> =>
219
- Loop.nextAfterFold(
220
- stream,
221
- [] as ReadonlyArray<ToolResult>,
222
- (acc, e) => (isOutput(e) ? Arr.append(acc, e.result) : acc),
223
- build,
233
+ ): Stream.Stream<Loop.Step<ToolEvent, S>, never, R> =>
234
+ Stream.unwrap(
235
+ Effect.gen(function* () {
236
+ const ref = yield* Ref.make<ReadonlyArray<ToolResult>>([])
237
+ const tapped = stream.pipe(
238
+ Stream.tap((e) =>
239
+ isOutput(e) ? Ref.update(ref, (acc) => Arr.append(acc, e.result)) : Effect.void,
240
+ ),
241
+ Stream.map(Loop.value),
242
+ )
243
+ const continuation = Stream.unwrap(
244
+ Ref.get(ref).pipe(Effect.map((acc) => Loop.next(build(acc)))),
245
+ )
246
+ return tapped.pipe(Stream.concat(continuation))
247
+ }),
224
248
  ),
225
249
  )
@@ -1,4 +1,4 @@
1
- import { Effect, Stream } from "effect"
1
+ import { Duration, Effect, Stream } from "effect"
2
2
  import { describe, expect, expectTypeOf, it } from "vitest"
3
3
  import type * as AiError from "../domain/AiError.js"
4
4
  import type { TranscriptEvent, TranscriptResult } from "../domain/Transcript.js"
@@ -8,7 +8,7 @@ import * as Transcriber from "./Transcriber.js"
8
8
  describe("Transcriber.transcribe", () => {
9
9
  it("returns the scripted TranscriptResult", async () => {
10
10
  const mock = MockTranscriber.layer({
11
- transcripts: [{ text: "hello world", durationSeconds: 1.23 }],
11
+ transcripts: [{ text: "hello world", duration: Duration.seconds(1.23) }],
12
12
  })
13
13
  const program = Transcriber.transcribe({
14
14
  audio: { _tag: "bytes", bytes: new Uint8Array([0]), mimeType: "audio/wav" },
@@ -16,7 +16,7 @@ describe("Transcriber.transcribe", () => {
16
16
  })
17
17
  const result = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))
18
18
  expect(result.text).toBe("hello world")
19
- expect(result.durationSeconds).toBe(1.23)
19
+ expect(result.duration).toStrictEqual(Duration.seconds(1.23))
20
20
  })
21
21
 
22
22
  it("records each transcribe call", async () => {
@@ -15,12 +15,13 @@ export type CommonTranscribeRequest = {
15
15
  readonly model: string
16
16
  /** ISO-639-1 / BCP-47. Omit for autodetection (where supported). */
17
17
  readonly language?: string
18
- /**
19
- * Vocab biasing. Single-string covers OpenAI/Whisper-style prompts;
20
- * `terms[]` covers Deepgram `keyterm`, Google adaptation phrases, AWS
21
- * `vocabularyName`. Providers ignore what they don't support.
22
- */
23
- readonly prompt?: string | { readonly terms: ReadonlyArray<string> }
18
+ /** Free-form context / style hint (Whisper-style prose). OpenAI honors
19
+ * it; providers without a prompt field `warnDropped`. */
20
+ readonly prompt?: string
21
+ /** Vocabulary biasing — discrete terms to boost (names, jargon). Maps to
22
+ * Deepgram `keyterm`, ElevenLabs `keyterms`, Google `adaptation`,
23
+ * Inworld `prompts`; others `warnDropped`. */
24
+ readonly biasingTerms?: ReadonlyArray<string>
24
25
  readonly diarization?: boolean
25
26
  readonly wordTimestamps?: boolean
26
27
  }
@@ -28,7 +29,9 @@ export type CommonTranscribeRequest = {
28
29
  /**
29
30
  * Streaming-transcription request. `inputFormat` declares what the
30
31
  * bytes in the input stream will look like — providers reject
31
- * mismatches at stream startup with `AiError.InvalidRequest`.
32
+ * formats they can't ingest at stream startup with
33
+ * `AiError.Unsupported` (a per-Layer capability gap, not a wire-shape
34
+ * mismatch).
32
35
  */
33
36
  export type CommonStreamTranscribeRequest = Omit<CommonTranscribeRequest, "audio"> & {
34
37
  readonly inputFormat: AudioFormat
@@ -1 +0,0 @@
1
- {"version":3,"file":"Audio-BfCTGnH3.d.mts","names":[],"sources":["../src/domain/Audio.ts"],"mappings":";;;;;;;;;;;;;;KAUY,aAAA;;;;;AAAZ;;;;;KA0BY,WAAA,GACR,QAAA,CAAS,aAAA,IACT,WAAA,CAAY,aAAA,IACZ,UAAA,CAAW,aAAA;AAAA,cAEF,UAAA,GAAc,CAAA,EAAG,WAAA,KAAc,CAAA,IAAK,QAAA,CAAS,aAAA;AAAA,cAC7C,aAAA,GAAiB,CAAA,EAAG,WAAA,KAAc,CAAA,IAAK,WAAA,CAAY,aAAA;AAAA,cAEnD,YAAA,GAAgB,CAAA,EAAG,WAAA,KAAc,CAAA,IAAK,UAAA,CAAW,aAAA;;;;;;;KAQlD,WAAA;EAAA,SACD,SAAA;EAAA,SACA,QAAA;EAAA,SAUA,UAAA,wDA1BK;EAAA,SA4BL,OAAA;EAAA,SACA,QAAA;AAAA;;AA1BX;;;;KAkCY,UAAA;EAAA,SACD,KAAA,EAAO,UAAA;AAAA;;;;;KAON,SAAA;EAAA,SACD,MAAA,EAAQ,WAAA;EAAA,SACR,KAAA,EAAO,UAAA;EAAA,SACP,eAAA;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"Items-BH8xUkoR.d.mts","names":[],"sources":["../src/domain/Items.ts"],"mappings":"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"}