@assistant-ui/eve 0.0.5 → 0.0.7

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":"convertEveMessages.d.ts","names":[],"sources":["../src/convertEveMessages.ts"],"mappings":";;;;KAmCY;;;;;WAKD;WACA,iBAAiB,SAAS,eAAe;;;;;cAiLvC,oBACX,SAAS,YACT,eACA,mBAAmB,cACnB,UAAS,8BACR;;;;cAuCU,qBACX,MAAM,gBACN,UAAS,8BACR;;;;;cASU,uBACX,SAAS,kBACR,YAAY;;;;cAqDF,qBACX,UAAU,iCACT"}
1
+ {"version":3,"file":"convertEveMessages.d.ts","names":[],"sources":["../src/convertEveMessages.ts"],"mappings":";;;;KAoCY;;;;;WAKD;WACA,iBAAiB,SAAS,eAAe;;;;;cAiLvC,oBACX,SAAS,YACT,eACA,mBAAmB,cACnB,UAAS,8BACR;;;;cAuCU,qBACX,MAAM,gBACN,UAAS,8BACR;;;;;cASU,uBACX,SAAS,kBACR,YAAY;;;;cAoEF,qBACX,UAAU,iCACT"}
@@ -1,4 +1,5 @@
1
1
  import { fromThreadMessageLike } from "@assistant-ui/core";
2
+ import { httpUrlPattern, parseDataUrl } from "@assistant-ui/core/internal";
2
3
  //#region src/convertEveMessages.ts
3
4
  const ASSISTANT_COMPLETE_STATUS = {
4
5
  type: "complete",
@@ -147,7 +148,7 @@ const convertEveMessages = (data, options = {}) => data.messages.map((message, i
147
148
  * Eve's `send` API.
148
149
  */
149
150
  const getEveMessageContent = (message) => {
150
- const parts = [...message.content, ...message.attachments?.flatMap((attachment) => attachment.content) ?? []].map((part) => {
151
+ const parts = [...message.content, ...message.attachments?.flatMap((attachment) => attachment.content) ?? []].flatMap((part) => {
151
152
  const type = part.type;
152
153
  switch (type) {
153
154
  case "text": return {
@@ -166,6 +167,16 @@ const getEveMessageContent = (message) => {
166
167
  mediaType: "image/*",
167
168
  ...part.filename && { filename: part.filename }
168
169
  };
170
+ case "audio": {
171
+ const mediaType = `audio/${part.audio.format}`;
172
+ const data = part.audio.data;
173
+ return {
174
+ type: "file",
175
+ data: httpUrlPattern.test(data) ? data : `data:${mediaType};base64,${parseDataUrl(data)?.data ?? data}`,
176
+ mediaType
177
+ };
178
+ }
179
+ case "data": return [];
169
180
  default: throw new Error(`Unsupported eve append message part type: ${type}`);
170
181
  }
171
182
  });
@@ -1 +1 @@
1
- {"version":3,"file":"convertEveMessages.js","names":["_exhaustiveCheck"],"sources":["../src/convertEveMessages.ts"],"sourcesContent":["import {\n fromThreadMessageLike,\n type AppendMessage,\n type MessageStatus,\n type RespondToToolApprovalOptions,\n type ThreadAssistantMessagePart,\n type ThreadMessage,\n type ThreadMessageLike,\n type ThreadUserMessagePart,\n type ToolApprovalOption,\n type ToolCallMessagePart,\n} from \"@assistant-ui/core\";\nimport type {\n EveDynamicToolPart,\n EveMessage,\n EveMessageData,\n EveMessageInputRequest,\n EveMessagePart,\n} from \"eve/react\";\nimport type { InputResponse, SendTurnPayload } from \"eve/client\";\n\nconst ASSISTANT_COMPLETE_STATUS = {\n type: \"complete\",\n reason: \"stop\",\n} satisfies MessageStatus;\n\nconst ASSISTANT_RUNNING_STATUS = {\n type: \"running\",\n} satisfies MessageStatus;\n\nconst USER_FALLBACK_STATUS = {\n type: \"complete\",\n reason: \"unknown\",\n} satisfies MessageStatus;\n\nexport type ConvertEveMessagesOptions = {\n /**\n * Marks the last assistant message as running while Eve is submitting or\n * streaming.\n */\n readonly isRunning?: boolean | undefined;\n readonly getCreatedAt?: ((message: EveMessage) => Date) | undefined;\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst toJsonObject = (value: unknown): ToolCallMessagePart[\"args\"] => {\n if (isRecord(value)) return value as ToolCallMessagePart[\"args\"];\n if (value === undefined) return {};\n return { value } as ToolCallMessagePart[\"args\"];\n};\n\nconst stringifyArgs = (value: unknown): string => {\n try {\n return JSON.stringify(value ?? {});\n } catch {\n return \"\";\n }\n};\n\nconst toMessageStatus = (\n message: EveMessage,\n index: number,\n messages: readonly EveMessage[],\n options: ConvertEveMessagesOptions,\n): MessageStatus => {\n if (message.role !== \"assistant\") return USER_FALLBACK_STATUS;\n\n const hasPendingApproval = message.parts.some(\n (part) =>\n part.type === \"dynamic-tool\" && part.state === \"approval-requested\",\n );\n\n if (hasPendingApproval) {\n return { type: \"requires-action\", reason: \"tool-calls\" };\n }\n\n if (message.metadata?.status === \"failed\") {\n return { type: \"incomplete\", reason: \"error\" };\n }\n\n if (\n message.metadata?.status === \"streaming\" ||\n (options.isRunning === true && index === messages.length - 1)\n ) {\n return ASSISTANT_RUNNING_STATUS;\n }\n\n return ASSISTANT_COMPLETE_STATUS;\n};\n\nconst toolApprovalOptionsFromInputRequest = (\n inputRequest: EveMessageInputRequest | undefined,\n): readonly ToolApprovalOption[] | undefined => {\n const options = inputRequest?.options;\n if (!options || options.length === 0) return undefined;\n\n return options.map((option) => ({\n id: option.id,\n kind:\n option.id === \"approve\"\n ? \"allow-once\"\n : option.id === \"deny\"\n ? \"reject-once\"\n : `_${option.id}`,\n ...(option.label && { label: option.label }),\n ...(option.description && { description: option.description }),\n }));\n};\n\nconst toApproval = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart[\"approval\"] | undefined => {\n if (\n part.state !== \"approval-requested\" &&\n part.state !== \"approval-responded\" &&\n part.state !== \"output-available\" &&\n part.state !== \"output-error\" &&\n part.state !== \"output-denied\"\n ) {\n return undefined;\n }\n\n const approval = part.approval;\n if (!approval) return undefined;\n const options = toolApprovalOptionsFromInputRequest(\n part.toolMetadata?.eve?.inputRequest,\n );\n\n return {\n id: approval.id,\n ...(approval.approved !== undefined && { approved: approval.approved }),\n ...(approval.reason && { reason: approval.reason }),\n ...(approval.isAutomatic !== undefined && {\n isAutomatic: approval.isAutomatic,\n }),\n ...(options && { options }),\n };\n};\n\nconst convertDynamicToolPart = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart => {\n const approval = toApproval(part);\n const toolCall: ToolCallMessagePart = {\n type: \"tool-call\",\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n args: toJsonObject(part.input),\n argsText: stringifyArgs(part.input),\n ...(approval && { approval }),\n };\n\n switch (part.state) {\n case \"output-available\":\n return { ...toolCall, result: part.output };\n\n case \"output-error\":\n return {\n ...toolCall,\n result: { error: part.errorText },\n isError: true,\n };\n\n case \"output-denied\":\n return {\n ...toolCall,\n result: { error: part.approval?.reason ?? \"Tool approval denied\" },\n isError: true,\n };\n\n default:\n return toolCall;\n }\n};\n\nconst convertAssistantPart = (\n part: EveMessagePart,\n): ThreadAssistantMessagePart | null => {\n switch (part.type) {\n case \"text\":\n case \"reasoning\":\n return { type: part.type, text: part.text };\n\n case \"step-start\":\n return null;\n\n case \"dynamic-tool\":\n return convertDynamicToolPart(part);\n\n default:\n return null;\n }\n};\n\nconst convertUserPart = (\n part: EveMessagePart,\n): ThreadUserMessagePart | null => {\n switch (part.type) {\n case \"text\":\n return { type: \"text\", text: part.text };\n\n default:\n return null;\n }\n};\n\nconst toUserContent = (\n parts: readonly EveMessagePart[],\n): readonly ThreadUserMessagePart[] => {\n const content = parts.map(convertUserPart).filter((part) => part !== null);\n return content.length > 0 ? content : [{ type: \"text\", text: \"\" }];\n};\n\n/**\n * Converts a single Eve message into an assistant-ui thread message.\n */\nexport const convertEveMessage = (\n message: EveMessage,\n index: number,\n messages: readonly EveMessage[],\n options: ConvertEveMessagesOptions = {},\n): ThreadMessage => {\n const createdAt = options.getCreatedAt?.(message) ?? new Date();\n const metadata = {\n ...(message.metadata?.optimistic && { isOptimistic: true }),\n custom: {\n ...(message.metadata ?? {}),\n },\n };\n\n const like: ThreadMessageLike =\n message.role === \"user\"\n ? {\n role: \"user\",\n id: message.id,\n createdAt,\n content: toUserContent(message.parts),\n attachments: [],\n metadata,\n }\n : {\n role: \"assistant\",\n id: message.id,\n createdAt,\n content: message.parts\n .map(convertAssistantPart)\n .filter((part) => part !== null),\n metadata,\n };\n\n return fromThreadMessageLike(\n like,\n message.id,\n toMessageStatus(message, index, messages, options),\n );\n};\n\n/**\n * Converts the full Eve message data object into assistant-ui thread messages.\n */\nexport const convertEveMessages = (\n data: EveMessageData,\n options: ConvertEveMessagesOptions = {},\n): ThreadMessage[] =>\n data.messages.map((message, index, messages) =>\n convertEveMessage(message, index, messages, options),\n );\n\n/**\n * Converts an assistant-ui append message into the message payload accepted by\n * Eve's `send` API.\n */\nexport const getEveMessageContent = (\n message: AppendMessage,\n): NonNullable<SendTurnPayload[\"message\"]> => {\n const content = [\n ...message.content,\n ...(message.attachments?.flatMap((attachment) => attachment.content) ?? []),\n ];\n\n const parts = content.map((part) => {\n const type = part.type;\n switch (type) {\n case \"text\":\n return { type: \"text\" as const, text: part.text };\n\n case \"file\":\n return {\n type: \"file\" as const,\n data: part.data,\n mediaType: part.mimeType,\n ...(part.filename && { filename: part.filename }),\n };\n\n case \"image\":\n return {\n type: \"file\" as const,\n data: part.image,\n mediaType: \"image/*\",\n ...(part.filename && { filename: part.filename }),\n };\n\n default: {\n const _exhaustiveCheck:\n | \"audio\"\n | \"data\"\n | \"generative-ui\"\n | \"reasoning\"\n | \"source\"\n | \"tool-call\" = type;\n throw new Error(\n `Unsupported eve append message part type: ${_exhaustiveCheck}`,\n );\n }\n }\n });\n\n if (parts.length === 1 && parts[0]?.type === \"text\") {\n return parts[0].text;\n }\n\n return parts;\n};\n\n/**\n * Converts an assistant-ui tool approval response into an Eve input response.\n */\nexport const toEveInputResponse = (\n response: RespondToToolApprovalOptions,\n): InputResponse => ({\n requestId: response.approvalId,\n optionId: response.optionId ?? (response.approved ? \"approve\" : \"deny\"),\n ...(response.reason && { text: response.reason }),\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"convertEveMessages.js","names":["_exhaustiveCheck"],"sources":["../src/convertEveMessages.ts"],"sourcesContent":["import {\n fromThreadMessageLike,\n type AppendMessage,\n type MessageStatus,\n type RespondToToolApprovalOptions,\n type ThreadAssistantMessagePart,\n type ThreadMessage,\n type ThreadMessageLike,\n type ThreadUserMessagePart,\n type ToolApprovalOption,\n type ToolCallMessagePart,\n} from \"@assistant-ui/core\";\nimport { httpUrlPattern, parseDataUrl } from \"@assistant-ui/core/internal\";\nimport type {\n EveDynamicToolPart,\n EveMessage,\n EveMessageData,\n EveMessageInputRequest,\n EveMessagePart,\n} from \"eve/react\";\nimport type { InputResponse, SendTurnPayload } from \"eve/client\";\n\nconst ASSISTANT_COMPLETE_STATUS = {\n type: \"complete\",\n reason: \"stop\",\n} satisfies MessageStatus;\n\nconst ASSISTANT_RUNNING_STATUS = {\n type: \"running\",\n} satisfies MessageStatus;\n\nconst USER_FALLBACK_STATUS = {\n type: \"complete\",\n reason: \"unknown\",\n} satisfies MessageStatus;\n\nexport type ConvertEveMessagesOptions = {\n /**\n * Marks the last assistant message as running while Eve is submitting or\n * streaming.\n */\n readonly isRunning?: boolean | undefined;\n readonly getCreatedAt?: ((message: EveMessage) => Date) | undefined;\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst toJsonObject = (value: unknown): ToolCallMessagePart[\"args\"] => {\n if (isRecord(value)) return value as ToolCallMessagePart[\"args\"];\n if (value === undefined) return {};\n return { value } as ToolCallMessagePart[\"args\"];\n};\n\nconst stringifyArgs = (value: unknown): string => {\n try {\n return JSON.stringify(value ?? {});\n } catch {\n return \"\";\n }\n};\n\nconst toMessageStatus = (\n message: EveMessage,\n index: number,\n messages: readonly EveMessage[],\n options: ConvertEveMessagesOptions,\n): MessageStatus => {\n if (message.role !== \"assistant\") return USER_FALLBACK_STATUS;\n\n const hasPendingApproval = message.parts.some(\n (part) =>\n part.type === \"dynamic-tool\" && part.state === \"approval-requested\",\n );\n\n if (hasPendingApproval) {\n return { type: \"requires-action\", reason: \"tool-calls\" };\n }\n\n if (message.metadata?.status === \"failed\") {\n return { type: \"incomplete\", reason: \"error\" };\n }\n\n if (\n message.metadata?.status === \"streaming\" ||\n (options.isRunning === true && index === messages.length - 1)\n ) {\n return ASSISTANT_RUNNING_STATUS;\n }\n\n return ASSISTANT_COMPLETE_STATUS;\n};\n\nconst toolApprovalOptionsFromInputRequest = (\n inputRequest: EveMessageInputRequest | undefined,\n): readonly ToolApprovalOption[] | undefined => {\n const options = inputRequest?.options;\n if (!options || options.length === 0) return undefined;\n\n return options.map((option) => ({\n id: option.id,\n kind:\n option.id === \"approve\"\n ? \"allow-once\"\n : option.id === \"deny\"\n ? \"reject-once\"\n : `_${option.id}`,\n ...(option.label && { label: option.label }),\n ...(option.description && { description: option.description }),\n }));\n};\n\nconst toApproval = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart[\"approval\"] | undefined => {\n if (\n part.state !== \"approval-requested\" &&\n part.state !== \"approval-responded\" &&\n part.state !== \"output-available\" &&\n part.state !== \"output-error\" &&\n part.state !== \"output-denied\"\n ) {\n return undefined;\n }\n\n const approval = part.approval;\n if (!approval) return undefined;\n const options = toolApprovalOptionsFromInputRequest(\n part.toolMetadata?.eve?.inputRequest,\n );\n\n return {\n id: approval.id,\n ...(approval.approved !== undefined && { approved: approval.approved }),\n ...(approval.reason && { reason: approval.reason }),\n ...(approval.isAutomatic !== undefined && {\n isAutomatic: approval.isAutomatic,\n }),\n ...(options && { options }),\n };\n};\n\nconst convertDynamicToolPart = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart => {\n const approval = toApproval(part);\n const toolCall: ToolCallMessagePart = {\n type: \"tool-call\",\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n args: toJsonObject(part.input),\n argsText: stringifyArgs(part.input),\n ...(approval && { approval }),\n };\n\n switch (part.state) {\n case \"output-available\":\n return { ...toolCall, result: part.output };\n\n case \"output-error\":\n return {\n ...toolCall,\n result: { error: part.errorText },\n isError: true,\n };\n\n case \"output-denied\":\n return {\n ...toolCall,\n result: { error: part.approval?.reason ?? \"Tool approval denied\" },\n isError: true,\n };\n\n default:\n return toolCall;\n }\n};\n\nconst convertAssistantPart = (\n part: EveMessagePart,\n): ThreadAssistantMessagePart | null => {\n switch (part.type) {\n case \"text\":\n case \"reasoning\":\n return { type: part.type, text: part.text };\n\n case \"step-start\":\n return null;\n\n case \"dynamic-tool\":\n return convertDynamicToolPart(part);\n\n default:\n return null;\n }\n};\n\nconst convertUserPart = (\n part: EveMessagePart,\n): ThreadUserMessagePart | null => {\n switch (part.type) {\n case \"text\":\n return { type: \"text\", text: part.text };\n\n default:\n return null;\n }\n};\n\nconst toUserContent = (\n parts: readonly EveMessagePart[],\n): readonly ThreadUserMessagePart[] => {\n const content = parts.map(convertUserPart).filter((part) => part !== null);\n return content.length > 0 ? content : [{ type: \"text\", text: \"\" }];\n};\n\n/**\n * Converts a single Eve message into an assistant-ui thread message.\n */\nexport const convertEveMessage = (\n message: EveMessage,\n index: number,\n messages: readonly EveMessage[],\n options: ConvertEveMessagesOptions = {},\n): ThreadMessage => {\n const createdAt = options.getCreatedAt?.(message) ?? new Date();\n const metadata = {\n ...(message.metadata?.optimistic && { isOptimistic: true }),\n custom: {\n ...(message.metadata ?? {}),\n },\n };\n\n const like: ThreadMessageLike =\n message.role === \"user\"\n ? {\n role: \"user\",\n id: message.id,\n createdAt,\n content: toUserContent(message.parts),\n attachments: [],\n metadata,\n }\n : {\n role: \"assistant\",\n id: message.id,\n createdAt,\n content: message.parts\n .map(convertAssistantPart)\n .filter((part) => part !== null),\n metadata,\n };\n\n return fromThreadMessageLike(\n like,\n message.id,\n toMessageStatus(message, index, messages, options),\n );\n};\n\n/**\n * Converts the full Eve message data object into assistant-ui thread messages.\n */\nexport const convertEveMessages = (\n data: EveMessageData,\n options: ConvertEveMessagesOptions = {},\n): ThreadMessage[] =>\n data.messages.map((message, index, messages) =>\n convertEveMessage(message, index, messages, options),\n );\n\n/**\n * Converts an assistant-ui append message into the message payload accepted by\n * Eve's `send` API.\n */\nexport const getEveMessageContent = (\n message: AppendMessage,\n): NonNullable<SendTurnPayload[\"message\"]> => {\n const content = [\n ...message.content,\n ...(message.attachments?.flatMap((attachment) => attachment.content) ?? []),\n ];\n\n const parts = content.flatMap((part) => {\n const type = part.type;\n switch (type) {\n case \"text\":\n return { type: \"text\" as const, text: part.text };\n\n case \"file\":\n return {\n type: \"file\" as const,\n data: part.data,\n mediaType: part.mimeType,\n ...(part.filename && { filename: part.filename }),\n };\n\n case \"image\":\n return {\n type: \"file\" as const,\n data: part.image,\n mediaType: \"image/*\",\n ...(part.filename && { filename: part.filename }),\n };\n\n case \"audio\": {\n // A data URL's own media type wins over `mediaType` downstream, so the\n // envelope is rebuilt from the typed format rather than forwarded.\n const mediaType = `audio/${part.audio.format}`;\n const data = part.audio.data;\n return {\n type: \"file\" as const,\n data: httpUrlPattern.test(data)\n ? data\n : `data:${mediaType};base64,${parseDataUrl(data)?.data ?? data}`,\n mediaType,\n };\n }\n\n case \"data\":\n return [];\n\n default: {\n const _exhaustiveCheck:\n | \"generative-ui\"\n | \"reasoning\"\n | \"source\"\n | \"tool-call\" = type;\n throw new Error(\n `Unsupported eve append message part type: ${_exhaustiveCheck}`,\n );\n }\n }\n });\n\n if (parts.length === 1 && parts[0]?.type === \"text\") {\n return parts[0].text;\n }\n\n return parts;\n};\n\n/**\n * Converts an assistant-ui tool approval response into an Eve input response.\n */\nexport const toEveInputResponse = (\n response: RespondToToolApprovalOptions,\n): InputResponse => ({\n requestId: response.approvalId,\n optionId: response.optionId ?? (response.approved ? \"approve\" : \"deny\"),\n ...(response.reason && { text: response.reason }),\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/eve",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Eve runtime adapter for assistant-ui",
5
5
  "keywords": [
6
6
  "eve",
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "sideEffects": false,
30
30
  "dependencies": {
31
- "@assistant-ui/core": "^0.2.22"
31
+ "@assistant-ui/core": "^0.3.4"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@types/react": "*",
@@ -45,7 +45,7 @@
45
45
  "eve": "^0.27.6",
46
46
  "react": "^19.2.8",
47
47
  "vitest": "^4.1.10",
48
- "@assistant-ui/x-buildutils": "0.0.19"
48
+ "@assistant-ui/x-buildutils": "0.0.20"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public",
@@ -280,20 +280,106 @@ describe("convertEveMessages", () => {
280
280
  });
281
281
 
282
282
  describe("getEveMessageContent", () => {
283
+ const baseAppendMessage = {
284
+ role: "user",
285
+ createdAt: new Date(),
286
+ parentId: null,
287
+ sourceId: null,
288
+ runConfig: undefined,
289
+ metadata: { custom: {} },
290
+ attachments: [],
291
+ } as const;
292
+
283
293
  it("returns plain text for text-only messages", () => {
284
294
  const message = {
285
- role: "user",
286
- createdAt: new Date(),
287
- parentId: null,
288
- sourceId: null,
289
- runConfig: undefined,
295
+ ...baseAppendMessage,
290
296
  content: [{ type: "text", text: "Hello" }],
291
- metadata: { custom: {} },
292
- attachments: [],
293
297
  } satisfies AppendMessage;
294
298
 
295
299
  expect(getEveMessageContent(message)).toBe("Hello");
296
300
  });
301
+
302
+ it("converts an audio part into a file part with the format-derived media type", () => {
303
+ const message = {
304
+ ...baseAppendMessage,
305
+ content: [{ type: "audio", audio: { data: "QUJD", format: "mp3" } }],
306
+ } as unknown as AppendMessage;
307
+
308
+ expect(getEveMessageContent(message)).toEqual([
309
+ {
310
+ type: "file",
311
+ data: "data:audio/mp3;base64,QUJD",
312
+ mediaType: "audio/mp3",
313
+ },
314
+ ]);
315
+ });
316
+
317
+ it("converts a wav audio part", () => {
318
+ const message = {
319
+ ...baseAppendMessage,
320
+ content: [{ type: "audio", audio: { data: "QUJD", format: "wav" } }],
321
+ } as unknown as AppendMessage;
322
+
323
+ expect(getEveMessageContent(message)).toEqual([
324
+ {
325
+ type: "file",
326
+ data: "data:audio/wav;base64,QUJD",
327
+ mediaType: "audio/wav",
328
+ },
329
+ ]);
330
+ });
331
+
332
+ it("rebuilds the audio data URL envelope from the typed format", () => {
333
+ const message = {
334
+ ...baseAppendMessage,
335
+ content: [
336
+ {
337
+ type: "audio",
338
+ audio: { data: "data:audio/mpeg;base64,QUJD", format: "mp3" },
339
+ },
340
+ ],
341
+ } as unknown as AppendMessage;
342
+
343
+ expect(getEveMessageContent(message)).toEqual([
344
+ {
345
+ type: "file",
346
+ data: "data:audio/mp3;base64,QUJD",
347
+ mediaType: "audio/mp3",
348
+ },
349
+ ]);
350
+ });
351
+
352
+ it("forwards an http audio source instead of wrapping it in a data URL", () => {
353
+ const message = {
354
+ ...baseAppendMessage,
355
+ content: [
356
+ {
357
+ type: "audio",
358
+ audio: { data: "https://cdn.example.com/memo.mp3", format: "mp3" },
359
+ },
360
+ ],
361
+ } as unknown as AppendMessage;
362
+
363
+ expect(getEveMessageContent(message)).toEqual([
364
+ {
365
+ type: "file",
366
+ data: "https://cdn.example.com/memo.mp3",
367
+ mediaType: "audio/mp3",
368
+ },
369
+ ]);
370
+ });
371
+
372
+ it("skips data parts while keeping surrounding text", () => {
373
+ const message = {
374
+ ...baseAppendMessage,
375
+ content: [
376
+ { type: "text", text: "hi" },
377
+ { type: "data", name: "chart", data: { x: 1 } },
378
+ ],
379
+ } as unknown as AppendMessage;
380
+
381
+ expect(getEveMessageContent(message)).toBe("hi");
382
+ });
297
383
  });
298
384
 
299
385
  describe("toEveInputResponse", () => {
@@ -10,6 +10,7 @@ import {
10
10
  type ToolApprovalOption,
11
11
  type ToolCallMessagePart,
12
12
  } from "@assistant-ui/core";
13
+ import { httpUrlPattern, parseDataUrl } from "@assistant-ui/core/internal";
13
14
  import type {
14
15
  EveDynamicToolPart,
15
16
  EveMessage,
@@ -280,7 +281,7 @@ export const getEveMessageContent = (
280
281
  ...(message.attachments?.flatMap((attachment) => attachment.content) ?? []),
281
282
  ];
282
283
 
283
- const parts = content.map((part) => {
284
+ const parts = content.flatMap((part) => {
284
285
  const type = part.type;
285
286
  switch (type) {
286
287
  case "text":
@@ -302,10 +303,25 @@ export const getEveMessageContent = (
302
303
  ...(part.filename && { filename: part.filename }),
303
304
  };
304
305
 
306
+ case "audio": {
307
+ // A data URL's own media type wins over `mediaType` downstream, so the
308
+ // envelope is rebuilt from the typed format rather than forwarded.
309
+ const mediaType = `audio/${part.audio.format}`;
310
+ const data = part.audio.data;
311
+ return {
312
+ type: "file" as const,
313
+ data: httpUrlPattern.test(data)
314
+ ? data
315
+ : `data:${mediaType};base64,${parseDataUrl(data)?.data ?? data}`,
316
+ mediaType,
317
+ };
318
+ }
319
+
320
+ case "data":
321
+ return [];
322
+
305
323
  default: {
306
324
  const _exhaustiveCheck:
307
- | "audio"
308
- | "data"
309
325
  | "generative-ui"
310
326
  | "reasoning"
311
327
  | "source"