@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.
- package/README.md +1 -1
- package/dist/{Audio-BfCTGnH3.d.mts → Audio-DPlTsNdz.d.mts} +3 -2
- package/dist/Audio-DPlTsNdz.d.mts.map +1 -0
- package/dist/{Items-BH8xUkoR.d.mts → Items-D8UaXQx_.d.mts} +15 -15
- package/dist/Items-D8UaXQx_.d.mts.map +1 -0
- package/dist/SandboxError-BvKypkJm.d.mts +158 -0
- package/dist/SandboxError-BvKypkJm.d.mts.map +1 -0
- package/dist/{Tool-87ViKCCO.d.mts → Tool-CiFXYjmR.d.mts} +11 -11
- package/dist/{Tool-87ViKCCO.d.mts.map → Tool-CiFXYjmR.d.mts.map} +1 -1
- package/dist/capabilities/Capabilities.d.mts +78 -0
- package/dist/capabilities/Capabilities.d.mts.map +1 -0
- package/dist/capabilities/Capabilities.mjs +65 -0
- package/dist/capabilities/Capabilities.mjs.map +1 -0
- package/dist/domain/Audio.d.mts +1 -1
- package/dist/domain/Audio.mjs.map +1 -1
- package/dist/domain/Items.d.mts +2 -2
- package/dist/domain/Items.mjs +15 -15
- package/dist/domain/Items.mjs.map +1 -1
- package/dist/domain/Music.d.mts +131 -72
- package/dist/domain/Music.d.mts.map +1 -1
- package/dist/domain/Music.mjs +36 -9
- package/dist/domain/Music.mjs.map +1 -1
- package/dist/domain/Transcript.d.mts +8 -2
- package/dist/domain/Transcript.d.mts.map +1 -1
- package/dist/domain/Transcript.mjs.map +1 -1
- package/dist/domain/Turn.d.mts +388 -2
- package/dist/domain/Turn.d.mts.map +1 -0
- package/dist/domain/Turn.mjs +13 -13
- package/dist/domain/Turn.mjs.map +1 -1
- package/dist/embedding-model/Embedding.d.mts +12 -2
- package/dist/embedding-model/Embedding.d.mts.map +1 -1
- package/dist/embedding-model/Embedding.mjs.map +1 -1
- package/dist/embedding-model/EmbeddingModel.d.mts +36 -23
- package/dist/embedding-model/EmbeddingModel.d.mts.map +1 -1
- package/dist/embedding-model/EmbeddingModel.mjs +20 -1
- package/dist/embedding-model/EmbeddingModel.mjs.map +1 -1
- package/dist/embedding-model/EmbeddingModel.test.mjs +15 -3
- package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
- package/dist/index.d.mts +13 -7
- package/dist/index.mjs +9 -3
- package/dist/language-model/LanguageModel.d.mts +5 -5
- package/dist/language-model/LanguageModel.d.mts.map +1 -1
- package/dist/language-model/LanguageModel.mjs.map +1 -1
- package/dist/loop/Loop.d.mts +37 -90
- package/dist/loop/Loop.d.mts.map +1 -1
- package/dist/loop/Loop.mjs +39 -93
- package/dist/loop/Loop.mjs.map +1 -1
- package/dist/loop/Loop.test.mjs +46 -54
- package/dist/loop/Loop.test.mjs.map +1 -1
- package/dist/music-generator/MusicGenerator.d.mts +48 -25
- package/dist/music-generator/MusicGenerator.d.mts.map +1 -1
- package/dist/music-generator/MusicGenerator.mjs +17 -9
- package/dist/music-generator/MusicGenerator.mjs.map +1 -1
- package/dist/music-generator/MusicGenerator.test.mjs +81 -56
- package/dist/music-generator/MusicGenerator.test.mjs.map +1 -1
- package/dist/sandbox/Memory.d.mts +53 -0
- package/dist/sandbox/Memory.d.mts.map +1 -0
- package/dist/sandbox/Memory.mjs +68 -0
- package/dist/sandbox/Memory.mjs.map +1 -0
- package/dist/sandbox/Sandbox.d.mts +717 -0
- package/dist/sandbox/Sandbox.d.mts.map +1 -0
- package/dist/sandbox/Sandbox.mjs +192 -0
- package/dist/sandbox/Sandbox.mjs.map +1 -0
- package/dist/sandbox/SandboxError.d.mts +2 -0
- package/dist/sandbox/SandboxError.mjs +89 -0
- package/dist/sandbox/SandboxError.mjs.map +1 -0
- package/dist/sandbox/SandboxImage.d.mts +28 -0
- package/dist/sandbox/SandboxImage.d.mts.map +1 -0
- package/dist/sandbox/SandboxImage.mjs +32 -0
- package/dist/sandbox/SandboxImage.mjs.map +1 -0
- package/dist/sandbox/SandboxNetwork.d.mts +34 -0
- package/dist/sandbox/SandboxNetwork.d.mts.map +1 -0
- package/dist/sandbox/SandboxNetwork.mjs +36 -0
- package/dist/sandbox/SandboxNetwork.mjs.map +1 -0
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +84 -3
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs +32 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +59 -5
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -1
- package/dist/testing/MockMusicGenerator.d.mts +12 -7
- package/dist/testing/MockMusicGenerator.d.mts.map +1 -1
- package/dist/testing/MockMusicGenerator.mjs +4 -3
- package/dist/testing/MockMusicGenerator.mjs.map +1 -1
- package/dist/testing/MockProvider.d.mts +3 -3
- package/dist/testing/MockProvider.d.mts.map +1 -1
- package/dist/testing/MockProvider.mjs.map +1 -1
- package/dist/testing/MockSandbox.d.mts +604 -0
- package/dist/testing/MockSandbox.d.mts.map +1 -0
- package/dist/testing/MockSandbox.mjs +160 -0
- package/dist/testing/MockSandbox.mjs.map +1 -0
- package/dist/testing/MockSpeechSynthesizer.d.mts +27 -12
- package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -1
- package/dist/testing/MockSpeechSynthesizer.mjs +75 -51
- package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -1
- package/dist/tool/{Resolvers.d.mts → Approval.d.mts} +27 -27
- package/dist/tool/Approval.d.mts.map +1 -0
- package/dist/tool/{Resolvers.mjs → Approval.mjs} +18 -18
- package/dist/tool/Approval.mjs.map +1 -0
- package/dist/tool/{Resolvers.test.mjs → Approval.test.mjs} +40 -40
- package/dist/tool/Approval.test.mjs.map +1 -0
- package/dist/tool/HistoryCheck.d.mts +6 -6
- package/dist/tool/HistoryCheck.d.mts.map +1 -1
- package/dist/tool/HistoryCheck.mjs +5 -5
- package/dist/tool/HistoryCheck.mjs.map +1 -1
- package/dist/tool/Tool.d.mts +2 -2
- package/dist/tool/Tool.mjs +4 -4
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/ToolEvent.d.mts +19 -19
- package/dist/tool/ToolEvent.d.mts.map +1 -1
- package/dist/tool/ToolEvent.mjs +8 -8
- package/dist/tool/ToolEvent.mjs.map +1 -1
- package/dist/tool/{Outcome.d.mts → ToolResult.d.mts} +29 -29
- package/dist/tool/ToolResult.d.mts.map +1 -0
- package/dist/tool/ToolResult.mjs +61 -0
- package/dist/tool/ToolResult.mjs.map +1 -0
- package/dist/tool/Toolkit.d.mts +58 -26
- package/dist/tool/Toolkit.d.mts.map +1 -1
- package/dist/tool/Toolkit.mjs +62 -34
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/tool/Toolkit.test.mjs +13 -13
- package/dist/tool/Toolkit.test.mjs.map +1 -1
- package/dist/transcriber/Transcriber.d.mts +11 -10
- package/dist/transcriber/Transcriber.d.mts.map +1 -1
- package/dist/transcriber/Transcriber.mjs.map +1 -1
- package/dist/transcriber/Transcriber.test.mjs +3 -3
- package/dist/transcriber/Transcriber.test.mjs.map +1 -1
- package/package.json +35 -7
- package/src/capabilities/Capabilities.ts +81 -0
- package/src/domain/Audio.ts +2 -1
- package/src/domain/Items.ts +9 -9
- package/src/domain/Music.ts +142 -68
- package/src/domain/Transcript.ts +7 -1
- package/src/domain/Turn.ts +16 -16
- package/src/embedding-model/Embedding.ts +11 -0
- package/src/embedding-model/EmbeddingModel.test.ts +15 -4
- package/src/embedding-model/EmbeddingModel.ts +48 -21
- package/src/index.ts +8 -2
- package/src/language-model/LanguageModel.ts +3 -3
- package/src/loop/Loop.test.ts +93 -91
- package/src/loop/Loop.ts +72 -170
- package/src/music-generator/MusicGenerator.test.ts +78 -53
- package/src/music-generator/MusicGenerator.ts +65 -24
- package/src/sandbox/Memory.ts +128 -0
- package/src/sandbox/Sandbox.ts +639 -0
- package/src/sandbox/SandboxError.ts +140 -0
- package/src/sandbox/SandboxImage.ts +26 -0
- package/src/sandbox/SandboxNetwork.ts +34 -0
- package/src/speech-synthesizer/SpeechSynthesizer.test.ts +92 -3
- package/src/speech-synthesizer/SpeechSynthesizer.ts +109 -0
- package/src/testing/MockMusicGenerator.ts +28 -18
- package/src/testing/MockProvider.ts +21 -20
- package/src/testing/MockSandbox.ts +342 -0
- package/src/testing/MockSpeechSynthesizer.ts +137 -77
- package/src/tool/{Resolvers.test.ts → Approval.test.ts} +45 -54
- package/src/tool/{Resolvers.ts → Approval.ts} +25 -25
- package/src/tool/HistoryCheck.ts +10 -13
- package/src/tool/Tool.ts +13 -15
- package/src/tool/ToolEvent.ts +7 -7
- package/src/tool/{Outcome.ts → ToolResult.ts} +20 -20
- package/src/tool/Toolkit.test.ts +13 -13
- package/src/tool/Toolkit.ts +97 -73
- package/src/transcriber/Transcriber.test.ts +3 -3
- package/src/transcriber/Transcriber.ts +10 -7
- package/dist/Audio-BfCTGnH3.d.mts.map +0 -1
- package/dist/Items-BH8xUkoR.d.mts.map +0 -1
- package/dist/Turn-0CwCAyVe.d.mts +0 -388
- package/dist/Turn-0CwCAyVe.d.mts.map +0 -1
- package/dist/tool/Outcome.d.mts.map +0 -1
- package/dist/tool/Outcome.mjs +0 -61
- package/dist/tool/Outcome.mjs.map +0 -1
- package/dist/tool/Resolvers.d.mts.map +0 -1
- package/dist/tool/Resolvers.mjs.map +0 -1
- package/dist/tool/Resolvers.test.mjs.map +0 -1
- /package/dist/tool/{Resolvers.test.d.mts → Approval.test.d.mts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolkit.mjs","names":["Arr","Loop.nextAfterFold"],"sources":["../../src/tool/Toolkit.ts"],"sourcesContent":["import { Array as Arr, Effect, Function, Ref, Schema, Stream } from \"effect\"\nimport * as Loop from \"../loop/Loop.js\"\nimport type { FunctionCall } from \"../domain/Items.js\"\nimport {\n type AnyKindTool,\n type AnyPlainTool,\n type AnyStreamingTool,\n isStreamingTool,\n type StreamingTool,\n type Tool,\n type ToolDescriptor,\n} from \"./Tool.js\"\nimport { ToolResult, executionError, rejected } from \"./Outcome.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\nimport { isOutput } from \"./ToolEvent.js\"\n\nexport type AnyTool = Tool<string, any, any, any>\n\nexport type Toolkit<Tools extends ReadonlyArray<AnyTool>> = {\n readonly tools: Tools\n}\n\nexport type ToolsR<Tools extends ReadonlyArray<AnyTool>> =\n Tools[number] extends Tool<any, any, any, infer R> ? R : never\n\n/**\n * Union of every tool's `R` requirements in a mixed plain + streaming array.\n * Used by `executeAll` to surface the services tools need at the recipe\n * level, so the loop's stream type carries them through to `Effect.provide`.\n */\nexport type ToolKindR<Tools extends ReadonlyArray<AnyKindTool<any>>> =\n Tools[number] extends StreamingTool<any, any, any, any, infer R>\n ? R\n : Tools[number] extends Tool<any, any, any, infer R>\n ? R\n : never\n\nexport const make = <const Tools extends ReadonlyArray<AnyTool>>(tools: Tools): Toolkit<Tools> => ({\n tools,\n})\n\n/**\n * Render every tool in a toolkit to a provider-agnostic descriptor.\n * `inputSchema` is the JSON Schema document produced by the tool's\n * Standard Schema converter (draft 2020-12).\n */\nexport const toDescriptors = <Tools extends ReadonlyArray<AnyTool>>(\n toolkit: Toolkit<Tools>,\n): ReadonlyArray<ToolDescriptor> =>\n toolkit.tools.map((tool) => {\n const inputSchema = tool.inputSchema[\"~standard\"].jsonSchema.input({\n target: \"draft-2020-12\",\n })\n return tool.strict !== undefined\n ? {\n name: tool.name,\n description: tool.description,\n inputSchema,\n strict: tool.strict,\n }\n : { name: tool.name, description: tool.description, inputSchema }\n })\n\n// ---------------------------------------------------------------------------\n// Tool executor. Streams `ToolEvent`s in real time and dispatches streaming\n// and plain tools uniformly. Policy stays outside this module: callers pass\n// only the calls they have already decided should run.\n// ---------------------------------------------------------------------------\n\nexport type ExecuteOptions = {\n readonly concurrency?: number | \"unbounded\"\n}\n\n/** Execute every provided call. Approval/rejection policy belongs upstream. */\nexport const executeAll = <Tools extends ReadonlyArray<AnyKindTool<any>>>(\n tools: Tools,\n calls: ReadonlyArray<FunctionCall>,\n options?: ExecuteOptions,\n): Stream.Stream<ToolEvent, never, ToolKindR<Tools>> =>\n Stream.fromIterable(calls).pipe(\n Stream.flatMap((call) => runOne(tools, call), {\n concurrency: options?.concurrency ?? \"unbounded\",\n }),\n )\n\nconst valueResult = (call: FunctionCall, tool: string, value: unknown): ToolResult =>\n ToolResult.Value({\n call_id: call.call_id,\n tool,\n value,\n })\n\nconst runOne = <R>(\n tools: ReadonlyArray<AnyKindTool<R>>,\n call: FunctionCall,\n): Stream.Stream<ToolEvent, never, R> => {\n const tool = tools.find((t) => t.name === call.name)\n if (tool === undefined) {\n // Graceful: emit a synthetic Failure so OTHER calls in this turn\n // still execute. LLMs hallucinate tool names; MCP tools come and go.\n return Stream.succeed(\n ToolEvent.Output({\n result: rejected(call, \"unknown_tool\", `No tool registered with name \"${call.name}\"`),\n }),\n )\n }\n if (isStreamingTool(tool)) return runStreaming(tool, call)\n return runPlain(tool, call)\n}\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\nconst runPlain = <R>(\n tool: AnyPlainTool<R>,\n call: FunctionCall,\n): Stream.Stream<ToolEvent, never, R> =>\n Stream.fromEffect(\n Effect.gen(function* () {\n const parsed = yield* parseJsonUnknown(call.arguments).pipe(\n Effect.mapError(() => \"json_parse_error\" as const),\n )\n const validated = yield* Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: () => \"validation_threw\" as const,\n })\n if (validated.issues !== undefined) {\n return executionError(call, \"Tool input failed schema validation\")\n }\n const output = yield* tool.run(validated.value)\n return valueResult(call, tool.name, output)\n }).pipe(\n Effect.catchCause(() => Effect.succeed(executionError(call, \"Tool execution failed\"))),\n Effect.map((result) => ToolEvent.Output({ result })),\n ),\n )\n\nconst runStreaming = <R>(\n tool: AnyStreamingTool<R>,\n call: FunctionCall,\n): Stream.Stream<ToolEvent, never, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const parsed = yield* parseJsonUnknown(call.arguments).pipe(\n Effect.mapError(() => \"json_parse_error\" as const),\n )\n const validated = yield* Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: () => \"validation_threw\" as const,\n })\n if (validated.issues !== undefined) {\n return Stream.succeed<ToolEvent>(\n ToolEvent.Output({\n result: executionError(call, \"Tool input failed schema validation\"),\n }),\n )\n }\n\n // Real-time: tap each event into a Ref as it flows; emit one\n // Intermediate per event; then concat one synthetic Output element\n // built from the accumulated Ref via `finalize`.\n const ref = yield* Ref.make<Array<unknown>>([])\n const intermediates = tool.run(validated.value).pipe(\n Stream.tap((event) => Ref.update(ref, Arr.append(event))),\n Stream.map((data) =>\n ToolEvent.Intermediate({\n call_id: call.call_id,\n tool: tool.name,\n data,\n }),\n ),\n )\n const output = Stream.fromEffect(\n Ref.get(ref).pipe(\n Effect.map((events) =>\n ToolEvent.Output({\n result: valueResult(call, tool.name, tool.finalize(events)),\n }),\n ),\n ),\n )\n return intermediates.pipe(Stream.concat(output))\n }),\n ).pipe(\n Stream.catchCause(() =>\n Stream.succeed(\n ToolEvent.Output({\n result: executionError(call, \"Tool execution failed\"),\n }),\n ),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// `continueWith` - bridge from a `Stream<ToolEvent>` to the loop's emit\n// shape. Drains the stream to the consumer in real-time, taps every\n// `Output` into an internal Ref, and at end-of-stream emits\n// `Loop.next(build(results))`. Recipe never sees the Ref.\n//\n// Dual: data-first `continueWith(stream, build)` and data-last\n// `stream.pipe(continueWith(build))` both work.\n// ---------------------------------------------------------------------------\n\nexport const continueWith: {\n <S>(\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): <R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n ) => Stream.Stream<Loop.Event<ToolEvent, S>, never, R>\n <S, R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Event<ToolEvent, S>, never, R>\n} = Function.dual(\n 2,\n <S, R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Event<ToolEvent, S>, never, R> =>\n Loop.nextAfterFold(\n stream,\n [] as ReadonlyArray<ToolResult>,\n (acc, e) => (isOutput(e) ? Arr.append(acc, e.result) : acc),\n build,\n ),\n)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Toolkit.mjs","names":["Arr","Loop.value","Loop.next"],"sources":["../../src/tool/Toolkit.ts"],"sourcesContent":["import { Array as Arr, Effect, Function, Ref, Schema, Stream } from \"effect\"\nimport * as Loop from \"../loop/Loop.js\"\nimport type { ToolCall, HistoryItem } from \"../domain/Items.js\"\nimport { appendToHistory, type Turn } from \"../domain/Turn.js\"\nimport {\n type AnyTool,\n type AnyPlainTool,\n type AnyStreamingTool,\n isStreamingTool,\n type StreamingTool,\n type Tool,\n} from \"./Tool.js\"\nimport { ToolResult, executionError, failed, toToolCallOutput } from \"./ToolResult.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\nimport { isOutput } from \"./ToolEvent.js\"\n\n/**\n * Union of every tool's `R` requirements in a mixed plain + streaming array.\n * Used by `run` to surface the services tools need at the recipe level, so\n * the loop's stream type carries them through to `Effect.provide`.\n */\nexport type ToolKindR<Tools extends ReadonlyArray<AnyTool<any>>> =\n Tools[number] extends StreamingTool<any, any, any, any, infer R>\n ? R\n : Tools[number] extends Tool<any, any, any, infer R>\n ? R\n : never\n\n// ---------------------------------------------------------------------------\n// Tool executor. Streams `ToolEvent`s in real time and dispatches streaming\n// and plain tools uniformly. Policy stays outside this module: callers pass\n// only the calls they have already decided should run.\n// ---------------------------------------------------------------------------\n\nexport type ExecuteOptions = {\n readonly concurrency?: number | \"unbounded\"\n}\n\n/** Execute every provided call. Approval/rejection policy belongs upstream. */\nexport const run = <Tools extends ReadonlyArray<AnyTool<any>>>(\n tools: Tools,\n calls: ReadonlyArray<ToolCall>,\n options?: ExecuteOptions,\n): Stream.Stream<ToolEvent, never, ToolKindR<Tools>> =>\n Stream.fromIterable(calls).pipe(\n Stream.flatMap((call) => runOne(tools, call), {\n concurrency: options?.concurrency ?? \"unbounded\",\n }),\n )\n\nconst okResult = (call: ToolCall, tool: string, value: unknown): ToolResult =>\n ToolResult.Ok({\n call_id: call.call_id,\n tool,\n value,\n })\n\nconst runOne = <R>(\n tools: ReadonlyArray<AnyTool<R>>,\n call: ToolCall,\n): Stream.Stream<ToolEvent, never, R> => {\n const tool = tools.find((t) => t.name === call.name)\n if (tool === undefined) {\n // Graceful: emit a synthetic Failure so OTHER calls in this turn\n // still execute. LLMs hallucinate tool names; MCP tools come and go.\n return Stream.succeed(\n ToolEvent.Output({\n result: failed(call, \"unknown_tool\", `No tool registered with name \"${call.name}\"`),\n }),\n )\n }\n if (isStreamingTool(tool)) return runStreaming(tool, call)\n return runPlain(tool, call)\n}\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\nconst runPlain = <R>(tool: AnyPlainTool<R>, call: ToolCall): Stream.Stream<ToolEvent, never, R> =>\n Stream.fromEffect(\n Effect.gen(function* () {\n const parsed = yield* parseJsonUnknown(call.arguments).pipe(\n Effect.mapError(() => \"json_parse_error\" as const),\n )\n const validated = yield* Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: () => \"validation_threw\" as const,\n })\n if (validated.issues !== undefined) {\n return executionError(call, \"Tool input failed schema validation\")\n }\n const output = yield* tool.run(validated.value)\n return okResult(call, tool.name, output)\n }).pipe(\n Effect.catchCause(() => Effect.succeed(executionError(call, \"Tool execution failed\"))),\n Effect.map((result) => ToolEvent.Output({ result })),\n ),\n )\n\nconst runStreaming = <R>(\n tool: AnyStreamingTool<R>,\n call: ToolCall,\n): Stream.Stream<ToolEvent, never, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const parsed = yield* parseJsonUnknown(call.arguments).pipe(\n Effect.mapError(() => \"json_parse_error\" as const),\n )\n const validated = yield* Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: () => \"validation_threw\" as const,\n })\n if (validated.issues !== undefined) {\n return Stream.succeed<ToolEvent>(\n ToolEvent.Output({\n result: executionError(call, \"Tool input failed schema validation\"),\n }),\n )\n }\n\n // Real-time: tap each event into a Ref as it flows; emit one\n // Progress per event; then concat one synthetic Output element\n // built from the accumulated Ref via `finalize`.\n const ref = yield* Ref.make<Array<unknown>>([])\n const progress = tool.run(validated.value).pipe(\n Stream.tap((event) => Ref.update(ref, Arr.append(event))),\n Stream.map((data) =>\n ToolEvent.Progress({\n call_id: call.call_id,\n tool: tool.name,\n data,\n }),\n ),\n )\n const output = Stream.fromEffect(\n Ref.get(ref).pipe(\n Effect.map((events) =>\n ToolEvent.Output({\n result: okResult(call, tool.name, tool.finalize(events)),\n }),\n ),\n ),\n )\n return progress.pipe(Stream.concat(output))\n }),\n ).pipe(\n Stream.catchCause(() =>\n Stream.succeed(\n ToolEvent.Output({\n result: executionError(call, \"Tool execution failed\"),\n }),\n ),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// `continueWithResults` - bridge from a `Stream<ToolEvent>` to the loop's\n// emit shape. Drains the stream to the consumer in real-time, taps every\n// `Output` into an internal Ref, and at end-of-stream emits\n// `Loop.next(build(results))`. Recipe never sees the Ref.\n//\n// Dual: data-first `continueWithResults(stream, build)` and data-last\n// `stream.pipe(continueWithResults(build))` both work.\n// ---------------------------------------------------------------------------\n\n/**\n * Append a completed turn plus tool results to a state's history, converting\n * the results to wire-format `ToolCallOutput`s. Curried so it slots directly\n * into `continueWithResults`:\n *\n * Toolkit.run(tools, calls).pipe(\n * Toolkit.continueWithResults(Toolkit.appendToolResults(state, turn)),\n * )\n *\n * Equivalent to `appendToHistory(state, turn, results.map(toToolCallOutput))`\n * — the helper just hides the wire-conversion step.\n */\nexport const appendToolResults =\n <S extends { readonly history: ReadonlyArray<HistoryItem> }>(state: S, turn: Turn) =>\n (results: ReadonlyArray<ToolResult>): S =>\n appendToHistory(state, turn, results.map(toToolCallOutput))\n\n/**\n * Drain a `Stream<ToolEvent>` and return the accumulated `ToolResult`s\n * from every `Output` event. One-shot — the type is `Effect<results>`,\n * not a one-element stream — so it composes naturally with `Effect.map`\n * to build state.\n *\n * Counterpart to `continueWithResults`: `continueWithResults` bundles\n * drain + emit Next into one bridge; this one is the right arm of a\n * broadcast/fork-and-merge for callers that want to vary the left arm\n * (e.g., filter/tap ToolEvents before forwarding).\n */\nexport const collectResults = <E, R>(\n stream: Stream.Stream<ToolEvent, E, R>,\n): Effect.Effect<ReadonlyArray<ToolResult>, E, R> =>\n stream.pipe(\n Stream.filter(isOutput),\n Stream.map((e) => e.result),\n Stream.runCollect,\n )\n\n/**\n * Bridge from a `Stream<ToolEvent>` to the loop's emit shape. Forwards\n * every ToolEvent downstream as a `Loop.value` and at end-of-stream emits\n * one `Loop.next(build(results))` carrying the accumulated `ToolResult`s\n * from terminal `Output` events.\n *\n * Conceptually a broadcast fork (one arm passes events through, the other\n * drains them into a state). Implemented as a single-pass Ref tap + concat\n * for zero buffering — the broadcast version is observationally equivalent\n * but holds events in a PubSub until both arms drain. The public primitives\n * (`Loop.value`, `collectResults`, `Loop.next`) compose the same pattern\n * when you need to vary an arm.\n *\n * Dual: data-first `continueWithResults(stream, build)` and data-last\n * `stream.pipe(continueWithResults(build))` both work.\n */\nexport const continueWithResults: {\n <S>(\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): <R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n ) => Stream.Stream<Loop.Step<ToolEvent, S>, never, R>\n <S, R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Step<ToolEvent, S>, never, R>\n} = Function.dual(\n 2,\n <S, R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Step<ToolEvent, S>, never, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const ref = yield* Ref.make<ReadonlyArray<ToolResult>>([])\n const tapped = stream.pipe(\n Stream.tap((e) =>\n isOutput(e) ? Ref.update(ref, (acc) => Arr.append(acc, e.result)) : Effect.void,\n ),\n Stream.map(Loop.value),\n )\n const continuation = Stream.unwrap(\n Ref.get(ref).pipe(Effect.map((acc) => Loop.next(build(acc)))),\n )\n return tapped.pipe(Stream.concat(continuation))\n }),\n ),\n)\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { fromEffectSchema, make } from "./Tool.mjs";
|
|
2
|
-
import {
|
|
1
|
+
import { fromEffectSchema, make, toDescriptors } from "./Tool.mjs";
|
|
2
|
+
import { isOk } from "./ToolResult.mjs";
|
|
3
3
|
import { isOutput } from "./ToolEvent.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { run } from "./Toolkit.mjs";
|
|
5
5
|
import { i as it, n as globalExpect, r as describe, t as import_dist } from "../dist-DV5ISja1.mjs";
|
|
6
6
|
import { Context, Effect, Layer, Schema, Stream } from "effect";
|
|
7
7
|
//#region src/tool/Toolkit.test.ts
|
|
8
|
-
describe("
|
|
8
|
+
describe("Tool.toDescriptors", () => {
|
|
9
9
|
const GetWeatherInput = Schema.Struct({ city: Schema.String });
|
|
10
10
|
const getWeather = make({
|
|
11
11
|
name: "get_weather",
|
|
@@ -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] = toDescriptors(
|
|
20
|
+
const [desc] = toDescriptors([getWeather]);
|
|
21
21
|
globalExpect(desc?.name).toBe("get_weather");
|
|
22
22
|
globalExpect(desc?.description).toBe("Look up the current temperature for a city.");
|
|
23
23
|
globalExpect(desc?.inputSchema).toMatchObject({
|
|
@@ -27,7 +27,7 @@ describe("Toolkit.toDescriptors", () => {
|
|
|
27
27
|
});
|
|
28
28
|
});
|
|
29
29
|
it("includes strict flag only when set on the tool", () => {
|
|
30
|
-
const [s, l] = toDescriptors(
|
|
30
|
+
const [s, l] = toDescriptors([make({
|
|
31
31
|
name: "strict_one",
|
|
32
32
|
description: "",
|
|
33
33
|
inputSchema: fromEffectSchema(GetWeatherInput),
|
|
@@ -38,12 +38,12 @@ describe("Toolkit.toDescriptors", () => {
|
|
|
38
38
|
description: "",
|
|
39
39
|
inputSchema: fromEffectSchema(GetWeatherInput),
|
|
40
40
|
run: () => Effect.succeed({})
|
|
41
|
-
})])
|
|
41
|
+
})]);
|
|
42
42
|
globalExpect(s?.strict).toBe(true);
|
|
43
43
|
globalExpect(l).not.toHaveProperty("strict");
|
|
44
44
|
});
|
|
45
45
|
});
|
|
46
|
-
describe("Toolkit.
|
|
46
|
+
describe("Toolkit.run - tools with R requirements", () => {
|
|
47
47
|
class WeatherApiKey extends Context.Service()("test/WeatherApiKey") {}
|
|
48
48
|
class GeoApiKey extends Context.Service()("test/GeoApiKey") {}
|
|
49
49
|
const Empty = Schema.Struct({});
|
|
@@ -78,21 +78,21 @@ describe("Toolkit.executeAll - tools with R requirements", () => {
|
|
|
78
78
|
arguments: "{}"
|
|
79
79
|
});
|
|
80
80
|
it("propagates each tool's R into the resulting Stream's requirements", () => {
|
|
81
|
-
(0, import_dist.expectTypeOf)(
|
|
81
|
+
(0, import_dist.expectTypeOf)(run([getWeather, getCoords], [])).toEqualTypeOf();
|
|
82
82
|
});
|
|
83
83
|
it("runs each tool with its own service injected", async () => {
|
|
84
84
|
const layer = Layer.mergeAll(Layer.succeed(WeatherApiKey, { key: "weather-123" }), Layer.succeed(GeoApiKey, { key: "geo-456" }));
|
|
85
|
-
const program =
|
|
85
|
+
const program = run([getWeather, getCoords], [call("get_weather", "c1"), call("get_coords", "c2")]).pipe(Stream.runCollect, Effect.provide(layer));
|
|
86
86
|
const events = await Effect.runPromise(program);
|
|
87
87
|
const outputs = Array.from(events).filter(isOutput);
|
|
88
88
|
const byCall = new Map(outputs.map((e) => [e.result.call_id, e.result]));
|
|
89
89
|
const w = byCall.get("c1");
|
|
90
90
|
const g = byCall.get("c2");
|
|
91
|
-
globalExpect(w !== void 0 &&
|
|
91
|
+
globalExpect(w !== void 0 && isOk(w) && w.value).toEqual({
|
|
92
92
|
source: "weather",
|
|
93
93
|
key: "weather-123"
|
|
94
94
|
});
|
|
95
|
-
globalExpect(g !== void 0 &&
|
|
95
|
+
globalExpect(g !== void 0 && isOk(g) && g.value).toEqual({
|
|
96
96
|
source: "geo",
|
|
97
97
|
key: "geo-456"
|
|
98
98
|
});
|
|
@@ -104,7 +104,7 @@ describe("Toolkit.executeAll - tools with R requirements", () => {
|
|
|
104
104
|
inputSchema: fromEffectSchema(Empty),
|
|
105
105
|
run: () => Effect.succeed(0)
|
|
106
106
|
});
|
|
107
|
-
(0, import_dist.expectTypeOf)(
|
|
107
|
+
(0, import_dist.expectTypeOf)(run([plain], [])).toEqualTypeOf();
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
110
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolkit.test.mjs","names":["Tool.make","Tool.fromEffectSchema","
|
|
1
|
+
{"version":3,"file":"Toolkit.test.mjs","names":["Tool.make","Tool.fromEffectSchema","Tool.toDescriptors","Toolkit.run"],"sources":["../../src/tool/Toolkit.test.ts"],"sourcesContent":["import { Context, Effect, Layer, Schema, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { isOutput } from \"./ToolEvent.js\"\nimport { isOk } from \"./ToolResult.js\"\nimport * as Tool from \"./Tool.js\"\nimport * as Toolkit from \"./Toolkit.js\"\n\ndescribe(\"Tool.toDescriptors\", () => {\n const GetWeatherInput = Schema.Struct({ city: Schema.String })\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"Look up the current temperature for a city.\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: ({ city }) => Effect.succeed({ city, tempC: 18 }),\n })\n\n it(\"renders the input schema as a JSON Schema document\", () => {\n const [desc] = Tool.toDescriptors([getWeather])\n expect(desc?.name).toBe(\"get_weather\")\n expect(desc?.description).toBe(\"Look up the current temperature for a city.\")\n expect(desc?.inputSchema).toMatchObject({\n type: \"object\",\n properties: { city: { type: \"string\" } },\n required: [\"city\"],\n })\n })\n\n it(\"includes strict flag only when set on the tool\", () => {\n const strictTool = Tool.make({\n name: \"strict_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n strict: true,\n })\n const looseTool = Tool.make({\n name: \"loose_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n })\n const [s, l] = Tool.toDescriptors([strictTool, looseTool])\n expect(s?.strict).toBe(true)\n expect(l).not.toHaveProperty(\"strict\")\n })\n})\n\ndescribe(\"Toolkit.run - tools with R requirements\", () => {\n // Two distinct services, modelling the \"typed per-tool context\" use case\n // (cf. AI SDK 7's `toolsContext`). In Effect each tool declares its R, the\n // compiler enforces it, and `run` surfaces the union for the caller\n // to provide via Layer.\n type WeatherApiKeyShape = { readonly key: string }\n class WeatherApiKey extends Context.Service<WeatherApiKey, WeatherApiKeyShape>()(\n \"test/WeatherApiKey\",\n ) {}\n\n type GeoApiKeyShape = { readonly key: string }\n class GeoApiKey extends Context.Service<GeoApiKey, GeoApiKeyShape>()(\"test/GeoApiKey\") {}\n\n const Empty = Schema.Struct({})\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* WeatherApiKey\n return { source: \"weather\", key }\n }),\n })\n\n const getCoords = Tool.make({\n name: \"get_coords\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* GeoApiKey\n return { source: \"geo\", key }\n }),\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"propagates each tool's R into the resulting Stream's requirements\", () => {\n const stream = Toolkit.run([getWeather, getCoords], [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, WeatherApiKey | GeoApiKey>\n >()\n })\n\n it(\"runs each tool with its own service injected\", async () => {\n const layer = Layer.mergeAll(\n Layer.succeed(WeatherApiKey, { key: \"weather-123\" }),\n Layer.succeed(GeoApiKey, { key: \"geo-456\" }),\n )\n\n const program = Toolkit.run(\n [getWeather, getCoords],\n [call(\"get_weather\", \"c1\"), call(\"get_coords\", \"c2\")],\n ).pipe(Stream.runCollect, Effect.provide(layer))\n\n const events = await Effect.runPromise(program)\n const outputs = Array.from(events).filter(isOutput)\n const byCall = new Map(outputs.map((e) => [e.result.call_id, e.result]))\n\n const w = byCall.get(\"c1\")\n const g = byCall.get(\"c2\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({\n source: \"weather\",\n key: \"weather-123\",\n })\n expect(g !== undefined && isOk(g) && g.value).toEqual({\n source: \"geo\",\n key: \"geo-456\",\n })\n })\n\n it(\"with no service-needing tools, R is never\", () => {\n const plain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n const stream = Toolkit.run([plain], [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>\n >()\n })\n})\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as AiError } from "../AiError-CAX_48RU.mjs";
|
|
2
|
-
import { a as AudioSource, r as AudioFormat } from "../Audio-
|
|
2
|
+
import { a as AudioSource, r as AudioFormat } from "../Audio-DPlTsNdz.mjs";
|
|
3
3
|
import { TranscriptEvent, TranscriptResult } from "../domain/Transcript.mjs";
|
|
4
4
|
import { Context, Effect, Stream } from "effect";
|
|
5
5
|
|
|
@@ -17,21 +17,22 @@ type CommonTranscribeRequest = {
|
|
|
17
17
|
readonly audio: AudioSource; /** Model identifier. Each provider narrows to its typed literal union. */
|
|
18
18
|
readonly model: string; /** ISO-639-1 / BCP-47. Omit for autodetection (where supported). */
|
|
19
19
|
readonly language?: string;
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
};
|
|
20
|
+
/** Free-form context / style hint (Whisper-style prose). OpenAI honors
|
|
21
|
+
* it; providers without a prompt field `warnDropped`. */
|
|
22
|
+
readonly prompt?: string;
|
|
23
|
+
/** Vocabulary biasing — discrete terms to boost (names, jargon). Maps to
|
|
24
|
+
* Deepgram `keyterm`, ElevenLabs `keyterms`, Google `adaptation`,
|
|
25
|
+
* Inworld `prompts`; others `warnDropped`. */
|
|
26
|
+
readonly biasingTerms?: ReadonlyArray<string>;
|
|
28
27
|
readonly diarization?: boolean;
|
|
29
28
|
readonly wordTimestamps?: boolean;
|
|
30
29
|
};
|
|
31
30
|
/**
|
|
32
31
|
* Streaming-transcription request. `inputFormat` declares what the
|
|
33
32
|
* bytes in the input stream will look like — providers reject
|
|
34
|
-
*
|
|
33
|
+
* formats they can't ingest at stream startup with
|
|
34
|
+
* `AiError.Unsupported` (a per-Layer capability gap, not a wire-shape
|
|
35
|
+
* mismatch).
|
|
35
36
|
*/
|
|
36
37
|
type CommonStreamTranscribeRequest = Omit<CommonTranscribeRequest, "audio"> & {
|
|
37
38
|
readonly inputFormat: AudioFormat;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Transcriber.d.mts","names":[],"sources":["../../src/transcriber/Transcriber.ts"],"mappings":";;;;;;;;;;;;;;;KAWY,uBAAA;EAAA,SACD,KAAA,EAAO,WAAA;WAEP,KAAA;WAEA,QAAA;EALC
|
|
1
|
+
{"version":3,"file":"Transcriber.d.mts","names":[],"sources":["../../src/transcriber/Transcriber.ts"],"mappings":";;;;;;;;;;;;;;;KAWY,uBAAA;EAAA,SACD,KAAA,EAAO,WAAA;WAEP,KAAA;WAEA,QAAA;EALC;;EAAA,SAQD,MAAA;EAI4B;;;EAAA,SAA5B,YAAA,GAAe,aAAA;EAAA,SACf,WAAA;EAAA,SACA,cAAA;AAAA;;;;;;AAUX;;KAAY,6BAAA,GAAgC,IAAA,CAAK,uBAAA;EAAA,SACtC,WAAA,EAAa,WAAA;EAAA,SACb,cAAA;EAAA,SACA,SAAA;AAAA;AAAA,KAGC,kBAAA;EANgC;;;;;EAAA,SAYjC,UAAA,GACP,OAAA,EAAS,uBAAA,KACN,MAAA,CAAO,MAAA,CAAO,gBAAA,EAAkB,OAAA;EAXnB;;AAGpB;;;;;;;;;;EAHoB,SAwBT,uBAAA,SACP,OAAA,EAAS,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,CAAA,EAAG,CAAA,GACtC,OAAA,EAAS,6BAAA,KACN,MAAA,CAAO,MAAA,CAAO,eAAA,EAAiB,OAAA,GAAkB,CAAA,EAAG,CAAA;AAAA;AAAA,cAC1D,gBAAA;cAEY,WAAA,SAAoB,gBAAA;AAAA,cAE7B,iBAAA;;;;;;;;;;;cAYS,YAAA,SAAqB,iBAAA;;cAKrB,UAAA,GACX,OAAA,EAAS,uBAAA,KACR,MAAA,CAAO,MAAA,CAAO,gBAAA,EAAkB,OAAA,EAAiB,WAAA;;;;;;;;;;;;;;;;;;cAoBvC,uBAAA;EAAA,CAET,OAAA,EAAS,6BAAA,UAET,OAAA,EAAS,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,CAAA,EAAG,CAAA,MACnC,MAAA,CAAO,MAAA,CAAO,eAAA,EAAiB,OAAA,GAAkB,CAAA,EAAG,CAAA,GAAI,WAAA,GAAc,YAAA;EAAA,OAEzE,OAAA,EAAS,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,CAAA,EAAG,CAAA,GACtC,OAAA,EAAS,6BAAA,GACR,MAAA,CAAO,MAAA,CAAO,eAAA,EAAiB,OAAA,GAAkB,CAAA,EAAG,CAAA,GAAI,WAAA,GAAc,YAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Transcriber.mjs","names":[],"sources":["../../src/transcriber/Transcriber.ts"],"sourcesContent":["import { Context, Effect, Function, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { AudioFormat, AudioSource } from \"../domain/Audio.js\"\nimport type { TranscriptEvent, TranscriptResult } from \"../domain/Transcript.js\"\n\n/**\n * Cross-provider sync transcription request. Provider-specific\n * extensions (Deepgram `keyterm[]`, ElevenLabs `diarize`, Google\n * `adaptation`, …) live on each provider's typed request which extends\n * this and narrows `model`.\n */\nexport type CommonTranscribeRequest = {\n readonly audio: AudioSource\n /** Model identifier. Each provider narrows to its typed literal union. */\n readonly model: string\n /** ISO-639-1 / BCP-47. Omit for autodetection (where supported). */\n readonly language?: string\n
|
|
1
|
+
{"version":3,"file":"Transcriber.mjs","names":[],"sources":["../../src/transcriber/Transcriber.ts"],"sourcesContent":["import { Context, Effect, Function, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { AudioFormat, AudioSource } from \"../domain/Audio.js\"\nimport type { TranscriptEvent, TranscriptResult } from \"../domain/Transcript.js\"\n\n/**\n * Cross-provider sync transcription request. Provider-specific\n * extensions (Deepgram `keyterm[]`, ElevenLabs `diarize`, Google\n * `adaptation`, …) live on each provider's typed request which extends\n * this and narrows `model`.\n */\nexport type CommonTranscribeRequest = {\n readonly audio: AudioSource\n /** Model identifier. Each provider narrows to its typed literal union. */\n readonly model: string\n /** ISO-639-1 / BCP-47. Omit for autodetection (where supported). */\n readonly language?: string\n /** Free-form context / style hint (Whisper-style prose). OpenAI honors\n * it; providers without a prompt field `warnDropped`. */\n readonly prompt?: string\n /** Vocabulary biasing — discrete terms to boost (names, jargon). Maps to\n * Deepgram `keyterm`, ElevenLabs `keyterms`, Google `adaptation`,\n * Inworld `prompts`; others `warnDropped`. */\n readonly biasingTerms?: ReadonlyArray<string>\n readonly diarization?: boolean\n readonly wordTimestamps?: boolean\n}\n\n/**\n * Streaming-transcription request. `inputFormat` declares what the\n * bytes in the input stream will look like — providers reject\n * formats they can't ingest at stream startup with\n * `AiError.Unsupported` (a per-Layer capability gap, not a wire-shape\n * mismatch).\n */\nexport type CommonStreamTranscribeRequest = Omit<CommonTranscribeRequest, \"audio\"> & {\n readonly inputFormat: AudioFormat\n readonly interimResults?: boolean\n readonly vadEvents?: boolean\n}\n\nexport type TranscriberService = {\n /**\n * One-shot transcription. Universal — AWS Transcribe (which has no\n * native sync endpoint) emulates this by draining a streaming session\n * internally.\n */\n readonly transcribe: (\n request: CommonTranscribeRequest,\n ) => Effect.Effect<TranscriptResult, AiError.AiError>\n /**\n * Live transcription as a Stream transformer. Consumes audio bytes\n * from `audioIn`; emits `TranscriptEvent`s as they arrive. The\n * underlying WS / gRPC connection is acquired on first pull and\n * released when the output stream is finalized (success, failure, or\n * interruption) via `Stream.scoped` — no explicit Scope handling at\n * the call site.\n *\n * Gated by the `SttStreaming` capability marker on the top-level\n * helper — providers without streaming-STT support don't ship the\n * marker, so calls fail at `Effect.provide` with a type error.\n */\n readonly streamTranscriptionFrom: <E, R>(\n audioIn: Stream.Stream<Uint8Array, E, R>,\n request: CommonStreamTranscribeRequest,\n ) => Stream.Stream<TranscriptEvent, AiError.AiError | E, R>\n}\n\nexport class Transcriber extends Context.Service<Transcriber, TranscriberService>()(\n \"@betalyra/effect-uai/Transcriber\",\n) {}\n\n/**\n * Capability marker — provided by provider layers whose\n * `streamTranscriptionFrom` is wired up at the wire level. Azure does\n * not ship it (streaming-STT is SDK-internal). Calling\n * `streamTranscriptionFrom` while only Azure's Layer is in scope fails\n * at `Effect.provide` with a type error, not at runtime.\n *\n * Phantom — the value is `void`; providers register with\n * `Layer.succeed(SttStreaming, undefined)`.\n */\nexport class SttStreaming extends Context.Service<SttStreaming, void>()(\n \"@betalyra/effect-uai/capability/SttStreaming\",\n) {}\n\n/** One-shot transcription. */\nexport const transcribe = (\n request: CommonTranscribeRequest,\n): Effect.Effect<TranscriptResult, AiError.AiError, Transcriber> =>\n Effect.flatMap(Transcriber.asEffect(), (t) => t.transcribe(request))\n\n/**\n * Live transcription. Dual-arity: pipeable (data-last) and direct\n * (data-first). Requires `SttStreaming` in R — providers without\n * streaming support are a type error at provide time.\n *\n * @example\n * ```ts\n * // Pipeable — composes with other Stream operators\n * mic.frames.pipe(\n * Transcriber.streamTranscriptionFrom(req),\n * Stream.filter((e) => e._tag === \"final\"),\n * )\n *\n * // Direct\n * Transcriber.streamTranscriptionFrom(mic.frames, req)\n * ```\n */\nexport const streamTranscriptionFrom: {\n (\n request: CommonStreamTranscribeRequest,\n ): <E, R>(\n audioIn: Stream.Stream<Uint8Array, E, R>,\n ) => Stream.Stream<TranscriptEvent, AiError.AiError | E, R | Transcriber | SttStreaming>\n <E, R>(\n audioIn: Stream.Stream<Uint8Array, E, R>,\n request: CommonStreamTranscribeRequest,\n ): Stream.Stream<TranscriptEvent, AiError.AiError | E, R | Transcriber | SttStreaming>\n} = Function.dual(\n 2,\n <E, R>(audioIn: Stream.Stream<Uint8Array, E, R>, request: CommonStreamTranscribeRequest) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const t = yield* Transcriber.asEffect()\n yield* SttStreaming.asEffect()\n return t.streamTranscriptionFrom(audioIn, request)\n }),\n ),\n)\n"],"mappings":";;;;;;;;;AAoEA,IAAa,cAAb,cAAiC,QAAQ,SAA0C,CACjF,mCACD,CAAC;;;;;;;;;;;AAYF,IAAa,eAAb,cAAkC,QAAQ,SAA6B,CACrE,+CACD,CAAC;;AAGF,MAAa,cACX,YAEA,OAAO,QAAQ,YAAY,UAAU,GAAG,MAAM,EAAE,WAAW,QAAQ,CAAC;;;;;;;;;;;;;;;;;;AAmBtE,MAAa,0BAUT,SAAS,KACX,IACO,SAA0C,YAC/C,OAAO,OACL,OAAO,IAAI,aAAa;CACtB,MAAM,IAAI,OAAO,YAAY,UAAU;AACvC,QAAO,aAAa,UAAU;AAC9B,QAAO,EAAE,wBAAwB,SAAS,QAAQ;EAClD,CACH,CACJ"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { streamTranscriptionFrom, transcribe } from "./Transcriber.mjs";
|
|
2
2
|
import { i as it, n as globalExpect, r as describe, t as import_dist } from "../dist-DV5ISja1.mjs";
|
|
3
3
|
import { layer, layerSyncOnly } from "../testing/MockTranscriber.mjs";
|
|
4
|
-
import { Effect, Stream } from "effect";
|
|
4
|
+
import { Duration, Effect, Stream } from "effect";
|
|
5
5
|
//#region src/transcriber/Transcriber.test.ts
|
|
6
6
|
describe("Transcriber.transcribe", () => {
|
|
7
7
|
it("returns the scripted TranscriptResult", async () => {
|
|
8
8
|
const mock = layer({ transcripts: [{
|
|
9
9
|
text: "hello world",
|
|
10
|
-
|
|
10
|
+
duration: Duration.seconds(1.23)
|
|
11
11
|
}] });
|
|
12
12
|
const program = transcribe({
|
|
13
13
|
audio: {
|
|
@@ -19,7 +19,7 @@ describe("Transcriber.transcribe", () => {
|
|
|
19
19
|
});
|
|
20
20
|
const result = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)));
|
|
21
21
|
globalExpect(result.text).toBe("hello world");
|
|
22
|
-
globalExpect(result.
|
|
22
|
+
globalExpect(result.duration).toStrictEqual(Duration.seconds(1.23));
|
|
23
23
|
});
|
|
24
24
|
it("records each transcribe call", async () => {
|
|
25
25
|
const mock = layer({ transcripts: [{ text: "a" }, { text: "b" }] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Transcriber.test.mjs","names":["MockTranscriber.layer","Transcriber.transcribe","Transcriber.streamTranscriptionFrom","MockTranscriber.layerSyncOnly"],"sources":["../../src/transcriber/Transcriber.test.ts"],"sourcesContent":["import { Effect, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type * as AiError from \"../domain/AiError.js\"\nimport type { TranscriptEvent, TranscriptResult } from \"../domain/Transcript.js\"\nimport * as MockTranscriber from \"../testing/MockTranscriber.js\"\nimport * as Transcriber from \"./Transcriber.js\"\n\ndescribe(\"Transcriber.transcribe\", () => {\n it(\"returns the scripted TranscriptResult\", async () => {\n const mock = MockTranscriber.layer({\n transcripts: [{ text: \"hello world\",
|
|
1
|
+
{"version":3,"file":"Transcriber.test.mjs","names":["MockTranscriber.layer","Transcriber.transcribe","Transcriber.streamTranscriptionFrom","MockTranscriber.layerSyncOnly"],"sources":["../../src/transcriber/Transcriber.test.ts"],"sourcesContent":["import { Duration, Effect, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type * as AiError from \"../domain/AiError.js\"\nimport type { TranscriptEvent, TranscriptResult } from \"../domain/Transcript.js\"\nimport * as MockTranscriber from \"../testing/MockTranscriber.js\"\nimport * as Transcriber from \"./Transcriber.js\"\n\ndescribe(\"Transcriber.transcribe\", () => {\n it(\"returns the scripted TranscriptResult\", async () => {\n const mock = MockTranscriber.layer({\n transcripts: [{ text: \"hello world\", duration: Duration.seconds(1.23) }],\n })\n const program = Transcriber.transcribe({\n audio: { _tag: \"bytes\", bytes: new Uint8Array([0]), mimeType: \"audio/wav\" },\n model: \"mock-stt\",\n })\n const result = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(result.text).toBe(\"hello world\")\n expect(result.duration).toStrictEqual(Duration.seconds(1.23))\n })\n\n it(\"records each transcribe call\", async () => {\n const mock = MockTranscriber.layer({\n transcripts: [{ text: \"a\" }, { text: \"b\" }],\n })\n const program = Effect.gen(function* () {\n yield* Transcriber.transcribe({\n audio: { _tag: \"bytes\", bytes: new Uint8Array([1]), mimeType: \"audio/wav\" },\n model: \"m1\",\n })\n yield* Transcriber.transcribe({\n audio: { _tag: \"bytes\", bytes: new Uint8Array([2]), mimeType: \"audio/wav\" },\n model: \"m2\",\n })\n return yield* mock.recorder\n })\n const rec = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(rec.transcribeCalls.map((c) => c.model)).toEqual([\"m1\", \"m2\"])\n })\n})\n\ndescribe(\"Transcriber capability marker (compile-time)\", () => {\n const sttReq: Transcriber.CommonStreamTranscribeRequest = {\n model: \"mock-stt\",\n inputFormat: { container: \"raw\", encoding: \"pcm_s16le\", sampleRate: 16000 },\n }\n\n it(\"requires `SttStreaming` on the R channel of streamTranscriptionFrom\", () => {\n const audio: Stream.Stream<Uint8Array> = Stream.fromIterable([new Uint8Array([0])])\n const events = audio.pipe(Transcriber.streamTranscriptionFrom(sttReq))\n expectTypeOf(events).toEqualTypeOf<\n Stream.Stream<\n TranscriptEvent,\n AiError.AiError,\n Transcriber.Transcriber | Transcriber.SttStreaming\n >\n >()\n })\n\n it(\"does NOT require `SttStreaming` for sync `transcribe`\", () => {\n const eff = Transcriber.transcribe({\n audio: { _tag: \"bytes\", bytes: new Uint8Array([0]), mimeType: \"audio/wav\" },\n model: \"m\",\n })\n expectTypeOf(eff).toEqualTypeOf<\n Effect.Effect<TranscriptResult, AiError.AiError, Transcriber.Transcriber>\n >()\n })\n\n it(\"a sync-only layer leaves `SttStreaming` unsatisfied in R\", () => {\n const syncOnly = MockTranscriber.layerSyncOnly({})\n const audio: Stream.Stream<Uint8Array> = Stream.fromIterable([new Uint8Array([0])])\n const events = audio.pipe(Transcriber.streamTranscriptionFrom(sttReq))\n const program = Stream.runDrain(events).pipe(Effect.provide(syncOnly.layer))\n // `Transcriber` is provided by syncOnly.layer; `SttStreaming` is not.\n expectTypeOf(program).toEqualTypeOf<\n Effect.Effect<void, AiError.AiError, Transcriber.SttStreaming>\n >()\n })\n\n it(\"a full layer (with marker) clears R to never\", () => {\n const fullMock = MockTranscriber.layer({ streams: [[]] })\n const audio: Stream.Stream<Uint8Array> = Stream.fromIterable([new Uint8Array([0])])\n const events = audio.pipe(Transcriber.streamTranscriptionFrom(sttReq))\n const program = Stream.runDrain(events).pipe(Effect.provide(fullMock.layer))\n expectTypeOf(program).toEqualTypeOf<Effect.Effect<void, AiError.AiError, never>>()\n })\n})\n\ndescribe(\"Transcriber.streamTranscriptionFrom\", () => {\n const sttReq: Transcriber.CommonStreamTranscribeRequest = {\n model: \"mock-stt\",\n inputFormat: { container: \"raw\", encoding: \"pcm_s16le\", sampleRate: 16000 },\n }\n\n it(\"emits scripted events after draining the input audio stream\", async () => {\n const mock = MockTranscriber.layer({\n streams: [\n [\n { _tag: \"partial\", text: \"hello\" },\n { _tag: \"final\", text: \"hello world\" },\n ],\n ],\n })\n const audio = Stream.fromIterable([new Uint8Array([0, 1, 2]), new Uint8Array([3, 4, 5])])\n const events = audio.pipe(Transcriber.streamTranscriptionFrom(sttReq))\n const collected = await Effect.runPromise(\n Stream.runCollect(events).pipe(Effect.provide(mock.layer)),\n )\n expect(collected).toEqual([\n { _tag: \"partial\", text: \"hello\" },\n { _tag: \"final\", text: \"hello world\" },\n ])\n })\n\n it(\"works data-first (direct call) as well as pipeable (data-last)\", async () => {\n const mock = MockTranscriber.layer({\n streams: [[{ _tag: \"final\", text: \"x\" }]],\n })\n const audio = Stream.fromIterable([new Uint8Array([0])])\n const events = Transcriber.streamTranscriptionFrom(audio, sttReq)\n const out = await Effect.runPromise(Stream.runCollect(events).pipe(Effect.provide(mock.layer)))\n expect(out).toEqual([{ _tag: \"final\", text: \"x\" }])\n })\n})\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-uai/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Low-level primitives (loop, conversation, items, tools, streaming codecs) for building AI agents with Effect.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -82,6 +82,26 @@
|
|
|
82
82
|
"types": "./dist/music-generator/MusicGenerator.d.mts",
|
|
83
83
|
"import": "./dist/music-generator/MusicGenerator.mjs"
|
|
84
84
|
},
|
|
85
|
+
"./Memory": {
|
|
86
|
+
"types": "./dist/sandbox/Memory.d.mts",
|
|
87
|
+
"import": "./dist/sandbox/Memory.mjs"
|
|
88
|
+
},
|
|
89
|
+
"./Sandbox": {
|
|
90
|
+
"types": "./dist/sandbox/Sandbox.d.mts",
|
|
91
|
+
"import": "./dist/sandbox/Sandbox.mjs"
|
|
92
|
+
},
|
|
93
|
+
"./SandboxError": {
|
|
94
|
+
"types": "./dist/sandbox/SandboxError.d.mts",
|
|
95
|
+
"import": "./dist/sandbox/SandboxError.mjs"
|
|
96
|
+
},
|
|
97
|
+
"./SandboxImage": {
|
|
98
|
+
"types": "./dist/sandbox/SandboxImage.d.mts",
|
|
99
|
+
"import": "./dist/sandbox/SandboxImage.mjs"
|
|
100
|
+
},
|
|
101
|
+
"./SandboxNetwork": {
|
|
102
|
+
"types": "./dist/sandbox/SandboxNetwork.d.mts",
|
|
103
|
+
"import": "./dist/sandbox/SandboxNetwork.mjs"
|
|
104
|
+
},
|
|
85
105
|
"./SpeechSynthesizer": {
|
|
86
106
|
"types": "./dist/speech-synthesizer/SpeechSynthesizer.d.mts",
|
|
87
107
|
"import": "./dist/speech-synthesizer/SpeechSynthesizer.mjs"
|
|
@@ -110,22 +130,26 @@
|
|
|
110
130
|
"types": "./dist/tool/Toolkit.d.mts",
|
|
111
131
|
"import": "./dist/tool/Toolkit.mjs"
|
|
112
132
|
},
|
|
113
|
-
"./
|
|
114
|
-
"types": "./dist/tool/
|
|
115
|
-
"import": "./dist/tool/
|
|
133
|
+
"./ToolResult": {
|
|
134
|
+
"types": "./dist/tool/ToolResult.d.mts",
|
|
135
|
+
"import": "./dist/tool/ToolResult.mjs"
|
|
116
136
|
},
|
|
117
137
|
"./ToolEvent": {
|
|
118
138
|
"types": "./dist/tool/ToolEvent.d.mts",
|
|
119
139
|
"import": "./dist/tool/ToolEvent.mjs"
|
|
120
140
|
},
|
|
121
|
-
"./
|
|
122
|
-
"types": "./dist/tool/
|
|
123
|
-
"import": "./dist/tool/
|
|
141
|
+
"./Approval": {
|
|
142
|
+
"types": "./dist/tool/Approval.d.mts",
|
|
143
|
+
"import": "./dist/tool/Approval.mjs"
|
|
124
144
|
},
|
|
125
145
|
"./HistoryCheck": {
|
|
126
146
|
"types": "./dist/tool/HistoryCheck.d.mts",
|
|
127
147
|
"import": "./dist/tool/HistoryCheck.mjs"
|
|
128
148
|
},
|
|
149
|
+
"./Capabilities": {
|
|
150
|
+
"types": "./dist/capabilities/Capabilities.d.mts",
|
|
151
|
+
"import": "./dist/capabilities/Capabilities.mjs"
|
|
152
|
+
},
|
|
129
153
|
"./JSONL": {
|
|
130
154
|
"types": "./dist/streaming/JSONL.d.mts",
|
|
131
155
|
"import": "./dist/streaming/JSONL.mjs"
|
|
@@ -158,6 +182,10 @@
|
|
|
158
182
|
"types": "./dist/testing/MockSpeechSynthesizer.d.mts",
|
|
159
183
|
"import": "./dist/testing/MockSpeechSynthesizer.mjs"
|
|
160
184
|
},
|
|
185
|
+
"./testing/MockSandbox": {
|
|
186
|
+
"types": "./dist/testing/MockSandbox.d.mts",
|
|
187
|
+
"import": "./dist/testing/MockSandbox.mjs"
|
|
188
|
+
},
|
|
161
189
|
"./testing/MockTranscriber": {
|
|
162
190
|
"types": "./dist/testing/MockTranscriber.d.mts",
|
|
163
191
|
"import": "./dist/testing/MockTranscriber.mjs"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Structured event emitted when a provider adapter drops a Common
|
|
5
|
+
* request field because its wire API has no place to put it (bucket 2
|
|
6
|
+
* per the capabilities policy: the provider has no structured
|
|
7
|
+
* interpretation; the output is still valid, just less aligned with
|
|
8
|
+
* the caller's hint).
|
|
9
|
+
*
|
|
10
|
+
* Carried on `Effect.logWarning` today for visibility; may be
|
|
11
|
+
* promoted to a typed `AiError` variant in the future if callers need
|
|
12
|
+
* to react programmatically. The shape itself is stable.
|
|
13
|
+
*
|
|
14
|
+
* @category capabilities
|
|
15
|
+
*/
|
|
16
|
+
export type CapabilityWarning = {
|
|
17
|
+
readonly _tag: "CapabilityWarning"
|
|
18
|
+
readonly provider: string
|
|
19
|
+
/** The capability name as it appears in docs (`"lyrics"`, `"bpm"`). */
|
|
20
|
+
readonly capability: string
|
|
21
|
+
/** The Common-request field name that was dropped. */
|
|
22
|
+
readonly field: string
|
|
23
|
+
/** The value the caller passed; included when useful for debugging. */
|
|
24
|
+
readonly value?: unknown
|
|
25
|
+
/** Human-readable explanation; should suggest a workaround. */
|
|
26
|
+
readonly reason: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Emit a structured `CapabilityWarning` via `Effect.logWarning`. Use
|
|
31
|
+
* when a provider adapter receives a Common-request field it has no
|
|
32
|
+
* structured wire field for, and would otherwise drop silently.
|
|
33
|
+
*
|
|
34
|
+
* Bucket-2 cases only (per the capabilities policy): the provider has
|
|
35
|
+
* no interpretation; the output is still valid, just less aligned
|
|
36
|
+
* with the caller's hint. For bucket-1 cases (structurally broken
|
|
37
|
+
* output if dropped, like wrong audio format), fail
|
|
38
|
+
* `AiError.Unsupported` instead.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* if (request.lyrics !== undefined) {
|
|
43
|
+
* yield* warnDropped({
|
|
44
|
+
* provider: "lyria",
|
|
45
|
+
* capability: "lyrics",
|
|
46
|
+
* field: "lyrics",
|
|
47
|
+
* reason: "Lyria 3 sync has no `lyrics` wire field. Embed in your prompt instead.",
|
|
48
|
+
* })
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export const warnDropped = (warning: Omit<CapabilityWarning, "_tag">): Effect.Effect<void> =>
|
|
53
|
+
Effect.logWarning("Capability dropped", { ...warning, _tag: "CapabilityWarning" })
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Shorthand for the most common shape: warn-and-drop when a specific
|
|
57
|
+
* field on the Common request is set. The field's value is included
|
|
58
|
+
* automatically.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* yield* Effect.all([
|
|
63
|
+
* warnDroppedWhen(request.lyrics, {
|
|
64
|
+
* provider: "lyria",
|
|
65
|
+
* capability: "lyrics",
|
|
66
|
+
* field: "lyrics",
|
|
67
|
+
* reason: "Lyria 3 sync has no `lyrics` wire field. Embed in your prompt instead.",
|
|
68
|
+
* }),
|
|
69
|
+
* warnDroppedWhen(request.duration, {
|
|
70
|
+
* provider: "lyria",
|
|
71
|
+
* capability: "duration",
|
|
72
|
+
* field: "duration",
|
|
73
|
+
* reason: "Lyria 3 clip is fixed at 30 s.",
|
|
74
|
+
* }),
|
|
75
|
+
* ], { discard: true })
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export const warnDroppedWhen = <T>(
|
|
79
|
+
value: T | undefined,
|
|
80
|
+
warning: Omit<CapabilityWarning, "_tag" | "value">,
|
|
81
|
+
): Effect.Effect<void> => (value === undefined ? Effect.void : warnDropped({ ...warning, value }))
|
package/src/domain/Audio.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Duration } from "effect"
|
|
1
2
|
import type { MediaBase64, MediaBytes, MediaUrl } from "./Media.js"
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -84,5 +85,5 @@ export type AudioChunk = {
|
|
|
84
85
|
export type AudioBlob = {
|
|
85
86
|
readonly format: AudioFormat
|
|
86
87
|
readonly bytes: Uint8Array
|
|
87
|
-
readonly
|
|
88
|
+
readonly duration?: Duration.Duration
|
|
88
89
|
}
|
package/src/domain/Items.ts
CHANGED
|
@@ -117,7 +117,7 @@ export const Message = Schema.Struct({
|
|
|
117
117
|
})
|
|
118
118
|
export type Message = typeof Message.Type
|
|
119
119
|
|
|
120
|
-
export const
|
|
120
|
+
export const ToolCall = Schema.Struct({
|
|
121
121
|
type: Schema.Literal("function_call"),
|
|
122
122
|
call_id: Schema.String,
|
|
123
123
|
name: Schema.String,
|
|
@@ -125,15 +125,15 @@ export const FunctionCall = Schema.Struct({
|
|
|
125
125
|
arguments: Schema.String,
|
|
126
126
|
providerData: ProviderData,
|
|
127
127
|
})
|
|
128
|
-
export type
|
|
128
|
+
export type ToolCall = typeof ToolCall.Type
|
|
129
129
|
|
|
130
|
-
export const
|
|
130
|
+
export const ToolCallOutput = Schema.Struct({
|
|
131
131
|
type: Schema.Literal("function_call_output"),
|
|
132
132
|
call_id: Schema.String,
|
|
133
133
|
output: Schema.String,
|
|
134
134
|
providerData: ProviderData,
|
|
135
135
|
})
|
|
136
|
-
export type
|
|
136
|
+
export type ToolCallOutput = typeof ToolCallOutput.Type
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
139
|
* Reasoning item - top-level, mirrors OpenAI Responses API. Common shape
|
|
@@ -150,8 +150,8 @@ export const Reasoning = Schema.Struct({
|
|
|
150
150
|
})
|
|
151
151
|
export type Reasoning = typeof Reasoning.Type
|
|
152
152
|
|
|
153
|
-
export const
|
|
154
|
-
export type
|
|
153
|
+
export const HistoryItem = Schema.Union([Message, ToolCall, ToolCallOutput, Reasoning])
|
|
154
|
+
export type HistoryItem = typeof HistoryItem.Type
|
|
155
155
|
|
|
156
156
|
// ---------------------------------------------------------------------------
|
|
157
157
|
// Type guards
|
|
@@ -163,8 +163,8 @@ export const isOutputText = Schema.is(OutputText)
|
|
|
163
163
|
export const isRefusal = Schema.is(Refusal)
|
|
164
164
|
|
|
165
165
|
export const isMessage = Schema.is(Message)
|
|
166
|
-
export const
|
|
167
|
-
export const
|
|
166
|
+
export const isToolCall = Schema.is(ToolCall)
|
|
167
|
+
export const isToolCallOutput = Schema.is(ToolCallOutput)
|
|
168
168
|
export const isReasoning = Schema.is(Reasoning)
|
|
169
169
|
|
|
170
170
|
// ---------------------------------------------------------------------------
|
|
@@ -224,7 +224,7 @@ export const assistantText = (text: string): Message => ({
|
|
|
224
224
|
content: [{ type: "output_text", text }],
|
|
225
225
|
})
|
|
226
226
|
|
|
227
|
-
export const
|
|
227
|
+
export const toolCallOutput = (call_id: string, output: string): ToolCallOutput => ({
|
|
228
228
|
type: "function_call_output",
|
|
229
229
|
call_id,
|
|
230
230
|
output,
|