@effect-uai/ai-sdk 0.12.1 → 0.14.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UIMessageStream.d.mts","names":[],"sources":["../src/UIMessageStream.ts"],"mappings":";;;;;;;;;;KAyBY;WACG;WAAwB;;WACxB;WAA6B;;WAC7B;WAA6B;WAAqB;;WAClD;WAA2B;;WAC3B;WAAkC;;WAClC;WAAkC;WAAqB;;WACvD;WAAgC;;WAChC;WAAmC;WAA6B;;WAEhE;WACA;WACA;;WAGA;WACA;WACA;WACA;;WAGA;WACA;WACA;;WAGA;WACA;WACA;WACA;;WAEA;WAAmC;;WACnC;WAAwB;;WACxB;;;;;;;KAOH;WACD;WACA;WACA;WACA;WACA;;;KAIC;WACD;WACA;;;;;;KAOC,WAAW,KAAK,mBAAmB,eAAe;;cAGjD,WAAQ,cACP,eACC;WACQ;WAAsB;MAC1C;;cAGU,kBAAe,sBAAwB;;;;;;;;cAYvC,iBAAiB;;;;;;
|
|
1
|
+
{"version":3,"file":"UIMessageStream.d.mts","names":[],"sources":["../src/UIMessageStream.ts"],"mappings":";;;;;;;;;;KAyBY;WACG;WAAwB;;WACxB;WAA6B;;WAC7B;WAA6B;WAAqB;;WAClD;WAA2B;;WAC3B;WAAkC;;WAClC;WAAkC;WAAqB;;WACvD;WAAgC;;WAChC;WAAmC;WAA6B;;WAEhE;WACA;WACA;;WAGA;WACA;WACA;WACA;;WAGA;WACA;WACA;;WAGA;WACA;WACA;WACA;;WAEA;WAAmC;;WACnC;WAAwB;;WACxB;;;;;;;KAOH;WACD;WACA;WACA;WACA;WACA;;;KAIC;WACD;WACA;;;;;;KAOC,WAAW,KAAK,mBAAmB,eAAe;;cAGjD,WAAQ,cACP,eACC;WACQ;WAAsB;MAC1C;;cAGU,kBAAe,sBAAwB;;;;;;;;cAYvC,iBAAiB;;;;;;cAkHjB,oBAAiB,uBAE3B,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,IAAI,OAAO,GAAG"}
|
package/dist/UIMessageStream.mjs
CHANGED
|
@@ -108,6 +108,7 @@ const step = (s, ev) => Match.value(ev).pipe(Match.tags({
|
|
|
108
108
|
UsageUpdate: () => [s, []],
|
|
109
109
|
WebSearchCall: () => [s, []],
|
|
110
110
|
CitationAdded: () => [s, []],
|
|
111
|
+
ImageOutput: () => [s, []],
|
|
111
112
|
TurnComplete: (e) => {
|
|
112
113
|
const closing = [
|
|
113
114
|
...s.textId !== null ? [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UIMessageStream.mjs","names":[],"sources":["../src/UIMessageStream.ts"],"sourcesContent":["/**\n * Project an effect-uai loop onto the Vercel AI SDK \"UI Message Stream\"\n * protocol (the `v1` wire format, stable across AI SDK v5–v7). The output is a\n * `Stream<SSE.Event>` ready for `SSE.toBytes`; a `useChat` client on the other\n * end consumes it with no frontend changes.\n *\n * The encoder input is an `Emission`: every `InteractionEvent` the loop\n * produces, plus optional `dataPart` and `messageMetadata` emissions a caller\n * interleaves to stream custom typed data (e.g. live metrics) and settled\n * per-message metadata (e.g. usage). A plain `Stream<InteractionEvent>` is a\n * valid `Stream<Emission>`, so existing callers are unaffected.\n *\n * The protocol wants a stable id and explicit start/end lifecycle around each\n * text and reasoning block, which effect-uai's delta events don't carry. This\n * module synthesizes them: one block id per turn, opened on the first delta\n * and closed on `TurnComplete`.\n */\nimport type * as SSE from \"@effect-uai/core/SSE\"\nimport * as Turn from \"@effect-uai/core/Turn\"\nimport { Match, Option, Stream } from \"effect\"\n\n/**\n * The stream parts `useChat` consumes. Names and shapes are the wire\n * contract with the AI SDK client; keep them literal.\n */\nexport type Part =\n | { readonly type: \"start\"; readonly messageId: string }\n | { readonly type: \"text-start\"; readonly id: string }\n | { readonly type: \"text-delta\"; readonly id: string; readonly delta: string }\n | { readonly type: \"text-end\"; readonly id: string }\n | { readonly type: \"reasoning-start\"; readonly id: string }\n | { readonly type: \"reasoning-delta\"; readonly id: string; readonly delta: string }\n | { readonly type: \"reasoning-end\"; readonly id: string }\n | { readonly type: \"tool-input-start\"; readonly toolCallId: string; readonly toolName: string }\n | {\n readonly type: \"tool-input-delta\"\n readonly toolCallId: string\n readonly inputTextDelta: string\n }\n | {\n readonly type: \"tool-input-available\"\n readonly toolCallId: string\n readonly toolName: string\n readonly input: unknown\n }\n | {\n readonly type: \"tool-output-available\"\n readonly toolCallId: string\n readonly output: unknown\n }\n | {\n readonly type: `data-${string}`\n readonly id?: string\n readonly data: unknown\n readonly transient?: boolean\n }\n | { readonly type: \"message-metadata\"; readonly messageMetadata: unknown }\n | { readonly type: \"error\"; readonly errorText: string }\n | { readonly type: \"finish\" }\n\n/**\n * A custom typed data part (`data-<name>`). `transient: true` delivers it to\n * the client's `onData` callback only (not persisted into `message.parts`);\n * reuse an `id` across emissions to reconcile/update one part in place.\n */\nexport type DataEmission = {\n readonly kind: \"data\"\n readonly name: string\n readonly data: unknown\n readonly id?: string\n readonly transient?: boolean\n}\n\n/** Settled per-message metadata, surfaced as `message.metadata` on the client. */\nexport type MetadataEmission = {\n readonly kind: \"metadata\"\n readonly metadata: unknown\n}\n\n/**\n * What `toUIMessageStream` encodes: loop `InteractionEvent`s plus optional\n * data / metadata emissions a caller interleaves into the stream.\n */\nexport type Emission = Turn.InteractionEvent | DataEmission | MetadataEmission\n\n/** Construct a `data-<name>` emission. See {@link DataEmission}. */\nexport const dataPart = (\n name: string,\n data: unknown,\n options?: { readonly id?: string; readonly transient?: boolean },\n): DataEmission => ({ kind: \"data\", name, data, ...options })\n\n/** Construct a message-metadata emission. See {@link MetadataEmission}. */\nexport const messageMetadata = (metadata: unknown): MetadataEmission => ({\n kind: \"metadata\",\n metadata,\n})\n\n/**\n * Headers the UI Message Stream protocol requires on the HTTP response.\n * `x-vercel-ai-ui-message-stream: v1` is mandatory; without it the client\n * falls back to the plain text-stream protocol and ignores non-text parts.\n * Pure data, so it composes with any server (web `Response`, `@effect/platform`\n * `HttpServerResponse`, raw Node) - the package deliberately owns no HTTP layer.\n */\nexport const responseHeaders: Record<string, string> = {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n \"x-vercel-ai-ui-message-stream\": \"v1\",\n}\n\nconst parseJson = Option.liftThrowable(JSON.parse)\n\n// Tool arguments/output cross effect-uai as JSON strings; hand the client the\n// decoded value, falling back to the raw string when it isn't JSON.\nconst decode = (raw: string): unknown => Option.getOrElse(parseJson(raw), () => raw)\n\nconst event = (part: Part | \"[DONE]\"): SSE.Event => ({\n data: part === \"[DONE]\" ? \"[DONE]\" : JSON.stringify(part),\n})\n\ntype State = {\n readonly messageId: string\n readonly textId: string | null\n readonly reasoningId: string | null\n readonly seq: number\n}\n\ntype Step = readonly [State, ReadonlyArray<Part>]\n\nconst toDataPart = (d: DataEmission): Part => {\n const base: Part = { type: `data-${d.name}`, data: d.data }\n const withId = d.id !== undefined ? { ...base, id: d.id } : base\n return d.transient !== undefined ? { ...withId, transient: d.transient } : withId\n}\n\n// One emission fans out to zero or more parts (a first TextDelta opens a block\n// *and* emits its delta), so each step yields an array.\nconst step = (s: State, ev: Emission): Step =>\n Match.value(ev).pipe(\n Match.tags({\n TextDelta: (e): Step => {\n if (s.textId !== null) return [s, [{ type: \"text-delta\", id: s.textId, delta: e.text }]]\n const id = `${s.messageId}:t${s.seq}`\n return [\n { ...s, textId: id, seq: s.seq + 1 },\n [\n { type: \"text-start\", id },\n { type: \"text-delta\", id, delta: e.text },\n ],\n ]\n },\n ReasoningDelta: (e): Step => {\n if (s.reasoningId !== null)\n return [s, [{ type: \"reasoning-delta\", id: s.reasoningId, delta: e.text }]]\n const id = `${s.messageId}:r${s.seq}`\n return [\n { ...s, reasoningId: id, seq: s.seq + 1 },\n [\n { type: \"reasoning-start\", id },\n { type: \"reasoning-delta\", id, delta: e.text },\n ],\n ]\n },\n ToolCallStart: (e): Step => [\n s,\n [{ type: \"tool-input-start\", toolCallId: e.call_id, toolName: e.name }],\n ],\n ToolCallArgsDelta: (e): Step => [\n s,\n [{ type: \"tool-input-delta\", toolCallId: e.call_id, inputTextDelta: e.delta }],\n ],\n RefusalDelta: (e): Step => [s, [{ type: \"error\", errorText: e.text }]],\n UsageUpdate: (): Step => [s, []],\n // Grounding progress and streamed citations have no UI Message Stream\n // part; the citations still arrive on `TurnComplete.turn`.\n WebSearchCall: (): Step => [s, []],\n CitationAdded: (): Step => [s, []],\n TurnComplete: (e): Step => {\n const closing: ReadonlyArray<Part> = [\n ...(s.textId !== null ? [{ type: \"text-end\", id: s.textId } as const] : []),\n ...(s.reasoningId !== null\n ? [{ type: \"reasoning-end\", id: s.reasoningId } as const]\n : []),\n ...Turn.getToolCalls(e.turn).map((call): Part => ({\n type: \"tool-input-available\",\n toolCallId: call.call_id,\n toolName: call.name,\n input: decode(call.arguments),\n })),\n ]\n // Reset block ids; the loop may run further turns after tools resolve.\n return [{ ...s, textId: null, reasoningId: null }, closing]\n },\n }),\n // The non-`_tag` arm of InteractionEvent: a resolved tool result the loop\n // appended after running the tool.\n Match.when({ type: \"function_call_output\" }, (o): Step => [\n s,\n [{ type: \"tool-output-available\", toolCallId: o.call_id, output: decode(o.output) }],\n ]),\n // Caller-interleaved emissions.\n Match.when({ kind: \"data\" }, (d): Step => [s, [toDataPart(d)]]),\n Match.when({ kind: \"metadata\" }, (m): Step => [\n s,\n [{ type: \"message-metadata\", messageMetadata: m.metadata }],\n ]),\n Match.exhaustive,\n )\n\n/**\n * Encode an `InteractionEvent` stream as UI Message Stream `SSE.Event`s,\n * bracketed by the protocol's `start` and terminal `finish` / `[DONE]`\n * markers. `messageId` identifies the assistant message on the client.\n */\nexport const toUIMessageStream =\n (messageId: string) =>\n <E, R>(self: Stream.Stream<Emission, E, R>): Stream.Stream<SSE.Event, E, R> =>\n Stream.make(event({ type: \"start\", messageId })).pipe(\n Stream.concat(\n Stream.mapAccum(\n self,\n (): State => ({ messageId, textId: null, reasoningId: null, seq: 0 }),\n (s, ev: Emission): readonly [State, ReadonlyArray<SSE.Event>] => {\n const [next, parts] = step(s, ev)\n return [next, parts.map(event)]\n },\n ),\n ),\n Stream.concat(Stream.make(event({ type: \"finish\" }), event(\"[DONE]\"))),\n )\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"UIMessageStream.mjs","names":[],"sources":["../src/UIMessageStream.ts"],"sourcesContent":["/**\n * Project an effect-uai loop onto the Vercel AI SDK \"UI Message Stream\"\n * protocol (the `v1` wire format, stable across AI SDK v5–v7). The output is a\n * `Stream<SSE.Event>` ready for `SSE.toBytes`; a `useChat` client on the other\n * end consumes it with no frontend changes.\n *\n * The encoder input is an `Emission`: every `InteractionEvent` the loop\n * produces, plus optional `dataPart` and `messageMetadata` emissions a caller\n * interleaves to stream custom typed data (e.g. live metrics) and settled\n * per-message metadata (e.g. usage). A plain `Stream<InteractionEvent>` is a\n * valid `Stream<Emission>`, so existing callers are unaffected.\n *\n * The protocol wants a stable id and explicit start/end lifecycle around each\n * text and reasoning block, which effect-uai's delta events don't carry. This\n * module synthesizes them: one block id per turn, opened on the first delta\n * and closed on `TurnComplete`.\n */\nimport type * as SSE from \"@effect-uai/core/SSE\"\nimport * as Turn from \"@effect-uai/core/Turn\"\nimport { Match, Option, Stream } from \"effect\"\n\n/**\n * The stream parts `useChat` consumes. Names and shapes are the wire\n * contract with the AI SDK client; keep them literal.\n */\nexport type Part =\n | { readonly type: \"start\"; readonly messageId: string }\n | { readonly type: \"text-start\"; readonly id: string }\n | { readonly type: \"text-delta\"; readonly id: string; readonly delta: string }\n | { readonly type: \"text-end\"; readonly id: string }\n | { readonly type: \"reasoning-start\"; readonly id: string }\n | { readonly type: \"reasoning-delta\"; readonly id: string; readonly delta: string }\n | { readonly type: \"reasoning-end\"; readonly id: string }\n | { readonly type: \"tool-input-start\"; readonly toolCallId: string; readonly toolName: string }\n | {\n readonly type: \"tool-input-delta\"\n readonly toolCallId: string\n readonly inputTextDelta: string\n }\n | {\n readonly type: \"tool-input-available\"\n readonly toolCallId: string\n readonly toolName: string\n readonly input: unknown\n }\n | {\n readonly type: \"tool-output-available\"\n readonly toolCallId: string\n readonly output: unknown\n }\n | {\n readonly type: `data-${string}`\n readonly id?: string\n readonly data: unknown\n readonly transient?: boolean\n }\n | { readonly type: \"message-metadata\"; readonly messageMetadata: unknown }\n | { readonly type: \"error\"; readonly errorText: string }\n | { readonly type: \"finish\" }\n\n/**\n * A custom typed data part (`data-<name>`). `transient: true` delivers it to\n * the client's `onData` callback only (not persisted into `message.parts`);\n * reuse an `id` across emissions to reconcile/update one part in place.\n */\nexport type DataEmission = {\n readonly kind: \"data\"\n readonly name: string\n readonly data: unknown\n readonly id?: string\n readonly transient?: boolean\n}\n\n/** Settled per-message metadata, surfaced as `message.metadata` on the client. */\nexport type MetadataEmission = {\n readonly kind: \"metadata\"\n readonly metadata: unknown\n}\n\n/**\n * What `toUIMessageStream` encodes: loop `InteractionEvent`s plus optional\n * data / metadata emissions a caller interleaves into the stream.\n */\nexport type Emission = Turn.InteractionEvent | DataEmission | MetadataEmission\n\n/** Construct a `data-<name>` emission. See {@link DataEmission}. */\nexport const dataPart = (\n name: string,\n data: unknown,\n options?: { readonly id?: string; readonly transient?: boolean },\n): DataEmission => ({ kind: \"data\", name, data, ...options })\n\n/** Construct a message-metadata emission. See {@link MetadataEmission}. */\nexport const messageMetadata = (metadata: unknown): MetadataEmission => ({\n kind: \"metadata\",\n metadata,\n})\n\n/**\n * Headers the UI Message Stream protocol requires on the HTTP response.\n * `x-vercel-ai-ui-message-stream: v1` is mandatory; without it the client\n * falls back to the plain text-stream protocol and ignores non-text parts.\n * Pure data, so it composes with any server (web `Response`, `@effect/platform`\n * `HttpServerResponse`, raw Node) - the package deliberately owns no HTTP layer.\n */\nexport const responseHeaders: Record<string, string> = {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n \"x-vercel-ai-ui-message-stream\": \"v1\",\n}\n\nconst parseJson = Option.liftThrowable(JSON.parse)\n\n// Tool arguments/output cross effect-uai as JSON strings; hand the client the\n// decoded value, falling back to the raw string when it isn't JSON.\nconst decode = (raw: string): unknown => Option.getOrElse(parseJson(raw), () => raw)\n\nconst event = (part: Part | \"[DONE]\"): SSE.Event => ({\n data: part === \"[DONE]\" ? \"[DONE]\" : JSON.stringify(part),\n})\n\ntype State = {\n readonly messageId: string\n readonly textId: string | null\n readonly reasoningId: string | null\n readonly seq: number\n}\n\ntype Step = readonly [State, ReadonlyArray<Part>]\n\nconst toDataPart = (d: DataEmission): Part => {\n const base: Part = { type: `data-${d.name}`, data: d.data }\n const withId = d.id !== undefined ? { ...base, id: d.id } : base\n return d.transient !== undefined ? { ...withId, transient: d.transient } : withId\n}\n\n// One emission fans out to zero or more parts (a first TextDelta opens a block\n// *and* emits its delta), so each step yields an array.\nconst step = (s: State, ev: Emission): Step =>\n Match.value(ev).pipe(\n Match.tags({\n TextDelta: (e): Step => {\n if (s.textId !== null) return [s, [{ type: \"text-delta\", id: s.textId, delta: e.text }]]\n const id = `${s.messageId}:t${s.seq}`\n return [\n { ...s, textId: id, seq: s.seq + 1 },\n [\n { type: \"text-start\", id },\n { type: \"text-delta\", id, delta: e.text },\n ],\n ]\n },\n ReasoningDelta: (e): Step => {\n if (s.reasoningId !== null)\n return [s, [{ type: \"reasoning-delta\", id: s.reasoningId, delta: e.text }]]\n const id = `${s.messageId}:r${s.seq}`\n return [\n { ...s, reasoningId: id, seq: s.seq + 1 },\n [\n { type: \"reasoning-start\", id },\n { type: \"reasoning-delta\", id, delta: e.text },\n ],\n ]\n },\n ToolCallStart: (e): Step => [\n s,\n [{ type: \"tool-input-start\", toolCallId: e.call_id, toolName: e.name }],\n ],\n ToolCallArgsDelta: (e): Step => [\n s,\n [{ type: \"tool-input-delta\", toolCallId: e.call_id, inputTextDelta: e.delta }],\n ],\n RefusalDelta: (e): Step => [s, [{ type: \"error\", errorText: e.text }]],\n UsageUpdate: (): Step => [s, []],\n // Grounding progress and streamed citations have no UI Message Stream\n // part; the citations still arrive on `TurnComplete.turn`.\n WebSearchCall: (): Step => [s, []],\n CitationAdded: (): Step => [s, []],\n // A model-drawn image reaches the UI on `TurnComplete.turn` as an\n // `output_image` block; the incremental event has no part to map to.\n ImageOutput: (): Step => [s, []],\n TurnComplete: (e): Step => {\n const closing: ReadonlyArray<Part> = [\n ...(s.textId !== null ? [{ type: \"text-end\", id: s.textId } as const] : []),\n ...(s.reasoningId !== null\n ? [{ type: \"reasoning-end\", id: s.reasoningId } as const]\n : []),\n ...Turn.getToolCalls(e.turn).map((call): Part => ({\n type: \"tool-input-available\",\n toolCallId: call.call_id,\n toolName: call.name,\n input: decode(call.arguments),\n })),\n ]\n // Reset block ids; the loop may run further turns after tools resolve.\n return [{ ...s, textId: null, reasoningId: null }, closing]\n },\n }),\n // The non-`_tag` arm of InteractionEvent: a resolved tool result the loop\n // appended after running the tool.\n Match.when({ type: \"function_call_output\" }, (o): Step => [\n s,\n [{ type: \"tool-output-available\", toolCallId: o.call_id, output: decode(o.output) }],\n ]),\n // Caller-interleaved emissions.\n Match.when({ kind: \"data\" }, (d): Step => [s, [toDataPart(d)]]),\n Match.when({ kind: \"metadata\" }, (m): Step => [\n s,\n [{ type: \"message-metadata\", messageMetadata: m.metadata }],\n ]),\n Match.exhaustive,\n )\n\n/**\n * Encode an `InteractionEvent` stream as UI Message Stream `SSE.Event`s,\n * bracketed by the protocol's `start` and terminal `finish` / `[DONE]`\n * markers. `messageId` identifies the assistant message on the client.\n */\nexport const toUIMessageStream =\n (messageId: string) =>\n <E, R>(self: Stream.Stream<Emission, E, R>): Stream.Stream<SSE.Event, E, R> =>\n Stream.make(event({ type: \"start\", messageId })).pipe(\n Stream.concat(\n Stream.mapAccum(\n self,\n (): State => ({ messageId, textId: null, reasoningId: null, seq: 0 }),\n (s, ev: Emission): readonly [State, ReadonlyArray<SSE.Event>] => {\n const [next, parts] = step(s, ev)\n return [next, parts.map(event)]\n },\n ),\n ),\n Stream.concat(Stream.make(event({ type: \"finish\" }), event(\"[DONE]\"))),\n )\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-uai/ai-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Vercel AI SDK (useChat) UI Message Stream compatibility for @effect-uai/core.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"ai": "^7.0.3",
|
|
54
54
|
"effect": "4.0.0-rc.111",
|
|
55
55
|
"typescript": "^7.0.2",
|
|
56
|
-
"@effect-uai/core": "0.
|
|
56
|
+
"@effect-uai/core": "0.14.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@effect-uai/core": ">=0.5.0 <1",
|
package/src/UIMessageStream.ts
CHANGED
|
@@ -177,6 +177,9 @@ const step = (s: State, ev: Emission): Step =>
|
|
|
177
177
|
// part; the citations still arrive on `TurnComplete.turn`.
|
|
178
178
|
WebSearchCall: (): Step => [s, []],
|
|
179
179
|
CitationAdded: (): Step => [s, []],
|
|
180
|
+
// A model-drawn image reaches the UI on `TurnComplete.turn` as an
|
|
181
|
+
// `output_image` block; the incremental event has no part to map to.
|
|
182
|
+
ImageOutput: (): Step => [s, []],
|
|
180
183
|
TurnComplete: (e): Step => {
|
|
181
184
|
const closing: ReadonlyArray<Part> = [
|
|
182
185
|
...(s.textId !== null ? [{ type: "text-end", id: s.textId } as const] : []),
|