@assistant-ui/eve 0.0.3 → 0.0.5

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,7 +1,6 @@
1
1
  import { AppendMessage, RespondToToolApprovalOptions, ThreadMessage } from "@assistant-ui/core";
2
2
  import { EveMessage, EveMessageData } from "eve/react";
3
3
  import { InputResponse, SendTurnPayload } from "eve/client";
4
-
5
4
  //#region src/convertEveMessages.d.ts
6
5
  type ConvertEveMessagesOptions = {
7
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"convertEveMessages.d.ts","names":[],"sources":["../src/convertEveMessages.ts"],"mappings":";;;;;KAkCY,yBAAA;;AAAZ;;;WAKW,SAAA;EAAA,SACA,YAAA,KAAiB,OAAA,EAAS,UAAA,KAAe,IAAI;AAAA;;;;cA0K3C,iBAAA,GACX,OAAA,EAAS,UAAA,EACT,KAAA,UACA,QAAA,WAAmB,UAAA,IACnB,OAAA,GAAS,yBAAA,KACR,aAAA;AA/KqD;AA0KxD;;AA1KwD,cAsO3C,kBAAA,GACX,IAAA,EAAM,cAAA,EACN,OAAA,GAAS,yBAAA,KACR,aAAA;;;;;cASU,oBAAA,GACX,OAAA,EAAS,aAAA,KACR,WAAA,CAAY,eAAA;;;;cAqDF,kBAAA,GACX,QAAA,EAAU,4BAAA,KACT,aAID"}
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,3 +1,4 @@
1
+ import { fromThreadMessageLike } from "@assistant-ui/core";
1
2
  //#region src/convertEveMessages.ts
2
3
  const ASSISTANT_COMPLETE_STATUS = {
3
4
  type: "complete",
@@ -106,6 +107,13 @@ const convertUserPart = (part) => {
106
107
  default: return null;
107
108
  }
108
109
  };
110
+ const toUserContent = (parts) => {
111
+ const content = parts.map(convertUserPart).filter((part) => part !== null);
112
+ return content.length > 0 ? content : [{
113
+ type: "text",
114
+ text: ""
115
+ }];
116
+ };
109
117
  /**
110
118
  * Converts a single Eve message into an assistant-ui thread message.
111
119
  */
@@ -115,33 +123,20 @@ const convertEveMessage = (message, index, messages, options = {}) => {
115
123
  ...message.metadata?.optimistic && { isOptimistic: true },
116
124
  custom: { ...message.metadata ?? {} }
117
125
  };
118
- const content = message.role === "assistant" ? message.parts.map(convertAssistantPart).filter((part) => part !== null) : message.parts.map(convertUserPart).filter((part) => part !== null);
119
- const fallbackTextPart = {
120
- type: "text",
121
- text: ""
122
- };
123
- if (message.role === "user") return {
126
+ return fromThreadMessageLike(message.role === "user" ? {
127
+ role: "user",
124
128
  id: message.id,
125
129
  createdAt,
126
- role: "user",
127
- content: content.length > 0 ? content : [fallbackTextPart],
130
+ content: toUserContent(message.parts),
128
131
  attachments: [],
129
132
  metadata
130
- };
131
- return {
133
+ } : {
134
+ role: "assistant",
132
135
  id: message.id,
133
136
  createdAt,
134
- role: "assistant",
135
- content: content.length > 0 ? content : [],
136
- status: toMessageStatus(message, index, messages, options),
137
- metadata: {
138
- unstable_state: null,
139
- unstable_annotations: [],
140
- unstable_data: [],
141
- steps: [],
142
- ...metadata
143
- }
144
- };
137
+ content: message.parts.map(convertAssistantPart).filter((part) => part !== null),
138
+ metadata
139
+ }, message.id, toMessageStatus(message, index, messages, options));
145
140
  };
146
141
  /**
147
142
  * Converts the full Eve message data object into assistant-ui thread messages.
@@ -1 +1 @@
1
- {"version":3,"file":"convertEveMessages.js","names":["_exhaustiveCheck"],"sources":["../src/convertEveMessages.ts"],"sourcesContent":["import type {\n AppendMessage,\n MessageStatus,\n RespondToToolApprovalOptions,\n TextMessagePart,\n ThreadAssistantMessagePart,\n ThreadMessage,\n ThreadUserMessagePart,\n ToolApprovalOption,\n 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\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 content =\n message.role === \"assistant\"\n ? message.parts.map(convertAssistantPart).filter((part) => part !== null)\n : message.parts.map(convertUserPart).filter((part) => part !== null);\n\n const fallbackTextPart: TextMessagePart = {\n type: \"text\",\n text: \"\",\n };\n\n if (message.role === \"user\") {\n return {\n id: message.id,\n createdAt,\n role: \"user\",\n content:\n content.length > 0\n ? (content as readonly ThreadUserMessagePart[])\n : [fallbackTextPart],\n attachments: [],\n metadata,\n };\n }\n\n return {\n id: message.id,\n createdAt,\n role: \"assistant\",\n content:\n content.length > 0\n ? (content as readonly ThreadAssistantMessagePart[])\n : [],\n status: toMessageStatus(message, index, messages, options),\n metadata: {\n unstable_state: null,\n unstable_annotations: [],\n unstable_data: [],\n steps: [],\n ...metadata,\n },\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 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":";;AAqBA,MAAM,4BAA4B;CAChC,MAAM;CACN,QAAQ;AACV;AAEA,MAAM,2BAA2B,EAC/B,MAAM,UACR;AAEA,MAAM,uBAAuB;CAC3B,MAAM;CACN,QAAQ;AACV;AAWA,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAErE,MAAM,gBAAgB,UAAgD;CACpE,IAAI,SAAS,KAAK,GAAG,OAAO;CAC5B,IAAI,UAAU,KAAA,GAAW,OAAO,CAAC;CACjC,OAAO,EAAE,MAAM;AACjB;AAEA,MAAM,iBAAiB,UAA2B;CAChD,IAAI;EACF,OAAO,KAAK,UAAU,SAAS,CAAC,CAAC;CACnC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAM,mBACJ,SACA,OACA,UACA,YACkB;CAClB,IAAI,QAAQ,SAAS,aAAa,OAAO;CAOzC,IAL2B,QAAQ,MAAM,MACtC,SACC,KAAK,SAAS,kBAAkB,KAAK,UAAU,oBAG9B,GACnB,OAAO;EAAE,MAAM;EAAmB,QAAQ;CAAa;CAGzD,IAAI,QAAQ,UAAU,WAAW,UAC/B,OAAO;EAAE,MAAM;EAAc,QAAQ;CAAQ;CAG/C,IACE,QAAQ,UAAU,WAAW,eAC5B,QAAQ,cAAc,QAAQ,UAAU,SAAS,SAAS,GAE3D,OAAO;CAGT,OAAO;AACT;AAEA,MAAM,uCACJ,iBAC8C;CAC9C,MAAM,UAAU,cAAc;CAC9B,IAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,OAAO,KAAA;CAE7C,OAAO,QAAQ,KAAK,YAAY;EAC9B,IAAI,OAAO;EACX,MACE,OAAO,OAAO,YACV,eACA,OAAO,OAAO,SACZ,gBACA,IAAI,OAAO;EACnB,GAAI,OAAO,SAAS,EAAE,OAAO,OAAO,MAAM;EAC1C,GAAI,OAAO,eAAe,EAAE,aAAa,OAAO,YAAY;CAC9D,EAAE;AACJ;AAEA,MAAM,cACJ,SACgD;CAChD,IACE,KAAK,UAAU,wBACf,KAAK,UAAU,wBACf,KAAK,UAAU,sBACf,KAAK,UAAU,kBACf,KAAK,UAAU,iBAEf;CAGF,MAAM,WAAW,KAAK;CACtB,IAAI,CAAC,UAAU,OAAO,KAAA;CACtB,MAAM,UAAU,oCACd,KAAK,cAAc,KAAK,YAC1B;CAEA,OAAO;EACL,IAAI,SAAS;EACb,GAAI,SAAS,aAAa,KAAA,KAAa,EAAE,UAAU,SAAS,SAAS;EACrE,GAAI,SAAS,UAAU,EAAE,QAAQ,SAAS,OAAO;EACjD,GAAI,SAAS,gBAAgB,KAAA,KAAa,EACxC,aAAa,SAAS,YACxB;EACA,GAAI,WAAW,EAAE,QAAQ;CAC3B;AACF;AAEA,MAAM,0BACJ,SACwB;CACxB,MAAM,WAAW,WAAW,IAAI;CAChC,MAAM,WAAgC;EACpC,MAAM;EACN,YAAY,KAAK;EACjB,UAAU,KAAK;EACf,MAAM,aAAa,KAAK,KAAK;EAC7B,UAAU,cAAc,KAAK,KAAK;EAClC,GAAI,YAAY,EAAE,SAAS;CAC7B;CAEA,QAAQ,KAAK,OAAb;EACE,KAAK,oBACH,OAAO;GAAE,GAAG;GAAU,QAAQ,KAAK;EAAO;EAE5C,KAAK,gBACH,OAAO;GACL,GAAG;GACH,QAAQ,EAAE,OAAO,KAAK,UAAU;GAChC,SAAS;EACX;EAEF,KAAK,iBACH,OAAO;GACL,GAAG;GACH,QAAQ,EAAE,OAAO,KAAK,UAAU,UAAU,uBAAuB;GACjE,SAAS;EACX;EAEF,SACE,OAAO;CACX;AACF;AAEA,MAAM,wBACJ,SACsC;CACtC,QAAQ,KAAK,MAAb;EACE,KAAK;EACL,KAAK,aACH,OAAO;GAAE,MAAM,KAAK;GAAM,MAAM,KAAK;EAAK;EAE5C,KAAK,cACH,OAAO;EAET,KAAK,gBACH,OAAO,uBAAuB,IAAI;EAEpC,SACE,OAAO;CACX;AACF;AAEA,MAAM,mBACJ,SACiC;CACjC,QAAQ,KAAK,MAAb;EACE,KAAK,QACH,OAAO;GAAE,MAAM;GAAQ,MAAM,KAAK;EAAK;EAEzC,SACE,OAAO;CACX;AACF;AAEA,MAAM,iBACJ,UACqC;CACrC,MAAM,UAAU,MAAM,IAAI,eAAe,CAAC,CAAC,QAAQ,SAAS,SAAS,IAAI;CACzE,OAAO,QAAQ,SAAS,IAAI,UAAU,CAAC;EAAE,MAAM;EAAQ,MAAM;CAAG,CAAC;AACnE;;;;AAKA,MAAa,qBACX,SACA,OACA,UACA,UAAqC,CAAC,MACpB;CAClB,MAAM,YAAY,QAAQ,eAAe,OAAO,qBAAK,IAAI,KAAK;CAC9D,MAAM,WAAW;EACf,GAAI,QAAQ,UAAU,cAAc,EAAE,cAAc,KAAK;EACzD,QAAQ,EACN,GAAI,QAAQ,YAAY,CAAC,EAC3B;CACF;CAsBA,OAAO,sBAnBL,QAAQ,SAAS,SACb;EACE,MAAM;EACN,IAAI,QAAQ;EACZ;EACA,SAAS,cAAc,QAAQ,KAAK;EACpC,aAAa,CAAC;EACd;CACF,IACA;EACE,MAAM;EACN,IAAI,QAAQ;EACZ;EACA,SAAS,QAAQ,MACd,IAAI,oBAAoB,CAAC,CACzB,QAAQ,SAAS,SAAS,IAAI;EACjC;CACF,GAIJ,QAAQ,IACR,gBAAgB,SAAS,OAAO,UAAU,OAAO,CACnD;AACF;;;;AAKA,MAAa,sBACX,MACA,UAAqC,CAAC,MAEtC,KAAK,SAAS,KAAK,SAAS,OAAO,aACjC,kBAAkB,SAAS,OAAO,UAAU,OAAO,CACrD;;;;;AAMF,MAAa,wBACX,YAC4C;CAM5C,MAAM,QAAQ,CAJZ,GAAG,QAAQ,SACX,GAAI,QAAQ,aAAa,SAAS,eAAe,WAAW,OAAO,KAAK,CAAC,CAGvD,CAAC,CAAC,KAAK,SAAS;EAClC,MAAM,OAAO,KAAK;EAClB,QAAQ,MAAR;GACE,KAAK,QACH,OAAO;IAAE,MAAM;IAAiB,MAAM,KAAK;GAAK;GAElD,KAAK,QACH,OAAO;IACL,MAAM;IACN,MAAM,KAAK;IACX,WAAW,KAAK;IAChB,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;GACjD;GAEF,KAAK,SACH,OAAO;IACL,MAAM;IACN,MAAM,KAAK;IACX,WAAW;IACX,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;GACjD;GAEF,SAQE,MAAM,IAAI,MACR,6CAA6CA,MAC/C;EAEJ;CACF,CAAC;CAED,IAAI,MAAM,WAAW,KAAK,MAAM,EAAE,EAAE,SAAS,QAC3C,OAAO,MAAM,EAAE,CAAC;CAGlB,OAAO;AACT;;;;AAKA,MAAa,sBACX,cACmB;CACnB,WAAW,SAAS;CACpB,UAAU,SAAS,aAAa,SAAS,WAAW,YAAY;CAChE,GAAI,SAAS,UAAU,EAAE,MAAM,SAAS,OAAO;AACjD"}
@@ -1,6 +1,5 @@
1
1
  import { AttachmentAdapter, DictationAdapter, ExternalStoreSharedOptions, FeedbackAdapter, RealtimeVoiceAdapter, SpeechSynthesisAdapter } from "@assistant-ui/core";
2
2
  import { EveMessageData, UseEveAgentOptions } from "eve/react";
3
-
4
3
  //#region src/useEveAgentRuntime.d.ts
5
4
  type UseEveAgentRuntimeOptions = Omit<UseEveAgentOptions<EveMessageData>, "reducer"> & ExternalStoreSharedOptions & {
6
5
  readonly adapters?: {
@@ -1 +1 @@
1
- {"version":3,"file":"useEveAgentRuntime.d.ts","names":[],"sources":["../src/useEveAgentRuntime.ts"],"mappings":";;;;KA+CY,yBAAA,GAA4B,IAAA,CACtC,kBAAA,CAAmB,cAAA,gBAGnB,0BAAA;EAAA,SACW,QAAA;IAAA,SAEM,WAAA,GAAc,iBAAA;IAAA,SACd,MAAA,GAAS,sBAAA;IAAA,SACT,SAAA,GAAY,gBAAA;IAAA,SACZ,KAAA,GAAQ,oBAAA;IAAA,SACR,QAAA,GAAW,eAAA;EAAA;AAAA;;;;;;;;cAYjB,kBAAA,GAAsB,OAAA,GAAS,yBAA8B,kCAAA,gBAAA"}
1
+ {"version":3,"file":"useEveAgentRuntime.d.ts","names":[],"sources":["../src/useEveAgentRuntime.ts"],"mappings":";;;KA+CY,4BAA4B,KACtC,mBAAmB,8BAGnB;WACW;aAEM,cAAc;aACd,SAAS;aACT,YAAY;aACZ,QAAQ;aACR,WAAW;;;;;;;;;;cAYjB,qBAAsB,UAAS,2DAA8B"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { convertEveMessages, getEveMessageContent, toEveInputResponse } from "./convertEveMessages.js";
3
- import { useMemo, useRef, useState } from "react";
4
3
  import { fromThreadMessageLike, generateId, pickExternalStoreSharedOptions } from "@assistant-ui/core";
4
+ import { useMemo, useRef, useState } from "react";
5
5
  import { useExternalStoreRuntime, useRuntimeAdapters } from "@assistant-ui/core/react";
6
6
  import { useEveAgent } from "eve/react";
7
7
  //#region src/useEveAgentRuntime.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/eve",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Eve runtime adapter for assistant-ui",
5
5
  "keywords": [
6
6
  "eve",
@@ -28,11 +28,11 @@
28
28
  ],
29
29
  "sideEffects": false,
30
30
  "dependencies": {
31
- "@assistant-ui/core": "^0.2.19"
31
+ "@assistant-ui/core": "^0.2.22"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@types/react": "*",
35
- "eve": "^0.12.0",
35
+ "eve": "^0.27.6",
36
36
  "react": "^18 || ^19"
37
37
  },
38
38
  "peerDependenciesMeta": {
@@ -42,10 +42,10 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/react": "^19.2.17",
45
- "eve": "^0.13.8",
46
- "react": "^19.2.7",
47
- "vitest": "^4.1.9",
48
- "@assistant-ui/x-buildutils": "0.0.17"
45
+ "eve": "^0.27.6",
46
+ "react": "^19.2.8",
47
+ "vitest": "^4.1.10",
48
+ "@assistant-ui/x-buildutils": "0.0.19"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public",
@@ -141,6 +141,124 @@ describe("convertEveMessages", () => {
141
141
  });
142
142
  });
143
143
 
144
+ it("drops empty and whitespace-only assistant text and reasoning parts", () => {
145
+ const data = {
146
+ messages: [
147
+ {
148
+ id: "a1",
149
+ role: "assistant",
150
+ parts: [
151
+ { type: "text", text: "" },
152
+ { type: "reasoning", text: " " },
153
+ { type: "text", text: "Hi" },
154
+ ],
155
+ },
156
+ ],
157
+ } satisfies EveMessageData;
158
+
159
+ const [message] = convertEveMessages(data);
160
+
161
+ expect(message?.content).toEqual([{ type: "text", text: "Hi" }]);
162
+ });
163
+
164
+ it("preserves isOptimistic on optimistic user messages", () => {
165
+ const data = {
166
+ messages: [
167
+ {
168
+ id: "u1",
169
+ role: "user",
170
+ metadata: { optimistic: true },
171
+ parts: [{ type: "text", text: "Hello" }],
172
+ },
173
+ ],
174
+ } satisfies EveMessageData;
175
+
176
+ const [message] = convertEveMessages(data);
177
+
178
+ expect(message?.metadata.isOptimistic).toBe(true);
179
+ });
180
+
181
+ it("omits isOptimistic on confirmed user messages", () => {
182
+ const data = {
183
+ messages: [
184
+ {
185
+ id: "u1",
186
+ role: "user",
187
+ metadata: { status: "submitted" },
188
+ parts: [{ type: "text", text: "Hello" }],
189
+ },
190
+ ],
191
+ } satisfies EveMessageData;
192
+
193
+ const [message] = convertEveMessages(data);
194
+
195
+ expect(message?.metadata).not.toHaveProperty("isOptimistic");
196
+ });
197
+
198
+ it("falls back to an empty text part for user messages without convertible parts", () => {
199
+ const data = {
200
+ messages: [
201
+ {
202
+ id: "u1",
203
+ role: "user",
204
+ parts: [{ type: "file", mediaType: "image/png" }],
205
+ },
206
+ ],
207
+ } satisfies EveMessageData;
208
+
209
+ const [message] = convertEveMessages(data);
210
+
211
+ expect(message?.content).toEqual([{ type: "text", text: "" }]);
212
+ });
213
+
214
+ it("drops non-convertible user parts without triggering the fallback", () => {
215
+ const data = {
216
+ messages: [
217
+ {
218
+ id: "u1",
219
+ role: "user",
220
+ parts: [
221
+ { type: "file", mediaType: "image/png" },
222
+ { type: "text", text: "Hello" },
223
+ ],
224
+ },
225
+ ],
226
+ } satisfies EveMessageData;
227
+
228
+ const [message] = convertEveMessages(data);
229
+
230
+ expect(message?.content).toEqual([{ type: "text", text: "Hello" }]);
231
+ });
232
+
233
+ it("drops non-convertible part types instead of throwing", () => {
234
+ const data = {
235
+ messages: [
236
+ {
237
+ id: "a1",
238
+ role: "assistant",
239
+ parts: [
240
+ { type: "step-start" },
241
+ {
242
+ type: "authorization",
243
+ state: "required",
244
+ name: "github",
245
+ description: "Sign in to GitHub",
246
+ displayName: "GitHub",
247
+ stepIndex: 0,
248
+ turnId: "turn_1",
249
+ },
250
+ { type: "file", mediaType: "application/pdf" },
251
+ { type: "text", text: "Done" },
252
+ ],
253
+ },
254
+ ],
255
+ } satisfies EveMessageData;
256
+
257
+ const [message] = convertEveMessages(data);
258
+
259
+ expect(message?.content).toEqual([{ type: "text", text: "Done" }]);
260
+ });
261
+
144
262
  it("uses the supplied message creation time", () => {
145
263
  const createdAt = new Date("2026-06-17T00:00:00.000Z");
146
264
  const data = {
@@ -1,13 +1,14 @@
1
- import type {
2
- AppendMessage,
3
- MessageStatus,
4
- RespondToToolApprovalOptions,
5
- TextMessagePart,
6
- ThreadAssistantMessagePart,
7
- ThreadMessage,
8
- ThreadUserMessagePart,
9
- ToolApprovalOption,
10
- ToolCallMessagePart,
1
+ import {
2
+ fromThreadMessageLike,
3
+ type AppendMessage,
4
+ type MessageStatus,
5
+ type RespondToToolApprovalOptions,
6
+ type ThreadAssistantMessagePart,
7
+ type ThreadMessage,
8
+ type ThreadMessageLike,
9
+ type ThreadUserMessagePart,
10
+ type ToolApprovalOption,
11
+ type ToolCallMessagePart,
11
12
  } from "@assistant-ui/core";
12
13
  import type {
13
14
  EveDynamicToolPart,
@@ -205,6 +206,13 @@ const convertUserPart = (
205
206
  }
206
207
  };
207
208
 
209
+ const toUserContent = (
210
+ parts: readonly EveMessagePart[],
211
+ ): readonly ThreadUserMessagePart[] => {
212
+ const content = parts.map(convertUserPart).filter((part) => part !== null);
213
+ return content.length > 0 ? content : [{ type: "text", text: "" }];
214
+ };
215
+
208
216
  /**
209
217
  * Converts a single Eve message into an assistant-ui thread message.
210
218
  */
@@ -222,47 +230,31 @@ export const convertEveMessage = (
222
230
  },
223
231
  };
224
232
 
225
- const content =
226
- message.role === "assistant"
227
- ? message.parts.map(convertAssistantPart).filter((part) => part !== null)
228
- : message.parts.map(convertUserPart).filter((part) => part !== null);
229
-
230
- const fallbackTextPart: TextMessagePart = {
231
- type: "text",
232
- text: "",
233
- };
234
-
235
- if (message.role === "user") {
236
- return {
237
- id: message.id,
238
- createdAt,
239
- role: "user",
240
- content:
241
- content.length > 0
242
- ? (content as readonly ThreadUserMessagePart[])
243
- : [fallbackTextPart],
244
- attachments: [],
245
- metadata,
246
- };
247
- }
233
+ const like: ThreadMessageLike =
234
+ message.role === "user"
235
+ ? {
236
+ role: "user",
237
+ id: message.id,
238
+ createdAt,
239
+ content: toUserContent(message.parts),
240
+ attachments: [],
241
+ metadata,
242
+ }
243
+ : {
244
+ role: "assistant",
245
+ id: message.id,
246
+ createdAt,
247
+ content: message.parts
248
+ .map(convertAssistantPart)
249
+ .filter((part) => part !== null),
250
+ metadata,
251
+ };
248
252
 
249
- return {
250
- id: message.id,
251
- createdAt,
252
- role: "assistant",
253
- content:
254
- content.length > 0
255
- ? (content as readonly ThreadAssistantMessagePart[])
256
- : [],
257
- status: toMessageStatus(message, index, messages, options),
258
- metadata: {
259
- unstable_state: null,
260
- unstable_annotations: [],
261
- unstable_data: [],
262
- steps: [],
263
- ...metadata,
264
- },
265
- };
253
+ return fromThreadMessageLike(
254
+ like,
255
+ message.id,
256
+ toMessageStatus(message, index, messages, options),
257
+ );
266
258
  };
267
259
 
268
260
  /**