@effect-uai/ai-sdk 0.18.0 → 0.19.0-dev-20261110065930

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":"Messages.d.mts","names":[],"sources":["../src/Messages.ts"],"mappings":";;;;;cAmBM,UAAQ,OAAA;;;;KAIF,kBAAkB,SAAS;cAEjC,UAAQ,OAAA;;;;;KAKF,kBAAkB,SAAS;cAEjC,UAAQ,OAAA;;;;;;;;KAQF,kBAAkB,SAAS;KAS3B;WACD;YACC;;KAGA;WACD;WACA;WACA,OAAO,cAAc;;cA8DnB,iBAAc,UACf,cAAc,eACvB,cAAc,MAAM"}
1
+ {"version":3,"file":"Messages.d.mts","names":[],"sources":["../src/Messages.ts"],"mappings":";;;;;cAqBM,UAAQ,OAAA;;;;KAIF,kBAAkB,SAAS;cAEjC,UAAQ,OAAA;;;;;KAKF,kBAAkB,SAAS;cAEjC,UAAQ,OAAA;;;;;;;;KAQF,kBAAkB,SAAS;KAS3B;WACD;YACC;;KAGA;WACD;WACA;WACA,OAAO,cAAc;;cAuEnB,iBAAc,UACf,cAAc,eACvB,cAAc,MAAM"}
package/dist/Messages.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
2
  import * as Image from "@effect-uai/core/Image";
3
3
  import * as Items from "@effect-uai/core/Items";
4
- import { Match, Schema } from "effect";
4
+ import { Match, Option, Predicate, Schema } from "effect";
5
5
  //#region src/Messages.ts
6
6
  /**
7
7
  * Decode the `messages` array a `useChat` client POSTs (Vercel's `UIMessage`
@@ -15,6 +15,7 @@ import { Match, Schema } from "effect";
15
15
  * non-image files and unresolved tool states are dropped.
16
16
  */
17
17
  var Messages_exports = /* @__PURE__ */ __exportAll({ decodeMessages: () => decodeMessages });
18
+ const asJson = Schema.decodeUnknownOption(Schema.Json);
18
19
  const TextPart = Schema.Struct({
19
20
  type: Schema.Literal("text"),
20
21
  text: Schema.String
@@ -61,8 +62,15 @@ const toolItems = (part) => {
61
62
  arguments: JSON.stringify(part.input ?? {})
62
63
  };
63
64
  if (part.state !== "output-available") return [call];
64
- const output = typeof part.output === "string" ? part.output : JSON.stringify(part.output);
65
- return [call, Items.toolCallOutput(part.toolCallId, output)];
65
+ const output = Match.value(part.output).pipe(Match.when(Predicate.isString, (text) => Items.text(text)), Match.orElse((value) => Option.match(asJson(value), {
66
+ onNone: () => Items.text(String(value)),
67
+ onSome: (json) => Items.json(json)
68
+ })));
69
+ return [call, {
70
+ type: "function_call_output",
71
+ call_id: part.toolCallId,
72
+ output: [output]
73
+ }];
66
74
  };
67
75
  const assistantItems = (message) => {
68
76
  const text = textOf(message);
@@ -1 +1 @@
1
- {"version":3,"file":"Messages.mjs","names":[],"sources":["../src/Messages.ts"],"sourcesContent":["/**\n * Decode the `messages` array a `useChat` client POSTs (Vercel's `UIMessage`\n * shape) back into effect-uai `HistoryItem`s, so an existing loop can run\n * against an unchanged frontend.\n *\n * One `UIMessage` can expand to several history items: an assistant message\n * carrying tool calls becomes an assistant `Message` followed by the\n * `ToolCall` / `ToolCallOutput` items the model needs to see. Text and image\n * (`file` with an `image/*` media type) parts fold into a message's content;\n * non-image files and unresolved tool states are dropped.\n */\nimport * as Image from \"@effect-uai/core/Image\"\nimport * as Items from \"@effect-uai/core/Items\"\nimport { Match, Schema } from \"effect\"\n\n// The inbound part variants we understand. Tool parts are identified\n// structurally by `toolCallId` - their wire `type` is a dynamic `tool-<name>`\n// (or `dynamic-tool`) rather than a fixed literal - so their schema keeps\n// `type` open and reads the name off it.\nconst TextPart = Schema.Struct({\n type: Schema.Literal(\"text\"),\n text: Schema.String,\n})\nexport type TextPart = typeof TextPart.Type\n\nconst FilePart = Schema.Struct({\n type: Schema.Literal(\"file\"),\n mediaType: Schema.String,\n url: Schema.String,\n})\nexport type FilePart = typeof FilePart.Type\n\nconst ToolPart = Schema.Struct({\n type: Schema.String,\n toolCallId: Schema.String,\n toolName: Schema.optional(Schema.String),\n state: Schema.optional(Schema.String),\n input: Schema.optional(Schema.Unknown),\n output: Schema.optional(Schema.Unknown),\n})\nexport type ToolPart = typeof ToolPart.Type\n\nconst isText = Schema.is(TextPart)\nconst isFile = Schema.is(FilePart)\nconst isTool = Schema.is(ToolPart)\nconst isImageFile = (part: FilePart): boolean => part.mediaType.startsWith(\"image\")\n\n// A raw inbound part: a discriminating `type` plus whatever else the client\n// attached. Kept open so the schema guards do the narrowing.\nexport type UIPart = {\n readonly type: string\n readonly [key: string]: unknown\n}\n\nexport type UIMessage = {\n readonly id?: string\n readonly role: \"user\" | \"assistant\" | \"system\"\n readonly parts: ReadonlyArray<UIPart>\n}\n\nconst textOf = (message: UIMessage): string =>\n message.parts\n .filter(isText)\n .map((part) => part.text)\n .join(\"\")\n\nconst imageBlocks = (message: UIMessage): ReadonlyArray<Items.InputImage> =>\n message.parts\n .filter(isFile)\n .filter(isImageFile)\n .map((part) => ({ type: \"input_image\", source: Image.imageUrl(part.url, part.mediaType) }))\n\n// A user turn: one joined text block (if any) followed by one image block per\n// image file, preserving the model's expectation of text-then-media.\nconst userMessage = (message: UIMessage): Items.Message => {\n const text = textOf(message)\n return {\n type: \"message\",\n role: \"user\",\n content: [\n ...(text.length > 0 ? [{ type: \"input_text\", text } as const] : []),\n ...imageBlocks(message),\n ],\n }\n}\n\nconst toolName = (part: ToolPart): string =>\n part.type === \"dynamic-tool\" ? (part.toolName ?? \"\") : part.type.slice(\"tool-\".length)\n\n// A resolved tool call becomes a `function_call` item, plus a\n// `function_call_output` once the client carries its output.\nconst toolItems = (part: ToolPart): ReadonlyArray<Items.HistoryItem> => {\n const call: Items.ToolCall = {\n type: \"function_call\",\n call_id: part.toolCallId,\n name: toolName(part),\n arguments: JSON.stringify(part.input ?? {}),\n }\n if (part.state !== \"output-available\") return [call]\n const output = typeof part.output === \"string\" ? part.output : JSON.stringify(part.output)\n return [call, Items.toolCallOutput(part.toolCallId, output)]\n}\n\nconst assistantItems = (message: UIMessage): ReadonlyArray<Items.HistoryItem> => {\n const text = textOf(message)\n return [\n ...(text.length > 0 ? [Items.assistantText(text)] : []),\n ...message.parts.filter(isTool).flatMap(toolItems),\n ]\n}\n\nconst decodeMessage = (message: UIMessage): ReadonlyArray<Items.HistoryItem> =>\n Match.value(message.role).pipe(\n Match.when(\"user\", () => [userMessage(message)]),\n Match.when(\"system\", () => [Items.systemText(textOf(message))]),\n Match.when(\"assistant\", () => assistantItems(message)),\n Match.exhaustive,\n )\n\nexport const decodeMessages = (\n messages: ReadonlyArray<UIMessage>,\n): ReadonlyArray<Items.HistoryItem> => messages.flatMap(decodeMessage)\n"],"mappings":";;;;;;;;;;;;;;;;;AAmBA,MAAM,WAAW,OAAO,OAAO;CAC7B,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,OAAO;AACf,CAAC;AAGD,MAAM,WAAW,OAAO,OAAO;CAC7B,MAAM,OAAO,QAAQ,MAAM;CAC3B,WAAW,OAAO;CAClB,KAAK,OAAO;AACd,CAAC;AAGD,MAAM,WAAW,OAAO,OAAO;CAC7B,MAAM,OAAO;CACb,YAAY,OAAO;CACnB,UAAU,OAAO,SAAS,OAAO,MAAM;CACvC,OAAO,OAAO,SAAS,OAAO,MAAM;CACpC,OAAO,OAAO,SAAS,OAAO,OAAO;CACrC,QAAQ,OAAO,SAAS,OAAO,OAAO;AACxC,CAAC;AAGD,MAAM,SAAS,OAAO,GAAG,QAAQ;AACjC,MAAM,SAAS,OAAO,GAAG,QAAQ;AACjC,MAAM,SAAS,OAAO,GAAG,QAAQ;AACjC,MAAM,eAAe,SAA4B,KAAK,UAAU,WAAW,OAAO;AAelF,MAAM,UAAU,YACd,QAAQ,MACL,OAAO,MAAM,CAAC,CACd,KAAK,SAAS,KAAK,IAAI,CAAC,CACxB,KAAK,EAAE;AAEZ,MAAM,eAAe,YACnB,QAAQ,MACL,OAAO,MAAM,CAAC,CACd,OAAO,WAAW,CAAC,CACnB,KAAK,UAAU;CAAE,MAAM;CAAe,QAAQ,MAAM,SAAS,KAAK,KAAK,KAAK,SAAS;AAAE,EAAE;AAI9F,MAAM,eAAe,YAAsC;CACzD,MAAM,OAAO,OAAO,OAAO;CAC3B,OAAO;EACL,MAAM;EACN,MAAM;EACN,SAAS,CACP,GAAI,KAAK,SAAS,IAAI,CAAC;GAAE,MAAM;GAAc;EAAK,CAAU,IAAI,CAAC,GACjE,GAAG,YAAY,OAAO,CACxB;CACF;AACF;AAEA,MAAM,YAAY,SAChB,KAAK,SAAS,iBAAkB,KAAK,YAAY,KAAM,KAAK,KAAK,MAAM,CAAc;AAIvF,MAAM,aAAa,SAAqD;CACtE,MAAM,OAAuB;EAC3B,MAAM;EACN,SAAS,KAAK;EACd,MAAM,SAAS,IAAI;EACnB,WAAW,KAAK,UAAU,KAAK,SAAS,CAAC,CAAC;CAC5C;CACA,IAAI,KAAK,UAAU,oBAAoB,OAAO,CAAC,IAAI;CACnD,MAAM,SAAS,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS,KAAK,UAAU,KAAK,MAAM;CACzF,OAAO,CAAC,MAAM,MAAM,eAAe,KAAK,YAAY,MAAM,CAAC;AAC7D;AAEA,MAAM,kBAAkB,YAAyD;CAC/E,MAAM,OAAO,OAAO,OAAO;CAC3B,OAAO,CACL,GAAI,KAAK,SAAS,IAAI,CAAC,MAAM,cAAc,IAAI,CAAC,IAAI,CAAC,GACrD,GAAG,QAAQ,MAAM,OAAO,MAAM,CAAC,CAAC,QAAQ,SAAS,CACnD;AACF;AAEA,MAAM,iBAAiB,YACrB,MAAM,MAAM,QAAQ,IAAI,CAAC,CAAC,KACxB,MAAM,KAAK,cAAc,CAAC,YAAY,OAAO,CAAC,CAAC,GAC/C,MAAM,KAAK,gBAAgB,CAAC,MAAM,WAAW,OAAO,OAAO,CAAC,CAAC,CAAC,GAC9D,MAAM,KAAK,mBAAmB,eAAe,OAAO,CAAC,GACrD,MAAM,UACR;AAEF,MAAa,kBACX,aACqC,SAAS,QAAQ,aAAa"}
1
+ {"version":3,"file":"Messages.mjs","names":[],"sources":["../src/Messages.ts"],"sourcesContent":["/**\n * Decode the `messages` array a `useChat` client POSTs (Vercel's `UIMessage`\n * shape) back into effect-uai `HistoryItem`s, so an existing loop can run\n * against an unchanged frontend.\n *\n * One `UIMessage` can expand to several history items: an assistant message\n * carrying tool calls becomes an assistant `Message` followed by the\n * `ToolCall` / `ToolCallOutput` items the model needs to see. Text and image\n * (`file` with an `image/*` media type) parts fold into a message's content;\n * non-image files and unresolved tool states are dropped.\n */\nimport * as Image from \"@effect-uai/core/Image\"\nimport * as Items from \"@effect-uai/core/Items\"\nimport { Match, Option, Predicate, Schema } from \"effect\"\n\nconst asJson = Schema.decodeUnknownOption(Schema.Json)\n\n// The inbound part variants we understand. Tool parts are identified\n// structurally by `toolCallId` - their wire `type` is a dynamic `tool-<name>`\n// (or `dynamic-tool`) rather than a fixed literal - so their schema keeps\n// `type` open and reads the name off it.\nconst TextPart = Schema.Struct({\n type: Schema.Literal(\"text\"),\n text: Schema.String,\n})\nexport type TextPart = typeof TextPart.Type\n\nconst FilePart = Schema.Struct({\n type: Schema.Literal(\"file\"),\n mediaType: Schema.String,\n url: Schema.String,\n})\nexport type FilePart = typeof FilePart.Type\n\nconst ToolPart = Schema.Struct({\n type: Schema.String,\n toolCallId: Schema.String,\n toolName: Schema.optional(Schema.String),\n state: Schema.optional(Schema.String),\n input: Schema.optional(Schema.Unknown),\n output: Schema.optional(Schema.Unknown),\n})\nexport type ToolPart = typeof ToolPart.Type\n\nconst isText = Schema.is(TextPart)\nconst isFile = Schema.is(FilePart)\nconst isTool = Schema.is(ToolPart)\nconst isImageFile = (part: FilePart): boolean => part.mediaType.startsWith(\"image\")\n\n// A raw inbound part: a discriminating `type` plus whatever else the client\n// attached. Kept open so the schema guards do the narrowing.\nexport type UIPart = {\n readonly type: string\n readonly [key: string]: unknown\n}\n\nexport type UIMessage = {\n readonly id?: string\n readonly role: \"user\" | \"assistant\" | \"system\"\n readonly parts: ReadonlyArray<UIPart>\n}\n\nconst textOf = (message: UIMessage): string =>\n message.parts\n .filter(isText)\n .map((part) => part.text)\n .join(\"\")\n\nconst imageBlocks = (message: UIMessage): ReadonlyArray<Items.InputImage> =>\n message.parts\n .filter(isFile)\n .filter(isImageFile)\n .map((part) => ({ type: \"input_image\", source: Image.imageUrl(part.url, part.mediaType) }))\n\n// A user turn: one joined text block (if any) followed by one image block per\n// image file, preserving the model's expectation of text-then-media.\nconst userMessage = (message: UIMessage): Items.Message => {\n const text = textOf(message)\n return {\n type: \"message\",\n role: \"user\",\n content: [\n ...(text.length > 0 ? [{ type: \"input_text\", text } as const] : []),\n ...imageBlocks(message),\n ],\n }\n}\n\nconst toolName = (part: ToolPart): string =>\n part.type === \"dynamic-tool\" ? (part.toolName ?? \"\") : part.type.slice(\"tool-\".length)\n\n// A resolved tool call becomes a `function_call` item, plus a\n// `function_call_output` once the client carries its output.\nconst toolItems = (part: ToolPart): ReadonlyArray<Items.HistoryItem> => {\n const call: Items.ToolCall = {\n type: \"function_call\",\n call_id: part.toolCallId,\n name: toolName(part),\n arguments: JSON.stringify(part.input ?? {}),\n }\n if (part.state !== \"output-available\") return [call]\n // Text stays text; any other JSON stays a value (the provider codec serializes it).\n const output: Items.ToolOutputContent = Match.value(part.output).pipe(\n Match.when(Predicate.isString, (text) => Items.text(text)),\n Match.orElse((value) =>\n Option.match(asJson(value), {\n onNone: () => Items.text(String(value)),\n onSome: (json) => Items.json(json),\n }),\n ),\n )\n return [call, { type: \"function_call_output\", call_id: part.toolCallId, output: [output] }]\n}\n\nconst assistantItems = (message: UIMessage): ReadonlyArray<Items.HistoryItem> => {\n const text = textOf(message)\n return [\n ...(text.length > 0 ? [Items.assistantText(text)] : []),\n ...message.parts.filter(isTool).flatMap(toolItems),\n ]\n}\n\nconst decodeMessage = (message: UIMessage): ReadonlyArray<Items.HistoryItem> =>\n Match.value(message.role).pipe(\n Match.when(\"user\", () => [userMessage(message)]),\n Match.when(\"system\", () => [Items.systemText(textOf(message))]),\n Match.when(\"assistant\", () => assistantItems(message)),\n Match.exhaustive,\n )\n\nexport const decodeMessages = (\n messages: ReadonlyArray<UIMessage>,\n): ReadonlyArray<Items.HistoryItem> => messages.flatMap(decodeMessage)\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,MAAM,SAAS,OAAO,oBAAoB,OAAO,IAAI;AAMrD,MAAM,WAAW,OAAO,OAAO;CAC7B,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,OAAO;AACf,CAAC;AAGD,MAAM,WAAW,OAAO,OAAO;CAC7B,MAAM,OAAO,QAAQ,MAAM;CAC3B,WAAW,OAAO;CAClB,KAAK,OAAO;AACd,CAAC;AAGD,MAAM,WAAW,OAAO,OAAO;CAC7B,MAAM,OAAO;CACb,YAAY,OAAO;CACnB,UAAU,OAAO,SAAS,OAAO,MAAM;CACvC,OAAO,OAAO,SAAS,OAAO,MAAM;CACpC,OAAO,OAAO,SAAS,OAAO,OAAO;CACrC,QAAQ,OAAO,SAAS,OAAO,OAAO;AACxC,CAAC;AAGD,MAAM,SAAS,OAAO,GAAG,QAAQ;AACjC,MAAM,SAAS,OAAO,GAAG,QAAQ;AACjC,MAAM,SAAS,OAAO,GAAG,QAAQ;AACjC,MAAM,eAAe,SAA4B,KAAK,UAAU,WAAW,OAAO;AAelF,MAAM,UAAU,YACd,QAAQ,MACL,OAAO,MAAM,CAAC,CACd,KAAK,SAAS,KAAK,IAAI,CAAC,CACxB,KAAK,EAAE;AAEZ,MAAM,eAAe,YACnB,QAAQ,MACL,OAAO,MAAM,CAAC,CACd,OAAO,WAAW,CAAC,CACnB,KAAK,UAAU;CAAE,MAAM;CAAe,QAAQ,MAAM,SAAS,KAAK,KAAK,KAAK,SAAS;AAAE,EAAE;AAI9F,MAAM,eAAe,YAAsC;CACzD,MAAM,OAAO,OAAO,OAAO;CAC3B,OAAO;EACL,MAAM;EACN,MAAM;EACN,SAAS,CACP,GAAI,KAAK,SAAS,IAAI,CAAC;GAAE,MAAM;GAAc;EAAK,CAAU,IAAI,CAAC,GACjE,GAAG,YAAY,OAAO,CACxB;CACF;AACF;AAEA,MAAM,YAAY,SAChB,KAAK,SAAS,iBAAkB,KAAK,YAAY,KAAM,KAAK,KAAK,MAAM,CAAc;AAIvF,MAAM,aAAa,SAAqD;CACtE,MAAM,OAAuB;EAC3B,MAAM;EACN,SAAS,KAAK;EACd,MAAM,SAAS,IAAI;EACnB,WAAW,KAAK,UAAU,KAAK,SAAS,CAAC,CAAC;CAC5C;CACA,IAAI,KAAK,UAAU,oBAAoB,OAAO,CAAC,IAAI;CAEnD,MAAM,SAAkC,MAAM,MAAM,KAAK,MAAM,CAAC,CAAC,KAC/D,MAAM,KAAK,UAAU,WAAW,SAAS,MAAM,KAAK,IAAI,CAAC,GACzD,MAAM,QAAQ,UACZ,OAAO,MAAM,OAAO,KAAK,GAAG;EAC1B,cAAc,MAAM,KAAK,OAAO,KAAK,CAAC;EACtC,SAAS,SAAS,MAAM,KAAK,IAAI;CACnC,CAAC,CACH,CACF;CACA,OAAO,CAAC,MAAM;EAAE,MAAM;EAAwB,SAAS,KAAK;EAAY,QAAQ,CAAC,MAAM;CAAE,CAAC;AAC5F;AAEA,MAAM,kBAAkB,YAAyD;CAC/E,MAAM,OAAO,OAAO,OAAO;CAC3B,OAAO,CACL,GAAI,KAAK,SAAS,IAAI,CAAC,MAAM,cAAc,IAAI,CAAC,IAAI,CAAC,GACrD,GAAG,QAAQ,MAAM,OAAO,MAAM,CAAC,CAAC,QAAQ,SAAS,CACnD;AACF;AAEA,MAAM,iBAAiB,YACrB,MAAM,MAAM,QAAQ,IAAI,CAAC,CAAC,KACxB,MAAM,KAAK,cAAc,CAAC,YAAY,OAAO,CAAC,CAAC,GAC/C,MAAM,KAAK,gBAAgB,CAAC,MAAM,WAAW,OAAO,OAAO,CAAC,CAAC,CAAC,GAC9D,MAAM,KAAK,mBAAmB,eAAe,OAAO,CAAC,GACrD,MAAM,UACR;AAEF,MAAa,kBACX,aACqC,SAAS,QAAQ,aAAa"}
@@ -48,6 +48,10 @@ type Part = {
48
48
  readonly type: "tool-output-available";
49
49
  readonly toolCallId: string;
50
50
  readonly output: unknown;
51
+ } | {
52
+ readonly type: "tool-output-error";
53
+ readonly toolCallId: string;
54
+ readonly errorText: string;
51
55
  } | {
52
56
  readonly type: `data-${string}`;
53
57
  readonly id?: string;
@@ -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;;;;;;cAkHjB,oBAAiB,uBAE3B,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,IAAI,OAAO,GAAG"}
1
+ {"version":3,"file":"UIMessageStream.d.mts","names":[],"sources":["../src/UIMessageStream.ts"],"mappings":";;;;;;;;;;KA8BY;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;;WAEA;WAAoC;WAA6B;;WAEjE;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;;;;;;cAkJjB,oBAAiB,uBAE3B,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,IAAI,OAAO,GAAG"}
@@ -1,6 +1,8 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { Match, Option, Stream } from "effect";
3
3
  import * as Turn from "@effect-uai/core/Turn";
4
+ import * as ToolOutput from "@effect-uai/core/ToolOutput";
5
+ import { Base64 } from "effect/encoding";
4
6
  //#region src/UIMessageStream.ts
5
7
  var UIMessageStream_exports = /* @__PURE__ */ __exportAll({
6
8
  dataPart: () => dataPart,
@@ -35,6 +37,37 @@ const responseHeaders = {
35
37
  };
36
38
  const parseJson = Option.liftThrowable(JSON.parse);
37
39
  const decode = (raw) => Option.getOrElse(parseJson(raw), () => raw);
40
+ const sourceUrl = Match.type().pipe(Match.tagsExhaustive({
41
+ url: (s) => s.url,
42
+ base64: (s) => `data:${s.mimeType};base64,${s.base64}`,
43
+ bytes: (s) => `data:${s.mimeType};base64,${Base64.encode(s.bytes)}`
44
+ }));
45
+ const partJson = Match.type().pipe(Match.discriminatorsExhaustive("type")({
46
+ input_text: (p) => p.text,
47
+ input_json: (p) => p.value,
48
+ input_image: (p) => ({
49
+ type: "image",
50
+ url: sourceUrl(p.source)
51
+ }),
52
+ input_file: (p) => ({
53
+ type: "file",
54
+ url: sourceUrl(p.source),
55
+ ...p.filename !== void 0 && { filename: p.filename }
56
+ })
57
+ }));
58
+ const outputJson = (o) => ToolOutput.single(o.output.map(partJson));
59
+ const toolOutputPart = (o) => {
60
+ const output = outputJson(o);
61
+ return o.isError === true ? {
62
+ type: "tool-output-error",
63
+ toolCallId: o.call_id,
64
+ errorText: typeof output === "string" ? output : JSON.stringify(output)
65
+ } : {
66
+ type: "tool-output-available",
67
+ toolCallId: o.call_id,
68
+ output
69
+ };
70
+ };
38
71
  const event = (part) => ({ data: part === "[DONE]" ? "[DONE]" : JSON.stringify(part) });
39
72
  const toDataPart = (d) => {
40
73
  const base = {
@@ -132,11 +165,7 @@ const step = (s, ev) => Match.value(ev).pipe(Match.tags({
132
165
  reasoningId: null
133
166
  }, closing];
134
167
  }
135
- }), Match.when({ type: "function_call_output" }, (o) => [s, [{
136
- type: "tool-output-available",
137
- toolCallId: o.call_id,
138
- output: decode(o.output)
139
- }]]), Match.when({ kind: "data" }, (d) => [s, [toDataPart(d)]]), Match.when({ kind: "metadata" }, (m) => [s, [{
168
+ }), Match.when({ type: "function_call_output" }, (o) => [s, [toolOutputPart(o)]]), Match.when({ kind: "data" }, (d) => [s, [toDataPart(d)]]), Match.when({ kind: "metadata" }, (m) => [s, [{
140
169
  type: "message-metadata",
141
170
  messageMetadata: m.metadata
142
171
  }]]), Match.exhaustive);
@@ -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 // 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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"}
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 { FileSource } from \"@effect-uai/core/File\"\nimport type { ImageSource } from \"@effect-uai/core/Image\"\nimport type * as Items from \"@effect-uai/core/Items\"\nimport type * as SSE from \"@effect-uai/core/SSE\"\nimport * as ToolOutput from \"@effect-uai/core/ToolOutput\"\nimport * as Turn from \"@effect-uai/core/Turn\"\nimport { Match, Option, Stream } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\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 | { readonly type: \"tool-output-error\"; readonly toolCallId: string; readonly errorText: string }\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 cross effect-uai as a JSON string; 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\n// Media goes to the client as a URL; inline bytes as a data URL.\nconst sourceUrl = Match.type<ImageSource | FileSource>().pipe(\n Match.tagsExhaustive({\n url: (s) => s.url,\n base64: (s) => `data:${s.mimeType};base64,${s.base64}`,\n bytes: (s) => `data:${s.mimeType};base64,${Base64.encode(s.bytes)}`,\n }),\n)\n\nconst partJson = Match.type<Items.ToolOutputContent>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (p): unknown => p.text,\n input_json: (p) => p.value,\n input_image: (p) => ({ type: \"image\", url: sourceUrl(p.source) }),\n input_file: (p) => ({\n type: \"file\",\n url: sourceUrl(p.source),\n ...(p.filename !== undefined && { filename: p.filename }),\n }),\n }),\n)\n\nconst outputJson = (o: Items.ToolCallOutput): unknown => ToolOutput.single(o.output.map(partJson))\n\nconst toolOutputPart = (o: Items.ToolCallOutput): Part => {\n const output = outputJson(o)\n return o.isError === true\n ? {\n type: \"tool-output-error\",\n toolCallId: o.call_id,\n errorText: typeof output === \"string\" ? output : JSON.stringify(output),\n }\n : { type: \"tool-output-available\", toolCallId: o.call_id, output }\n}\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 => [s, [toolOutputPart(o)]]),\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,5 +1,5 @@
1
1
  import { decodeMessages } from "./Messages.mjs";
2
- import { n as globalExpect, r as describe, t as it } from "./dist-Cu2gIMHV.mjs";
2
+ import { n as globalExpect, r as describe, t as it } from "./dist-DvYz71Uo.mjs";
3
3
  import { dataPart, messageMetadata, toUIMessageStream } from "./UIMessageStream.mjs";
4
4
  import * as Image from "@effect-uai/core/Image";
5
5
  import * as Items from "@effect-uai/core/Items";
@@ -53,7 +53,11 @@ describe("toUIMessageStream", () => {
53
53
  name: "search",
54
54
  arguments: `{"q":"lisbon"}`
55
55
  }], "tool_calls") }),
56
- Items.toolCallOutput("c1", `{"hits":3}`)
56
+ {
57
+ type: "function_call_output",
58
+ call_id: "c1",
59
+ output: [Items.json({ hits: 3 })]
60
+ }
57
61
  ]);
58
62
  globalExpect(types(parts)).toEqual([
59
63
  "start",
@@ -76,6 +80,41 @@ describe("toUIMessageStream", () => {
76
80
  output: { hits: 3 }
77
81
  });
78
82
  }));
83
+ it.effect("maps a failed call to tool-output-error and media to a data URL", () => Effect.gen(function* () {
84
+ const parts = yield* encode([{
85
+ type: "function_call_output",
86
+ call_id: "c1",
87
+ output: [Items.json({
88
+ _tag: "CityNotFound",
89
+ city: "Atlantis"
90
+ })],
91
+ isError: true
92
+ }, {
93
+ type: "function_call_output",
94
+ call_id: "c2",
95
+ output: [Items.text("Home"), Items.image({
96
+ _tag: "bytes",
97
+ bytes: new Uint8Array([
98
+ 1,
99
+ 2,
100
+ 3
101
+ ]),
102
+ mimeType: "image/png"
103
+ })]
104
+ }]);
105
+ globalExpect(parts.slice(1, 3)).toEqual([{
106
+ type: "tool-output-error",
107
+ toolCallId: "c1",
108
+ errorText: "{\"_tag\":\"CityNotFound\",\"city\":\"Atlantis\"}"
109
+ }, {
110
+ type: "tool-output-available",
111
+ toolCallId: "c2",
112
+ output: ["Home", {
113
+ type: "image",
114
+ url: "data:image/png;base64,AQID"
115
+ }]
116
+ }]);
117
+ }));
79
118
  it.effect("surfaces a refusal as an error part", () => Effect.gen(function* () {
80
119
  const parts = yield* encode([Turn.TurnEvent.RefusalDelta({ text: "cannot help" })]);
81
120
  globalExpect(types(parts)).toEqual([
@@ -215,7 +254,11 @@ describe("decodeMessages", () => {
215
254
  name: "search",
216
255
  arguments: `{"q":"lisbon"}`
217
256
  },
218
- Items.toolCallOutput("c1", `{"hits":3}`)
257
+ {
258
+ type: "function_call_output",
259
+ call_id: "c1",
260
+ output: [Items.json({ hits: 3 })]
261
+ }
219
262
  ]);
220
263
  });
221
264
  it("emits only function_call for a dynamic tool with no resolved output", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"UIMessageStream.test.mjs","names":[],"sources":["../src/UIMessageStream.test.ts"],"sourcesContent":["import * as Image from \"@effect-uai/core/Image\"\nimport * as Items from \"@effect-uai/core/Items\"\nimport * as Turn from \"@effect-uai/core/Turn\"\nimport { describe, it } from \"@effect/vitest\"\nimport { Effect, Stream } from \"effect\"\nimport { expect } from \"vitest\"\nimport { decodeMessages } from \"./Messages.js\"\nimport { dataPart, type Emission, messageMetadata, toUIMessageStream } from \"./UIMessageStream.js\"\n\nconst turn = (items: ReadonlyArray<Items.HistoryItem>, stop: Items.StopReason): Turn.Turn => ({\n items,\n usage: {},\n stop_reason: stop,\n})\n\n// Run an InteractionEvent script through the encoder and return the decoded\n// parts (the `data:` payload of each SSE event, JSON-parsed except `[DONE]`).\nconst encode = (events: ReadonlyArray<Emission>): Effect.Effect<ReadonlyArray<unknown>> =>\n Stream.fromIterable(events).pipe(\n toUIMessageStream(\"m1\"),\n Stream.runCollect,\n Effect.map((sse) => sse.map((e) => (e.data === \"[DONE]\" ? \"[DONE]\" : JSON.parse(e.data)))),\n )\n\nconst types = (parts: ReadonlyArray<unknown>): ReadonlyArray<string> =>\n parts.map((p) => (p === \"[DONE]\" ? \"[DONE]\" : (p as { type: string }).type))\n\ndescribe(\"toUIMessageStream\", () => {\n it.effect(\"brackets text deltas with start / text-start / text-end / finish / [DONE]\", () =>\n Effect.gen(function* () {\n const parts = yield* encode([\n Turn.TurnEvent.TextDelta({ text: \"Hel\" }),\n Turn.TurnEvent.TextDelta({ text: \"lo\" }),\n Turn.TurnEvent.TurnComplete({ turn: turn([Items.assistantText(\"Hello\")], \"stop\") }),\n ])\n\n expect(types(parts)).toEqual([\n \"start\",\n \"text-start\",\n \"text-delta\",\n \"text-delta\",\n \"text-end\",\n \"finish\",\n \"[DONE]\",\n ])\n expect(parts[0]).toEqual({ type: \"start\", messageId: \"m1\" })\n // Both deltas share the one synthesized block id.\n const id = (parts[1] as { id: string }).id\n expect((parts[2] as { id: string }).id).toBe(id)\n expect((parts[4] as { id: string }).id).toBe(id)\n }),\n )\n\n it.effect(\"maps a tool round-trip to input-start/delta/available then output-available\", () =>\n Effect.gen(function* () {\n const call: Items.ToolCall = {\n type: \"function_call\",\n call_id: \"c1\",\n name: \"search\",\n arguments: `{\"q\":\"lisbon\"}`,\n }\n const parts = yield* encode([\n Turn.TurnEvent.ToolCallStart({ call_id: \"c1\", name: \"search\" }),\n Turn.TurnEvent.ToolCallArgsDelta({ call_id: \"c1\", delta: `{\"q\":\"lisbon\"}` }),\n Turn.TurnEvent.TurnComplete({ turn: turn([call], \"tool_calls\") }),\n Items.toolCallOutput(\"c1\", `{\"hits\":3}`),\n ])\n\n expect(types(parts)).toEqual([\n \"start\",\n \"tool-input-start\",\n \"tool-input-delta\",\n \"tool-input-available\",\n \"tool-output-available\",\n \"finish\",\n \"[DONE]\",\n ])\n // JSON payloads are decoded, not passed through as strings.\n expect(parts[3]).toEqual({\n type: \"tool-input-available\",\n toolCallId: \"c1\",\n toolName: \"search\",\n input: { q: \"lisbon\" },\n })\n expect(parts[4]).toEqual({\n type: \"tool-output-available\",\n toolCallId: \"c1\",\n output: { hits: 3 },\n })\n }),\n )\n\n it.effect(\"surfaces a refusal as an error part\", () =>\n Effect.gen(function* () {\n const parts = yield* encode([Turn.TurnEvent.RefusalDelta({ text: \"cannot help\" })])\n expect(types(parts)).toEqual([\"start\", \"error\", \"finish\", \"[DONE]\"])\n expect(parts[1]).toEqual({ type: \"error\", errorText: \"cannot help\" })\n }),\n )\n\n it.effect(\"interleaves data-part and message-metadata emissions\", () =>\n Effect.gen(function* () {\n const parts = yield* encode([\n dataPart(\"metrics\", { tokps: 42 }, { transient: true }),\n Turn.TurnEvent.TextDelta({ text: \"hi\" }),\n messageMetadata({ model: \"gpt-5.4-mini\", usage: { output_tokens: 7 } }),\n Turn.TurnEvent.TurnComplete({ turn: turn([Items.assistantText(\"hi\")], \"stop\") }),\n ])\n\n expect(types(parts)).toEqual([\n \"start\",\n \"data-metrics\",\n \"text-start\",\n \"text-delta\",\n \"message-metadata\",\n \"text-end\",\n \"finish\",\n \"[DONE]\",\n ])\n expect(parts[1]).toEqual({ type: \"data-metrics\", data: { tokps: 42 }, transient: true })\n expect(parts[4]).toEqual({\n type: \"message-metadata\",\n messageMetadata: { model: \"gpt-5.4-mini\", usage: { output_tokens: 7 } },\n })\n }),\n )\n})\n\ndescribe(\"decodeMessages\", () => {\n it(\"folds UIMessage text parts back into HistoryItems by role\", () => {\n const items = decodeMessages([\n { role: \"system\", parts: [{ type: \"text\", text: \"be terse\" }] },\n {\n role: \"user\",\n parts: [\n { type: \"text\", text: \"hi \" },\n { type: \"text\", text: \"there\" },\n ],\n },\n { role: \"assistant\", parts: [{ type: \"text\", text: \"hello\" }] },\n ])\n\n expect(items).toEqual([\n Items.systemText(\"be terse\"),\n Items.userText(\"hi there\"),\n Items.assistantText(\"hello\"),\n ])\n })\n\n it(\"folds an image file part into an input_image block and drops non-image files\", () => {\n const items = decodeMessages([\n {\n role: \"user\",\n parts: [\n { type: \"text\", text: \"look\", state: \"done\" },\n { type: \"file\", mediaType: \"image/png\", url: \"https://x/i.png\", filename: \"i.png\" },\n { type: \"file\", mediaType: \"application/pdf\", url: \"https://x/doc.pdf\" },\n ],\n },\n ])\n\n expect(items).toEqual([\n {\n type: \"message\",\n role: \"user\",\n content: [\n { type: \"input_text\", text: \"look\" },\n { type: \"input_image\", source: Image.imageUrl(\"https://x/i.png\", \"image/png\") },\n ],\n },\n ])\n })\n\n it(\"expands an assistant tool call into function_call + function_call_output\", () => {\n const items = decodeMessages([\n {\n role: \"assistant\",\n parts: [\n { type: \"text\", text: \"Here\", state: \"done\" },\n {\n type: \"tool-search\",\n toolCallId: \"c1\",\n state: \"output-available\",\n input: { q: \"lisbon\" },\n output: { hits: 3 },\n providerExecuted: false,\n },\n ],\n },\n ])\n\n expect(items).toEqual([\n Items.assistantText(\"Here\"),\n { type: \"function_call\", call_id: \"c1\", name: \"search\", arguments: `{\"q\":\"lisbon\"}` },\n Items.toolCallOutput(\"c1\", `{\"hits\":3}`),\n ])\n })\n\n it(\"emits only function_call for a dynamic tool with no resolved output\", () => {\n const items = decodeMessages([\n {\n role: \"assistant\",\n parts: [\n {\n type: \"dynamic-tool\",\n toolName: \"lookup\",\n toolCallId: \"c9\",\n state: \"input-available\",\n input: { id: 7 },\n },\n ],\n },\n ])\n\n expect(items).toEqual([\n { type: \"function_call\", call_id: \"c9\", name: \"lookup\", arguments: `{\"id\":7}` },\n ])\n })\n})\n"],"mappings":";;;;;;;;AASA,MAAM,QAAQ,OAAyC,UAAuC;CAC5F;CACA,OAAO,CAAC;CACR,aAAa;AACf;AAIA,MAAM,UAAU,WACd,OAAO,aAAa,MAAM,CAAC,CAAC,KAC1B,kBAAkB,IAAI,GACtB,OAAO,YACP,OAAO,KAAK,QAAQ,IAAI,KAAK,MAAO,EAAE,SAAS,WAAW,WAAW,KAAK,MAAM,EAAE,IAAI,CAAE,CAAC,CAC3F;AAEF,MAAM,SAAS,UACb,MAAM,KAAK,MAAO,MAAM,WAAW,WAAY,EAAuB,IAAK;AAE7E,SAAS,2BAA2B;CAClC,GAAG,OAAO,mFACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,OAAO;GAC1B,KAAK,UAAU,UAAU,EAAE,MAAM,MAAM,CAAC;GACxC,KAAK,UAAU,UAAU,EAAE,MAAM,KAAK,CAAC;GACvC,KAAK,UAAU,aAAa,EAAE,MAAM,KAAK,CAAC,MAAM,cAAc,OAAO,CAAC,GAAG,MAAM,EAAE,CAAC;EACpF,CAAC;EAED,aAAO,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;EACD,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GAAE,MAAM;GAAS,WAAW;EAAK,CAAC;EAE3D,MAAM,KAAM,MAAM,EAAE,CAAoB;EACxC,aAAQ,MAAM,EAAE,CAAoB,EAAE,CAAC,CAAC,KAAK,EAAE;EAC/C,aAAQ,MAAM,EAAE,CAAoB,EAAE,CAAC,CAAC,KAAK,EAAE;CACjD,CAAC,CACH;CAEA,GAAG,OAAO,qFACR,OAAO,IAAI,aAAa;EAOtB,MAAM,QAAQ,OAAO,OAAO;GAC1B,KAAK,UAAU,cAAc;IAAE,SAAS;IAAM,MAAM;GAAS,CAAC;GAC9D,KAAK,UAAU,kBAAkB;IAAE,SAAS;IAAM,OAAO;GAAiB,CAAC;GAC3E,KAAK,UAAU,aAAa,EAAE,MAAM,KAAK,CAAC;IAR1C,MAAM;IACN,SAAS;IACT,MAAM;IACN,WAAW;GAKkC,CAAC,GAAG,YAAY,EAAE,CAAC;GAChE,MAAM,eAAe,MAAM,YAAY;EACzC,CAAC;EAED,aAAO,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;EAED,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GACvB,MAAM;GACN,YAAY;GACZ,UAAU;GACV,OAAO,EAAE,GAAG,SAAS;EACvB,CAAC;EACD,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GACvB,MAAM;GACN,YAAY;GACZ,QAAQ,EAAE,MAAM,EAAE;EACpB,CAAC;CACH,CAAC,CACH;CAEA,GAAG,OAAO,6CACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,OAAO,CAAC,KAAK,UAAU,aAAa,EAAE,MAAM,cAAc,CAAC,CAAC,CAAC;EAClF,aAAO,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ;GAAC;GAAS;GAAS;GAAU;EAAQ,CAAC;EACnE,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GAAE,MAAM;GAAS,WAAW;EAAc,CAAC;CACtE,CAAC,CACH;CAEA,GAAG,OAAO,8DACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,OAAO;GAC1B,SAAS,WAAW,EAAE,OAAO,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;GACtD,KAAK,UAAU,UAAU,EAAE,MAAM,KAAK,CAAC;GACvC,gBAAgB;IAAE,OAAO;IAAgB,OAAO,EAAE,eAAe,EAAE;GAAE,CAAC;GACtE,KAAK,UAAU,aAAa,EAAE,MAAM,KAAK,CAAC,MAAM,cAAc,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;EACjF,CAAC;EAED,aAAO,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;EACD,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GAAE,MAAM;GAAgB,MAAM,EAAE,OAAO,GAAG;GAAG,WAAW;EAAK,CAAC;EACvF,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GACvB,MAAM;GACN,iBAAiB;IAAE,OAAO;IAAgB,OAAO,EAAE,eAAe,EAAE;GAAE;EACxE,CAAC;CACH,CAAC,CACH;AACF,CAAC;AAED,SAAS,wBAAwB;CAC/B,GAAG,mEAAmE;EACpE,MAAM,QAAQ,eAAe;GAC3B;IAAE,MAAM;IAAU,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM;IAAW,CAAC;GAAE;GAC9D;IACE,MAAM;IACN,OAAO,CACL;KAAE,MAAM;KAAQ,MAAM;IAAM,GAC5B;KAAE,MAAM;KAAQ,MAAM;IAAQ,CAChC;GACF;GACA;IAAE,MAAM;IAAa,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM;IAAQ,CAAC;GAAE;EAChE,CAAC;EAED,aAAO,KAAK,CAAC,CAAC,QAAQ;GACpB,MAAM,WAAW,UAAU;GAC3B,MAAM,SAAS,UAAU;GACzB,MAAM,cAAc,OAAO;EAC7B,CAAC;CACH,CAAC;CAED,GAAG,sFAAsF;EACvF,MAAM,QAAQ,eAAe,CAC3B;GACE,MAAM;GACN,OAAO;IACL;KAAE,MAAM;KAAQ,MAAM;KAAQ,OAAO;IAAO;IAC5C;KAAE,MAAM;KAAQ,WAAW;KAAa,KAAK;KAAmB,UAAU;IAAQ;IAClF;KAAE,MAAM;KAAQ,WAAW;KAAmB,KAAK;IAAoB;GACzE;EACF,CACF,CAAC;EAED,aAAO,KAAK,CAAC,CAAC,QAAQ,CACpB;GACE,MAAM;GACN,MAAM;GACN,SAAS,CACP;IAAE,MAAM;IAAc,MAAM;GAAO,GACnC;IAAE,MAAM;IAAe,QAAQ,MAAM,SAAS,mBAAmB,WAAW;GAAE,CAChF;EACF,CACF,CAAC;CACH,CAAC;CAED,GAAG,kFAAkF;EACnF,MAAM,QAAQ,eAAe,CAC3B;GACE,MAAM;GACN,OAAO,CACL;IAAE,MAAM;IAAQ,MAAM;IAAQ,OAAO;GAAO,GAC5C;IACE,MAAM;IACN,YAAY;IACZ,OAAO;IACP,OAAO,EAAE,GAAG,SAAS;IACrB,QAAQ,EAAE,MAAM,EAAE;IAClB,kBAAkB;GACpB,CACF;EACF,CACF,CAAC;EAED,aAAO,KAAK,CAAC,CAAC,QAAQ;GACpB,MAAM,cAAc,MAAM;GAC1B;IAAE,MAAM;IAAiB,SAAS;IAAM,MAAM;IAAU,WAAW;GAAiB;GACpF,MAAM,eAAe,MAAM,YAAY;EACzC,CAAC;CACH,CAAC;CAED,GAAG,6EAA6E;EAC9E,MAAM,QAAQ,eAAe,CAC3B;GACE,MAAM;GACN,OAAO,CACL;IACE,MAAM;IACN,UAAU;IACV,YAAY;IACZ,OAAO;IACP,OAAO,EAAE,IAAI,EAAE;GACjB,CACF;EACF,CACF,CAAC;EAED,aAAO,KAAK,CAAC,CAAC,QAAQ,CACpB;GAAE,MAAM;GAAiB,SAAS;GAAM,MAAM;GAAU,WAAW;EAAW,CAChF,CAAC;CACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"UIMessageStream.test.mjs","names":[],"sources":["../src/UIMessageStream.test.ts"],"sourcesContent":["import * as Image from \"@effect-uai/core/Image\"\nimport * as Items from \"@effect-uai/core/Items\"\nimport * as Turn from \"@effect-uai/core/Turn\"\nimport { describe, it } from \"@effect/vitest\"\nimport { Effect, Stream } from \"effect\"\nimport { expect } from \"vitest\"\nimport { decodeMessages } from \"./Messages.js\"\nimport { dataPart, type Emission, messageMetadata, toUIMessageStream } from \"./UIMessageStream.js\"\n\nconst turn = (items: ReadonlyArray<Items.HistoryItem>, stop: Items.StopReason): Turn.Turn => ({\n items,\n usage: {},\n stop_reason: stop,\n})\n\n// Run an InteractionEvent script through the encoder and return the decoded\n// parts (the `data:` payload of each SSE event, JSON-parsed except `[DONE]`).\nconst encode = (events: ReadonlyArray<Emission>): Effect.Effect<ReadonlyArray<unknown>> =>\n Stream.fromIterable(events).pipe(\n toUIMessageStream(\"m1\"),\n Stream.runCollect,\n Effect.map((sse) => sse.map((e) => (e.data === \"[DONE]\" ? \"[DONE]\" : JSON.parse(e.data)))),\n )\n\nconst types = (parts: ReadonlyArray<unknown>): ReadonlyArray<string> =>\n parts.map((p) => (p === \"[DONE]\" ? \"[DONE]\" : (p as { type: string }).type))\n\ndescribe(\"toUIMessageStream\", () => {\n it.effect(\"brackets text deltas with start / text-start / text-end / finish / [DONE]\", () =>\n Effect.gen(function* () {\n const parts = yield* encode([\n Turn.TurnEvent.TextDelta({ text: \"Hel\" }),\n Turn.TurnEvent.TextDelta({ text: \"lo\" }),\n Turn.TurnEvent.TurnComplete({ turn: turn([Items.assistantText(\"Hello\")], \"stop\") }),\n ])\n\n expect(types(parts)).toEqual([\n \"start\",\n \"text-start\",\n \"text-delta\",\n \"text-delta\",\n \"text-end\",\n \"finish\",\n \"[DONE]\",\n ])\n expect(parts[0]).toEqual({ type: \"start\", messageId: \"m1\" })\n // Both deltas share the one synthesized block id.\n const id = (parts[1] as { id: string }).id\n expect((parts[2] as { id: string }).id).toBe(id)\n expect((parts[4] as { id: string }).id).toBe(id)\n }),\n )\n\n it.effect(\"maps a tool round-trip to input-start/delta/available then output-available\", () =>\n Effect.gen(function* () {\n const call: Items.ToolCall = {\n type: \"function_call\",\n call_id: \"c1\",\n name: \"search\",\n arguments: `{\"q\":\"lisbon\"}`,\n }\n const parts = yield* encode([\n Turn.TurnEvent.ToolCallStart({ call_id: \"c1\", name: \"search\" }),\n Turn.TurnEvent.ToolCallArgsDelta({ call_id: \"c1\", delta: `{\"q\":\"lisbon\"}` }),\n Turn.TurnEvent.TurnComplete({ turn: turn([call], \"tool_calls\") }),\n { type: \"function_call_output\", call_id: \"c1\", output: [Items.json({ hits: 3 })] },\n ])\n\n expect(types(parts)).toEqual([\n \"start\",\n \"tool-input-start\",\n \"tool-input-delta\",\n \"tool-input-available\",\n \"tool-output-available\",\n \"finish\",\n \"[DONE]\",\n ])\n // JSON payloads are decoded, not passed through as strings.\n expect(parts[3]).toEqual({\n type: \"tool-input-available\",\n toolCallId: \"c1\",\n toolName: \"search\",\n input: { q: \"lisbon\" },\n })\n expect(parts[4]).toEqual({\n type: \"tool-output-available\",\n toolCallId: \"c1\",\n output: { hits: 3 },\n })\n }),\n )\n\n it.effect(\"maps a failed call to tool-output-error and media to a data URL\", () =>\n Effect.gen(function* () {\n const parts = yield* encode([\n {\n type: \"function_call_output\",\n call_id: \"c1\",\n output: [Items.json({ _tag: \"CityNotFound\", city: \"Atlantis\" })],\n isError: true,\n },\n {\n type: \"function_call_output\",\n call_id: \"c2\",\n output: [\n Items.text(\"Home\"),\n Items.image({ _tag: \"bytes\", bytes: new Uint8Array([1, 2, 3]), mimeType: \"image/png\" }),\n ],\n },\n ])\n expect(parts.slice(1, 3)).toEqual([\n {\n type: \"tool-output-error\",\n toolCallId: \"c1\",\n errorText: '{\"_tag\":\"CityNotFound\",\"city\":\"Atlantis\"}',\n },\n {\n type: \"tool-output-available\",\n toolCallId: \"c2\",\n output: [\"Home\", { type: \"image\", url: \"data:image/png;base64,AQID\" }],\n },\n ])\n }),\n )\n\n it.effect(\"surfaces a refusal as an error part\", () =>\n Effect.gen(function* () {\n const parts = yield* encode([Turn.TurnEvent.RefusalDelta({ text: \"cannot help\" })])\n expect(types(parts)).toEqual([\"start\", \"error\", \"finish\", \"[DONE]\"])\n expect(parts[1]).toEqual({ type: \"error\", errorText: \"cannot help\" })\n }),\n )\n\n it.effect(\"interleaves data-part and message-metadata emissions\", () =>\n Effect.gen(function* () {\n const parts = yield* encode([\n dataPart(\"metrics\", { tokps: 42 }, { transient: true }),\n Turn.TurnEvent.TextDelta({ text: \"hi\" }),\n messageMetadata({ model: \"gpt-5.4-mini\", usage: { output_tokens: 7 } }),\n Turn.TurnEvent.TurnComplete({ turn: turn([Items.assistantText(\"hi\")], \"stop\") }),\n ])\n\n expect(types(parts)).toEqual([\n \"start\",\n \"data-metrics\",\n \"text-start\",\n \"text-delta\",\n \"message-metadata\",\n \"text-end\",\n \"finish\",\n \"[DONE]\",\n ])\n expect(parts[1]).toEqual({ type: \"data-metrics\", data: { tokps: 42 }, transient: true })\n expect(parts[4]).toEqual({\n type: \"message-metadata\",\n messageMetadata: { model: \"gpt-5.4-mini\", usage: { output_tokens: 7 } },\n })\n }),\n )\n})\n\ndescribe(\"decodeMessages\", () => {\n it(\"folds UIMessage text parts back into HistoryItems by role\", () => {\n const items = decodeMessages([\n { role: \"system\", parts: [{ type: \"text\", text: \"be terse\" }] },\n {\n role: \"user\",\n parts: [\n { type: \"text\", text: \"hi \" },\n { type: \"text\", text: \"there\" },\n ],\n },\n { role: \"assistant\", parts: [{ type: \"text\", text: \"hello\" }] },\n ])\n\n expect(items).toEqual([\n Items.systemText(\"be terse\"),\n Items.userText(\"hi there\"),\n Items.assistantText(\"hello\"),\n ])\n })\n\n it(\"folds an image file part into an input_image block and drops non-image files\", () => {\n const items = decodeMessages([\n {\n role: \"user\",\n parts: [\n { type: \"text\", text: \"look\", state: \"done\" },\n { type: \"file\", mediaType: \"image/png\", url: \"https://x/i.png\", filename: \"i.png\" },\n { type: \"file\", mediaType: \"application/pdf\", url: \"https://x/doc.pdf\" },\n ],\n },\n ])\n\n expect(items).toEqual([\n {\n type: \"message\",\n role: \"user\",\n content: [\n { type: \"input_text\", text: \"look\" },\n { type: \"input_image\", source: Image.imageUrl(\"https://x/i.png\", \"image/png\") },\n ],\n },\n ])\n })\n\n it(\"expands an assistant tool call into function_call + function_call_output\", () => {\n const items = decodeMessages([\n {\n role: \"assistant\",\n parts: [\n { type: \"text\", text: \"Here\", state: \"done\" },\n {\n type: \"tool-search\",\n toolCallId: \"c1\",\n state: \"output-available\",\n input: { q: \"lisbon\" },\n output: { hits: 3 },\n providerExecuted: false,\n },\n ],\n },\n ])\n\n expect(items).toEqual([\n Items.assistantText(\"Here\"),\n { type: \"function_call\", call_id: \"c1\", name: \"search\", arguments: `{\"q\":\"lisbon\"}` },\n // The output stays a value; the provider codec serializes it.\n { type: \"function_call_output\", call_id: \"c1\", output: [Items.json({ hits: 3 })] },\n ])\n })\n\n it(\"emits only function_call for a dynamic tool with no resolved output\", () => {\n const items = decodeMessages([\n {\n role: \"assistant\",\n parts: [\n {\n type: \"dynamic-tool\",\n toolName: \"lookup\",\n toolCallId: \"c9\",\n state: \"input-available\",\n input: { id: 7 },\n },\n ],\n },\n ])\n\n expect(items).toEqual([\n { type: \"function_call\", call_id: \"c9\", name: \"lookup\", arguments: `{\"id\":7}` },\n ])\n })\n})\n"],"mappings":";;;;;;;;AASA,MAAM,QAAQ,OAAyC,UAAuC;CAC5F;CACA,OAAO,CAAC;CACR,aAAa;AACf;AAIA,MAAM,UAAU,WACd,OAAO,aAAa,MAAM,CAAC,CAAC,KAC1B,kBAAkB,IAAI,GACtB,OAAO,YACP,OAAO,KAAK,QAAQ,IAAI,KAAK,MAAO,EAAE,SAAS,WAAW,WAAW,KAAK,MAAM,EAAE,IAAI,CAAE,CAAC,CAC3F;AAEF,MAAM,SAAS,UACb,MAAM,KAAK,MAAO,MAAM,WAAW,WAAY,EAAuB,IAAK;AAE7E,SAAS,2BAA2B;CAClC,GAAG,OAAO,mFACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,OAAO;GAC1B,KAAK,UAAU,UAAU,EAAE,MAAM,MAAM,CAAC;GACxC,KAAK,UAAU,UAAU,EAAE,MAAM,KAAK,CAAC;GACvC,KAAK,UAAU,aAAa,EAAE,MAAM,KAAK,CAAC,MAAM,cAAc,OAAO,CAAC,GAAG,MAAM,EAAE,CAAC;EACpF,CAAC;EAED,aAAO,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;EACD,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GAAE,MAAM;GAAS,WAAW;EAAK,CAAC;EAE3D,MAAM,KAAM,MAAM,EAAE,CAAoB;EACxC,aAAQ,MAAM,EAAE,CAAoB,EAAE,CAAC,CAAC,KAAK,EAAE;EAC/C,aAAQ,MAAM,EAAE,CAAoB,EAAE,CAAC,CAAC,KAAK,EAAE;CACjD,CAAC,CACH;CAEA,GAAG,OAAO,qFACR,OAAO,IAAI,aAAa;EAOtB,MAAM,QAAQ,OAAO,OAAO;GAC1B,KAAK,UAAU,cAAc;IAAE,SAAS;IAAM,MAAM;GAAS,CAAC;GAC9D,KAAK,UAAU,kBAAkB;IAAE,SAAS;IAAM,OAAO;GAAiB,CAAC;GAC3E,KAAK,UAAU,aAAa,EAAE,MAAM,KAAK,CAAC;IAR1C,MAAM;IACN,SAAS;IACT,MAAM;IACN,WAAW;GAKkC,CAAC,GAAG,YAAY,EAAE,CAAC;GAChE;IAAE,MAAM;IAAwB,SAAS;IAAM,QAAQ,CAAC,MAAM,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;GAAE;EACnF,CAAC;EAED,aAAO,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;EAED,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GACvB,MAAM;GACN,YAAY;GACZ,UAAU;GACV,OAAO,EAAE,GAAG,SAAS;EACvB,CAAC;EACD,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GACvB,MAAM;GACN,YAAY;GACZ,QAAQ,EAAE,MAAM,EAAE;EACpB,CAAC;CACH,CAAC,CACH;CAEA,GAAG,OAAO,yEACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,OAAO,CAC1B;GACE,MAAM;GACN,SAAS;GACT,QAAQ,CAAC,MAAM,KAAK;IAAE,MAAM;IAAgB,MAAM;GAAW,CAAC,CAAC;GAC/D,SAAS;EACX,GACA;GACE,MAAM;GACN,SAAS;GACT,QAAQ,CACN,MAAM,KAAK,MAAM,GACjB,MAAM,MAAM;IAAE,MAAM;IAAS,OAAO,IAAI,WAAW;KAAC;KAAG;KAAG;IAAC,CAAC;IAAG,UAAU;GAAY,CAAC,CACxF;EACF,CACF,CAAC;EACD,aAAO,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAChC;GACE,MAAM;GACN,YAAY;GACZ,WAAW;EACb,GACA;GACE,MAAM;GACN,YAAY;GACZ,QAAQ,CAAC,QAAQ;IAAE,MAAM;IAAS,KAAK;GAA6B,CAAC;EACvE,CACF,CAAC;CACH,CAAC,CACH;CAEA,GAAG,OAAO,6CACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,OAAO,CAAC,KAAK,UAAU,aAAa,EAAE,MAAM,cAAc,CAAC,CAAC,CAAC;EAClF,aAAO,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ;GAAC;GAAS;GAAS;GAAU;EAAQ,CAAC;EACnE,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GAAE,MAAM;GAAS,WAAW;EAAc,CAAC;CACtE,CAAC,CACH;CAEA,GAAG,OAAO,8DACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,OAAO;GAC1B,SAAS,WAAW,EAAE,OAAO,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;GACtD,KAAK,UAAU,UAAU,EAAE,MAAM,KAAK,CAAC;GACvC,gBAAgB;IAAE,OAAO;IAAgB,OAAO,EAAE,eAAe,EAAE;GAAE,CAAC;GACtE,KAAK,UAAU,aAAa,EAAE,MAAM,KAAK,CAAC,MAAM,cAAc,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;EACjF,CAAC;EAED,aAAO,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;EACD,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GAAE,MAAM;GAAgB,MAAM,EAAE,OAAO,GAAG;GAAG,WAAW;EAAK,CAAC;EACvF,aAAO,MAAM,EAAE,CAAC,CAAC,QAAQ;GACvB,MAAM;GACN,iBAAiB;IAAE,OAAO;IAAgB,OAAO,EAAE,eAAe,EAAE;GAAE;EACxE,CAAC;CACH,CAAC,CACH;AACF,CAAC;AAED,SAAS,wBAAwB;CAC/B,GAAG,mEAAmE;EACpE,MAAM,QAAQ,eAAe;GAC3B;IAAE,MAAM;IAAU,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM;IAAW,CAAC;GAAE;GAC9D;IACE,MAAM;IACN,OAAO,CACL;KAAE,MAAM;KAAQ,MAAM;IAAM,GAC5B;KAAE,MAAM;KAAQ,MAAM;IAAQ,CAChC;GACF;GACA;IAAE,MAAM;IAAa,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM;IAAQ,CAAC;GAAE;EAChE,CAAC;EAED,aAAO,KAAK,CAAC,CAAC,QAAQ;GACpB,MAAM,WAAW,UAAU;GAC3B,MAAM,SAAS,UAAU;GACzB,MAAM,cAAc,OAAO;EAC7B,CAAC;CACH,CAAC;CAED,GAAG,sFAAsF;EACvF,MAAM,QAAQ,eAAe,CAC3B;GACE,MAAM;GACN,OAAO;IACL;KAAE,MAAM;KAAQ,MAAM;KAAQ,OAAO;IAAO;IAC5C;KAAE,MAAM;KAAQ,WAAW;KAAa,KAAK;KAAmB,UAAU;IAAQ;IAClF;KAAE,MAAM;KAAQ,WAAW;KAAmB,KAAK;IAAoB;GACzE;EACF,CACF,CAAC;EAED,aAAO,KAAK,CAAC,CAAC,QAAQ,CACpB;GACE,MAAM;GACN,MAAM;GACN,SAAS,CACP;IAAE,MAAM;IAAc,MAAM;GAAO,GACnC;IAAE,MAAM;IAAe,QAAQ,MAAM,SAAS,mBAAmB,WAAW;GAAE,CAChF;EACF,CACF,CAAC;CACH,CAAC;CAED,GAAG,kFAAkF;EACnF,MAAM,QAAQ,eAAe,CAC3B;GACE,MAAM;GACN,OAAO,CACL;IAAE,MAAM;IAAQ,MAAM;IAAQ,OAAO;GAAO,GAC5C;IACE,MAAM;IACN,YAAY;IACZ,OAAO;IACP,OAAO,EAAE,GAAG,SAAS;IACrB,QAAQ,EAAE,MAAM,EAAE;IAClB,kBAAkB;GACpB,CACF;EACF,CACF,CAAC;EAED,aAAO,KAAK,CAAC,CAAC,QAAQ;GACpB,MAAM,cAAc,MAAM;GAC1B;IAAE,MAAM;IAAiB,SAAS;IAAM,MAAM;IAAU,WAAW;GAAiB;GAEpF;IAAE,MAAM;IAAwB,SAAS;IAAM,QAAQ,CAAC,MAAM,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;GAAE;EACnF,CAAC;CACH,CAAC;CAED,GAAG,6EAA6E;EAC9E,MAAM,QAAQ,eAAe,CAC3B;GACE,MAAM;GACN,OAAO,CACL;IACE,MAAM;IACN,UAAU;IACV,YAAY;IACZ,OAAO;IACP,OAAO,EAAE,IAAI,EAAE;GACjB,CACF;EACF,CACF,CAAC;EAED,aAAO,KAAK,CAAC,CAAC,QAAQ,CACpB;GAAE,MAAM;GAAiB,SAAS;GAAM,MAAM;GAAU,WAAW;EAAW,CAChF,CAAC;CACH,CAAC;AACH,CAAC"}
@@ -1,4 +1,4 @@
1
- import { n as globalExpect, r as describe, t as it } from "./dist-Cu2gIMHV.mjs";
1
+ import { n as globalExpect, r as describe, t as it } from "./dist-DvYz71Uo.mjs";
2
2
  import { toUIMessageStream } from "./UIMessageStream.mjs";
3
3
  import * as Items from "@effect-uai/core/Items";
4
4
  import { Array as Array$1, Effect, Option, Stream } from "effect";
@@ -10583,7 +10583,11 @@ describe("AI SDK conformance (readUIMessageStream)", () => {
10583
10583
  name: "search",
10584
10584
  arguments: `{"q":"lisbon"}`
10585
10585
  }], "tool_calls") }),
10586
- Items.toolCallOutput("c1", `{"hits":3}`)
10586
+ {
10587
+ type: "function_call_output",
10588
+ call_id: "c1",
10589
+ output: [Items.json({ hits: 3 })]
10590
+ }
10587
10591
  ]);
10588
10592
  const tool = Option.flatMap(message, (m) => Array$1.findFirst(m.parts, isToolUIPart));
10589
10593
  globalExpect(Option.map(tool, (t) => t.state)).toEqual(Option.some("output-available"));