@assistant-ui/eve 0.0.22 → 0.0.23

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","sourceRoot":"","sources":["../src/convertEveMessages.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"convertEveMessages.d.ts","sourceRoot":"","sources":["../src/convertEveMessages.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { fromThreadMessageLike, toAssistantError } from "@assistant-ui/core";
2
- import { httpUrlPattern, resolveFilePartSource } from "@assistant-ui/core/internal";
2
+ import { httpUrlPattern, resolveFileMediaType, resolveFilePartSource, resolveImageMediaType, toMediaWireUrl } from "@assistant-ui/core/internal";
3
3
  //#region src/convertEveMessages.ts
4
4
  const ASSISTANT_COMPLETE_STATUS = {
5
5
  type: "complete",
@@ -133,7 +133,7 @@ const convertDynamicToolPart = (part) => {
133
133
  toolCallId: part.toolCallId,
134
134
  toolName: part.toolName,
135
135
  args: toJsonObject(part.input),
136
- argsText: stringifyArgs(part.input),
136
+ argsText: part.state === "input-streaming" ? "" : stringifyArgs(part.input),
137
137
  ...approval && { approval },
138
138
  ...providerMetadata && { providerMetadata }
139
139
  };
@@ -176,7 +176,7 @@ const convertFilePart = (part) => {
176
176
  return {
177
177
  type: "file",
178
178
  data: part.url,
179
- mimeType: part.mediaType ?? "unknown/unknown",
179
+ mimeType: resolveFileMediaType(part.url, part.mediaType),
180
180
  ...part.filename && { filename: part.filename },
181
181
  ...httpUrlPattern.test(part.url) && { sourceType: "url" }
182
182
  };
@@ -309,25 +309,34 @@ const convertEveMessages = (data, options = {}) => {
309
309
  * Eve's `send` API.
310
310
  */
311
311
  const getEveMessageContent = (message) => {
312
- const parts = [...message.content, ...message.attachments?.flatMap((attachment) => attachment.content) ?? []].flatMap((part) => {
312
+ const parts = [...message.content, ...message.attachments?.flatMap((attachment) => attachment.content.map((part) => ({
313
+ ...part,
314
+ contentType: attachment.contentType
315
+ }))) ?? []].flatMap((part) => {
313
316
  const type = part.type;
314
317
  switch (type) {
315
318
  case "text": return {
316
319
  type: "text",
317
320
  text: part.text
318
321
  };
319
- case "file": return {
320
- type: "file",
321
- data: part.data,
322
- mediaType: part.mimeType,
323
- ...part.filename && { filename: part.filename }
324
- };
325
- case "image": return {
326
- type: "file",
327
- data: part.image,
328
- mediaType: "image/*",
329
- ...part.filename && { filename: part.filename }
330
- };
322
+ case "file": {
323
+ const mediaType = resolveFileMediaType(part.data, part.mimeType || part.contentType);
324
+ return {
325
+ type: "file",
326
+ data: part.sourceType === "id" ? part.data : toMediaWireUrl(part.data, mediaType),
327
+ mediaType,
328
+ ...part.filename && { filename: part.filename }
329
+ };
330
+ }
331
+ case "image": {
332
+ const mediaType = resolveImageMediaType(part.image, part.contentType);
333
+ return {
334
+ type: "file",
335
+ data: toMediaWireUrl(part.image, mediaType),
336
+ mediaType,
337
+ ...part.filename && { filename: part.filename }
338
+ };
339
+ }
331
340
  case "audio": {
332
341
  const mediaType = `audio/${part.audio.format}`;
333
342
  const data = part.audio.data;
@@ -1 +1 @@
1
- {"version":3,"file":"convertEveMessages.js","names":["_exhaustiveCheck"],"sources":["../src/convertEveMessages.ts"],"sourcesContent":["import {\n fromThreadMessageLike,\n toAssistantError,\n type AppendMessage,\n type CompleteAttachment,\n type DataMessagePart,\n type FileMessagePart,\n type MessagePartStreamStatus,\n type MessageStatus,\n type PartProviderMetadata,\n type RespondToToolApprovalOptions,\n type ThreadAssistantMessagePart,\n type ThreadMessage,\n type ThreadMessageLike,\n type ThreadUserMessagePart,\n type ToolApprovalDisplay,\n type ToolApprovalOption,\n type ToolCallMessagePart,\n} from \"@assistant-ui/core\";\nimport {\n httpUrlPattern,\n resolveFilePartSource,\n} from \"@assistant-ui/core/internal\";\nimport type {\n EveAuthorizationOutcome,\n EveAuthorizationPart,\n EveDynamicToolPart,\n EveMessage,\n EveMessageData,\n EveMessageInputRequest,\n EveMessagePart,\n} from \"eve/react\";\nimport type { InputResponse } from \"eve/client\";\nimport type { MessageStreamEvent } 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 ASSISTANT_CANCELLED_STATUS = {\n type: \"incomplete\",\n reason: \"cancelled\",\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. When omitted, a message carrying Eve's `\"streaming\"` marker is\n * treated as running; pass `false` to settle interrupted messages to a\n * terminal status.\n */\n readonly isRunning?: boolean | undefined;\n /**\n * The Eve session error, mapped onto the assistant message it interrupted.\n */\n readonly error?: unknown;\n readonly getCreatedAt?: ((message: EveMessage) => Date) | undefined;\n /**\n * The session's stream events. A `turn.failed` or `turn.cancelled` event\n * marks its turn's assistant message as incomplete, with a failure's code\n * and message as its error.\n */\n readonly events?: readonly MessageStreamEvent[] | 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 interruptedTurns: ReadonlyMap<string, MessageStatus>,\n): MessageStatus => {\n if (message.role !== \"assistant\") return USER_FALLBACK_STATUS;\n\n const interrupted =\n message.metadata?.turnId === undefined\n ? undefined\n : interruptedTurns.get(message.metadata.turnId);\n if (interrupted) return interrupted;\n\n const isLast = index === messages.length - 1;\n const hasPendingApproval = message.parts.some(\n (part) =>\n part.type === \"dynamic-tool\" && part.state === \"approval-requested\",\n );\n // Scoped to the stale-marker case so a terminalized or errored turn keeps its\n // own status; the hold only claims the fall-through that reads as cancelled.\n const hasPendingAuthorization =\n message.metadata?.status === \"streaming\" &&\n (!isLast || options.error === undefined) &&\n message.parts.some(\n (part) => part.type === \"authorization\" && part.state === \"required\",\n );\n\n if (hasPendingApproval) {\n return { type: \"requires-action\", reason: \"tool-calls\" };\n }\n\n // A connector authorization is answered outside the thread, so the turn is\n // held on external input rather than on a tool call awaiting a result.\n if (hasPendingAuthorization) {\n return { type: \"requires-action\", reason: \"interrupt\" };\n }\n\n if (message.metadata?.status === \"failed\") {\n return { type: \"incomplete\", reason: \"error\" };\n }\n\n if (isLast && options.isRunning === true) {\n return ASSISTANT_RUNNING_STATUS;\n }\n\n // Some eve versions leave the streaming marker after cancellation or failure.\n if (message.metadata?.status === \"streaming\") {\n if (options.isRunning === undefined) {\n return ASSISTANT_RUNNING_STATUS;\n }\n if (isLast && options.error !== undefined) {\n return {\n type: \"incomplete\",\n reason: \"error\",\n error: toAssistantError(options.error),\n };\n }\n return ASSISTANT_CANCELLED_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 === \"cancel\"\n ? \"reject-once\"\n : `_${option.id}`,\n ...(option.label && { label: option.label }),\n ...(option.description && { description: option.description }),\n }));\n};\n\n/**\n * Eve resolves a request the moment any response for it arrives, so the answer\n * shapes it accepts decide both what the renderer may offer and what\n * {@link toEveInputResponse} may submit. A request with no options and no\n * `display` takes a typed answer, which is why the absence of both is not the\n * same as a confirmation.\n */\nconst isDroppedOptionApproval = (inputRequest: EveMessageInputRequest) =>\n inputRequest.kind === \"tool-approval\" && !inputRequest.options?.length;\n\nconst acceptsFreeformAnswer = (inputRequest: EveMessageInputRequest): boolean =>\n inputRequest.display !== \"confirmation\" &&\n !isDroppedOptionApproval(inputRequest) &&\n (inputRequest.display === \"text\" ||\n inputRequest.allowFreeform === true ||\n !inputRequest.options?.length);\n\nconst toolApprovalDisplay = (\n inputRequest: EveMessageInputRequest,\n): ToolApprovalDisplay => {\n // The mapper answers this shape through its approve/cancel branch whatever\n // the request declares, so the projection has to agree: presenting it as a\n // question would offer an answer the mapper turns into a decision.\n if (isDroppedOptionApproval(inputRequest)) return \"decision\";\n\n switch (inputRequest.display) {\n case \"confirmation\":\n return \"decision\";\n case \"select\":\n return \"select\";\n case \"text\":\n return \"text\";\n default:\n if (inputRequest.kind === \"tool-approval\") return \"decision\";\n return inputRequest.options?.length ? \"select\" : \"text\";\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 inputRequest = part.toolMetadata?.eve?.inputRequest;\n const options = toolApprovalOptionsFromInputRequest(inputRequest);\n\n return {\n id: approval.id,\n ...(inputRequest && {\n prompt: inputRequest.prompt,\n display: toolApprovalDisplay(inputRequest),\n ...(acceptsFreeformAnswer(inputRequest) && { allowFreeform: true }),\n }),\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 toJsonSafeInputRequest = (\n inputRequest: EveMessageInputRequest,\n): PartProviderMetadata[string] => ({\n requestId: inputRequest.requestId,\n prompt: inputRequest.prompt,\n kind: inputRequest.kind,\n ...(inputRequest.display !== undefined && { display: inputRequest.display }),\n ...(inputRequest.allowFreeform !== undefined && {\n allowFreeform: inputRequest.allowFreeform,\n }),\n ...(inputRequest.options && {\n options: inputRequest.options.map((option) => ({\n id: option.id,\n label: option.label,\n ...(option.description !== undefined && {\n description: option.description,\n }),\n ...(option.style !== undefined && { style: option.style }),\n })),\n }),\n});\n\nconst toToolProviderMetadata = (\n part: EveDynamicToolPart,\n): PartProviderMetadata | undefined => {\n const inputRequest = part.toolMetadata?.eve?.inputRequest;\n if (!inputRequest) return undefined;\n return { eve: { inputRequest: toJsonSafeInputRequest(inputRequest) } };\n};\n\nconst convertDynamicToolPart = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart => {\n const approval = toApproval(part);\n const providerMetadata = toToolProviderMetadata(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 ...(providerMetadata && { providerMetadata }),\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\n/**\n * Payload of the `authorization` data part the Eve runtime emits for a\n * connector authorization challenge. `state` discriminates a pending challenge\n * from a settled one; every other field is present only when Eve projected it.\n * This converter drops a `url` whose scheme is not `http(s)`, so a renderer can\n * link to it without checking the scheme itself; Eve itself passes through\n * whatever the connector supplied. Eve's\n * `turnId` and `stepIndex` part identity is omitted: a renderer receives the\n * one part it renders, in Eve's own step order, so display never has to\n * re-identify a challenge among several.\n */\nexport type EveAuthorizationData = {\n readonly state: EveAuthorizationPart[\"state\"];\n readonly name: string;\n readonly displayName?: string;\n readonly description?: string;\n readonly url?: string;\n readonly userCode?: string;\n readonly instructions?: string;\n readonly expiresAt?: string;\n readonly outcome?: EveAuthorizationOutcome;\n readonly reason?: string;\n};\n\nconst convertAuthorizationPart = (\n part: EveAuthorizationPart,\n): DataMessagePart<EveAuthorizationData> => ({\n type: \"data\",\n name: \"authorization\",\n data: {\n state: part.state,\n name: part.name,\n ...(part.displayName !== undefined && { displayName: part.displayName }),\n ...(part.description !== undefined && { description: part.description }),\n ...(part.authorization?.url !== undefined &&\n httpUrlPattern.test(part.authorization.url) && {\n url: part.authorization.url,\n }),\n ...(part.authorization?.userCode !== undefined && {\n userCode: part.authorization.userCode,\n }),\n ...(part.authorization?.instructions !== undefined && {\n instructions: part.authorization.instructions,\n }),\n ...(part.authorization?.expiresAt !== undefined && {\n expiresAt: part.authorization.expiresAt,\n }),\n ...(part.outcome !== undefined && { outcome: part.outcome }),\n ...(part.reason !== undefined && { reason: part.reason }),\n },\n});\n\nconst convertFilePart = (\n part: Extract<EveMessagePart, { type: \"file\" }>,\n): FileMessagePart | null => {\n if (part.url === undefined) return null;\n return {\n type: \"file\",\n data: part.url,\n mimeType: part.mediaType ?? \"unknown/unknown\",\n ...(part.filename && { filename: part.filename }),\n ...(httpUrlPattern.test(part.url) && { sourceType: \"url\" as const }),\n };\n};\n\n// Eve's `state` is a run marker: the reducer settles a part to \"done\" only on\n// `reasoning.completed`, `message.completed`, or `turn.cancelled`.\n// `emitStreamContent` does not flush leftover reasoning on a tool-call, so a\n// non-last reasoning part stays `streaming` until the model stream ends and the\n// leftover flush runs. Mapping `streaming` to running would pin that block\n// open. Only `done` is trustworthy; an unsettled part falls back to core's\n// last-part rule.\nconst partStateToStatus = (\n state: \"done\" | \"streaming\" | undefined,\n): MessagePartStreamStatus | undefined =>\n state === \"done\" ? { type: \"complete\" } : undefined;\n\nconst convertAssistantPart = (\n part: EveMessagePart,\n): ThreadAssistantMessagePart | null => {\n switch (part.type) {\n case \"text\":\n case \"reasoning\": {\n const status = partStateToStatus(part.state);\n return {\n type: part.type,\n text: part.text,\n ...(status && { status }),\n };\n }\n\n case \"step-start\":\n return null;\n\n case \"dynamic-tool\":\n return convertDynamicToolPart(part);\n\n case \"authorization\":\n return convertAuthorizationPart(part);\n\n case \"file\":\n return convertFilePart(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 case \"file\":\n return convertFilePart(part);\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\nconst toUserAttachments = (\n parts: readonly EveMessagePart[],\n): CompleteAttachment[] => {\n const attachments: CompleteAttachment[] = [];\n for (const part of parts) {\n if (part.type !== \"file\") continue;\n const file = convertFilePart(part);\n if (file === null) continue;\n const isImage = file.mimeType.startsWith(\"image/\");\n attachments.push({\n id: String(attachments.length),\n type: isImage ? \"image\" : \"file\",\n name: part.filename ?? \"file\",\n content: [\n isImage\n ? {\n type: \"image\",\n image: file.data,\n ...(part.filename && { filename: part.filename }),\n }\n : file,\n ],\n contentType: file.mimeType,\n status: { type: \"complete\" },\n });\n }\n return attachments;\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 convertEveMessageWithInterruptions(\n message,\n index,\n messages,\n options,\n getInterruptedTurns(options.events),\n );\n\nconst convertEveMessageWithInterruptions = (\n message: EveMessage,\n index: number,\n messages: readonly EveMessage[],\n options: ConvertEveMessagesOptions,\n interruptedTurns: ReadonlyMap<string, MessageStatus>,\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: toUserAttachments(message.parts),\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, interruptedTurns),\n );\n};\n\nexport type InterruptedTurnEventCache = {\n lastEvents: readonly MessageStreamEvent[];\n interruptions: readonly MessageStreamEvent[];\n};\n\nconst isTurnInterruption = (event: MessageStreamEvent) =>\n event.type === \"turn.failed\" || event.type === \"turn.cancelled\";\n\n/**\n * Collects the `turn.failed` and `turn.cancelled` events of an append-only\n * event log, scanning only the events appended since the cached call. The\n * returned array keeps its identity until a new interruption arrives.\n */\nexport const collectInterruptedTurnEvents = (\n events: readonly MessageStreamEvent[],\n cache: InterruptedTurnEventCache,\n): readonly MessageStreamEvent[] => {\n if (events === cache.lastEvents) return cache.interruptions;\n\n const scanned = cache.lastEvents;\n const resumesScan =\n scanned.length === 0 ||\n events[scanned.length - 1] === scanned[scanned.length - 1];\n\n let interruptions = resumesScan ? cache.interruptions : [];\n for (let i = resumesScan ? scanned.length : 0; i < events.length; i++) {\n const event = events[i]!;\n if (isTurnInterruption(event)) interruptions = [...interruptions, event];\n }\n\n cache.lastEvents = events;\n cache.interruptions = interruptions;\n return interruptions;\n};\n\nconst getInterruptedTurns = (\n events: readonly MessageStreamEvent[] | undefined,\n) => {\n const interrupted = new Map<string, MessageStatus>();\n for (const event of events ?? []) {\n if (event.type === \"turn.failed\") {\n interrupted.set(event.data.turnId, {\n type: \"incomplete\",\n reason: \"error\",\n error: { code: event.data.code, message: event.data.message },\n });\n } else if (event.type === \"turn.cancelled\") {\n interrupted.set(event.data.turnId, ASSISTANT_CANCELLED_STATUS);\n }\n }\n return interrupted;\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 const interruptedTurns = getInterruptedTurns(options.events);\n return data.messages.map((message, index, messages) =>\n convertEveMessageWithInterruptions(\n message,\n index,\n messages,\n options,\n interruptedTurns,\n ),\n );\n};\n\n/**\n * Structural subset of the `string | UserContent` message content Eve's `send`\n * API accepts. The helpers declare the shapes they produce and leave\n * assignability to the send call site, checked against the installed version.\n */\nexport type EveMessageContent =\n | string\n | (\n | { readonly type: \"text\"; readonly text: string }\n | {\n readonly type: \"file\";\n readonly data: string;\n readonly mediaType: string;\n readonly filename?: string;\n }\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): EveMessageContent => {\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 const source = resolveFilePartSource({ data, mimeType: mediaType });\n return {\n type: \"file\" as const,\n data:\n source.kind === \"url\"\n ? source.url\n : `data:${mediaType};base64,${source.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 * Finds the pending input request answered by the given approval id, so the\n * response mapping can honor the request's display mode.\n */\nexport const findEveInputRequest = (\n data: EveMessageData,\n approvalId: string,\n): EveMessageInputRequest | undefined => {\n for (const message of data.messages) {\n for (const part of message.parts) {\n if (part.type === \"dynamic-tool\" && part.approval?.id === approvalId) {\n return part.toolMetadata?.eve?.inputRequest;\n }\n }\n }\n return undefined;\n};\n\n/**\n * Converts an assistant-ui tool approval response into an Eve input response.\n *\n * When `inputRequest` is known, every returned response carries either an\n * option the request declares or a free-form answer. A literal option match\n * wins, then the `\"approve\"` / `\"cancel\"` option the response's boolean decision\n * names, then the response's `text` (or its `reason`, for a caller that\n * predates the first-class answer field) when the request takes a free-form\n * answer (`display: \"text\"`, `allowFreeform`, or no options at all, and never\n * `display: \"confirmation\"`) and the response is not a refusal.\n * A one-argument call, or a part with no request, still forwards a caller\n * `optionId` as it always has, including ids the request never declared.\n *\n * A response the known request cannot record as the answer it asked for throws\n * instead of being submitted, because eve resolves a request the moment any\n * response for it arrives, and an empty one is recorded as an answer with no\n * content. Not sending leaves the request pending, so the caller can retry or\n * the user can answer it as an ordinary message.\n *\n * With no request to map against — a one-argument call, or a part carrying no\n * `toolMetadata.eve.inputRequest` — the response maps as it always has, to the\n * literal `\"approve\"` / `\"cancel\"` option every eve approval declares. Guessing\n * a display mode is what this mapper stopped doing; an unknown request is not\n * a guess about one.\n */\nexport const toEveInputResponse = (\n response: RespondToToolApprovalOptions,\n inputRequest?: EveMessageInputRequest,\n): InputResponse => {\n const requestId = response.approvalId;\n const options = inputRequest?.options;\n const text = response.text ?? response.reason;\n\n if (response.optionId !== undefined) {\n if (\n !inputRequest ||\n options?.some((option) => option.id === response.optionId)\n ) {\n return {\n requestId,\n optionId: response.optionId,\n ...(text && { text }),\n };\n }\n throw new Error(\n `Eve input request \"${requestId}\" has no option with id \"${response.optionId}\"; respond with one of: ${\n options?.length\n ? options.map((option) => option.id).join(\", \")\n : \"(the request carries no options)\"\n }`,\n );\n }\n\n const decisionOptionId = response.approved ? \"approve\" : \"cancel\";\n // Eve's harness always declares `approve`/`cancel` on a `tool-approval`\n // request, so honoring the kind when the options were dropped keeps a\n // non-spec approval payload answerable rather than stuck. A tool-approval\n // that declares its own options still requires one of them.\n if (\n !inputRequest ||\n (inputRequest.kind === \"tool-approval\" && !options?.length) ||\n options?.some((option) => option.id === decisionOptionId)\n ) {\n return { requestId, optionId: decisionOptionId, ...(text && { text }) };\n }\n\n // Eve recognises an approval by its literal two-option `approve`/`cancel`\n // shape, so past this point the request is a question and the boolean\n // carries no decision eve would act on.\n const acceptsText = acceptsFreeformAnswer(inputRequest);\n if (acceptsText && text && response.approved !== false) {\n return { requestId, text };\n }\n\n throw new Error(\n `Eve input request \"${requestId}\" (${inputRequest.prompt}) was not answered by this response; ${\n options?.length\n ? `respond with one of: ${options.map((option) => option.id).join(\", \")}`\n : !acceptsText\n ? \"the request declares no options to respond with\"\n : response.approved === false\n ? \"a refusal carries no answer for a free-form request; resend with `approved` unset or true and the answer in the response text\"\n : \"pass the answer as the response text, because the request takes a free-form answer\"\n }`,\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"convertEveMessages.js","names":["_exhaustiveCheck"],"sources":["../src/convertEveMessages.ts"],"sourcesContent":["import {\n fromThreadMessageLike,\n toAssistantError,\n type AppendMessage,\n type CompleteAttachment,\n type DataMessagePart,\n type FileMessagePart,\n type MessagePartStreamStatus,\n type MessageStatus,\n type PartProviderMetadata,\n type RespondToToolApprovalOptions,\n type ThreadAssistantMessagePart,\n type ThreadMessage,\n type ThreadMessageLike,\n type ThreadUserMessagePart,\n type ToolApprovalDisplay,\n type ToolApprovalOption,\n type ToolCallMessagePart,\n} from \"@assistant-ui/core\";\nimport {\n httpUrlPattern,\n resolveFileMediaType,\n resolveFilePartSource,\n resolveImageMediaType,\n toMediaWireUrl,\n} from \"@assistant-ui/core/internal\";\nimport type {\n EveAuthorizationOutcome,\n EveAuthorizationPart,\n EveDynamicToolPart,\n EveMessage,\n EveMessageData,\n EveMessageInputRequest,\n EveMessagePart,\n} from \"eve/react\";\nimport type { InputResponse } from \"eve/client\";\nimport type { MessageStreamEvent } 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 ASSISTANT_CANCELLED_STATUS = {\n type: \"incomplete\",\n reason: \"cancelled\",\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. When omitted, a message carrying Eve's `\"streaming\"` marker is\n * treated as running; pass `false` to settle interrupted messages to a\n * terminal status.\n */\n readonly isRunning?: boolean | undefined;\n /**\n * The Eve session error, mapped onto the assistant message it interrupted.\n */\n readonly error?: unknown;\n readonly getCreatedAt?: ((message: EveMessage) => Date) | undefined;\n /**\n * The session's stream events. A `turn.failed` or `turn.cancelled` event\n * marks its turn's assistant message as incomplete, with a failure's code\n * and message as its error.\n */\n readonly events?: readonly MessageStreamEvent[] | 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 interruptedTurns: ReadonlyMap<string, MessageStatus>,\n): MessageStatus => {\n if (message.role !== \"assistant\") return USER_FALLBACK_STATUS;\n\n const interrupted =\n message.metadata?.turnId === undefined\n ? undefined\n : interruptedTurns.get(message.metadata.turnId);\n if (interrupted) return interrupted;\n\n const isLast = index === messages.length - 1;\n const hasPendingApproval = message.parts.some(\n (part) =>\n part.type === \"dynamic-tool\" && part.state === \"approval-requested\",\n );\n // Scoped to the stale-marker case so a terminalized or errored turn keeps its\n // own status; the hold only claims the fall-through that reads as cancelled.\n const hasPendingAuthorization =\n message.metadata?.status === \"streaming\" &&\n (!isLast || options.error === undefined) &&\n message.parts.some(\n (part) => part.type === \"authorization\" && part.state === \"required\",\n );\n\n if (hasPendingApproval) {\n return { type: \"requires-action\", reason: \"tool-calls\" };\n }\n\n // A connector authorization is answered outside the thread, so the turn is\n // held on external input rather than on a tool call awaiting a result.\n if (hasPendingAuthorization) {\n return { type: \"requires-action\", reason: \"interrupt\" };\n }\n\n if (message.metadata?.status === \"failed\") {\n return { type: \"incomplete\", reason: \"error\" };\n }\n\n if (isLast && options.isRunning === true) {\n return ASSISTANT_RUNNING_STATUS;\n }\n\n // Some eve versions leave the streaming marker after cancellation or failure.\n if (message.metadata?.status === \"streaming\") {\n if (options.isRunning === undefined) {\n return ASSISTANT_RUNNING_STATUS;\n }\n if (isLast && options.error !== undefined) {\n return {\n type: \"incomplete\",\n reason: \"error\",\n error: toAssistantError(options.error),\n };\n }\n return ASSISTANT_CANCELLED_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 === \"cancel\"\n ? \"reject-once\"\n : `_${option.id}`,\n ...(option.label && { label: option.label }),\n ...(option.description && { description: option.description }),\n }));\n};\n\n/**\n * Eve resolves a request the moment any response for it arrives, so the answer\n * shapes it accepts decide both what the renderer may offer and what\n * {@link toEveInputResponse} may submit. A request with no options and no\n * `display` takes a typed answer, which is why the absence of both is not the\n * same as a confirmation.\n */\nconst isDroppedOptionApproval = (inputRequest: EveMessageInputRequest) =>\n inputRequest.kind === \"tool-approval\" && !inputRequest.options?.length;\n\nconst acceptsFreeformAnswer = (inputRequest: EveMessageInputRequest): boolean =>\n inputRequest.display !== \"confirmation\" &&\n !isDroppedOptionApproval(inputRequest) &&\n (inputRequest.display === \"text\" ||\n inputRequest.allowFreeform === true ||\n !inputRequest.options?.length);\n\nconst toolApprovalDisplay = (\n inputRequest: EveMessageInputRequest,\n): ToolApprovalDisplay => {\n // The mapper answers this shape through its approve/cancel branch whatever\n // the request declares, so the projection has to agree: presenting it as a\n // question would offer an answer the mapper turns into a decision.\n if (isDroppedOptionApproval(inputRequest)) return \"decision\";\n\n switch (inputRequest.display) {\n case \"confirmation\":\n return \"decision\";\n case \"select\":\n return \"select\";\n case \"text\":\n return \"text\";\n default:\n if (inputRequest.kind === \"tool-approval\") return \"decision\";\n return inputRequest.options?.length ? \"select\" : \"text\";\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 inputRequest = part.toolMetadata?.eve?.inputRequest;\n const options = toolApprovalOptionsFromInputRequest(inputRequest);\n\n return {\n id: approval.id,\n ...(inputRequest && {\n prompt: inputRequest.prompt,\n display: toolApprovalDisplay(inputRequest),\n ...(acceptsFreeformAnswer(inputRequest) && { allowFreeform: true }),\n }),\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 toJsonSafeInputRequest = (\n inputRequest: EveMessageInputRequest,\n): PartProviderMetadata[string] => ({\n requestId: inputRequest.requestId,\n prompt: inputRequest.prompt,\n kind: inputRequest.kind,\n ...(inputRequest.display !== undefined && { display: inputRequest.display }),\n ...(inputRequest.allowFreeform !== undefined && {\n allowFreeform: inputRequest.allowFreeform,\n }),\n ...(inputRequest.options && {\n options: inputRequest.options.map((option) => ({\n id: option.id,\n label: option.label,\n ...(option.description !== undefined && {\n description: option.description,\n }),\n ...(option.style !== undefined && { style: option.style }),\n })),\n }),\n});\n\nconst toToolProviderMetadata = (\n part: EveDynamicToolPart,\n): PartProviderMetadata | undefined => {\n const inputRequest = part.toolMetadata?.eve?.inputRequest;\n if (!inputRequest) return undefined;\n return { eve: { inputRequest: toJsonSafeInputRequest(inputRequest) } };\n};\n\nconst convertDynamicToolPart = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart => {\n const approval = toApproval(part);\n const providerMetadata = toToolProviderMetadata(part);\n const toolCall: ToolCallMessagePart = {\n type: \"tool-call\",\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n args: toJsonObject(part.input),\n argsText: part.state === \"input-streaming\" ? \"\" : stringifyArgs(part.input),\n ...(approval && { approval }),\n ...(providerMetadata && { providerMetadata }),\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\n/**\n * Payload of the `authorization` data part the Eve runtime emits for a\n * connector authorization challenge. `state` discriminates a pending challenge\n * from a settled one; every other field is present only when Eve projected it.\n * This converter drops a `url` whose scheme is not `http(s)`, so a renderer can\n * link to it without checking the scheme itself; Eve itself passes through\n * whatever the connector supplied. Eve's\n * `turnId` and `stepIndex` part identity is omitted: a renderer receives the\n * one part it renders, in Eve's own step order, so display never has to\n * re-identify a challenge among several.\n */\nexport type EveAuthorizationData = {\n readonly state: EveAuthorizationPart[\"state\"];\n readonly name: string;\n readonly displayName?: string;\n readonly description?: string;\n readonly url?: string;\n readonly userCode?: string;\n readonly instructions?: string;\n readonly expiresAt?: string;\n readonly outcome?: EveAuthorizationOutcome;\n readonly reason?: string;\n};\n\nconst convertAuthorizationPart = (\n part: EveAuthorizationPart,\n): DataMessagePart<EveAuthorizationData> => ({\n type: \"data\",\n name: \"authorization\",\n data: {\n state: part.state,\n name: part.name,\n ...(part.displayName !== undefined && { displayName: part.displayName }),\n ...(part.description !== undefined && { description: part.description }),\n ...(part.authorization?.url !== undefined &&\n httpUrlPattern.test(part.authorization.url) && {\n url: part.authorization.url,\n }),\n ...(part.authorization?.userCode !== undefined && {\n userCode: part.authorization.userCode,\n }),\n ...(part.authorization?.instructions !== undefined && {\n instructions: part.authorization.instructions,\n }),\n ...(part.authorization?.expiresAt !== undefined && {\n expiresAt: part.authorization.expiresAt,\n }),\n ...(part.outcome !== undefined && { outcome: part.outcome }),\n ...(part.reason !== undefined && { reason: part.reason }),\n },\n});\n\nconst convertFilePart = (\n part: Extract<EveMessagePart, { type: \"file\" }>,\n): FileMessagePart | null => {\n if (part.url === undefined) return null;\n return {\n type: \"file\",\n data: part.url,\n mimeType: resolveFileMediaType(part.url, part.mediaType),\n ...(part.filename && { filename: part.filename }),\n ...(httpUrlPattern.test(part.url) && { sourceType: \"url\" as const }),\n };\n};\n\n// Eve's `state` is a run marker: the reducer settles a part to \"done\" only on\n// `reasoning.completed`, `message.completed`, or `turn.cancelled`.\n// `emitStreamContent` does not flush leftover reasoning on a tool-call, so a\n// non-last reasoning part stays `streaming` until the model stream ends and the\n// leftover flush runs. Mapping `streaming` to running would pin that block\n// open. Only `done` is trustworthy; an unsettled part falls back to core's\n// last-part rule.\nconst partStateToStatus = (\n state: \"done\" | \"streaming\" | undefined,\n): MessagePartStreamStatus | undefined =>\n state === \"done\" ? { type: \"complete\" } : undefined;\n\nconst convertAssistantPart = (\n part: EveMessagePart,\n): ThreadAssistantMessagePart | null => {\n switch (part.type) {\n case \"text\":\n case \"reasoning\": {\n const status = partStateToStatus(part.state);\n return {\n type: part.type,\n text: part.text,\n ...(status && { status }),\n };\n }\n\n case \"step-start\":\n return null;\n\n case \"dynamic-tool\":\n return convertDynamicToolPart(part);\n\n case \"authorization\":\n return convertAuthorizationPart(part);\n\n case \"file\":\n return convertFilePart(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 case \"file\":\n return convertFilePart(part);\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\nconst toUserAttachments = (\n parts: readonly EveMessagePart[],\n): CompleteAttachment[] => {\n const attachments: CompleteAttachment[] = [];\n for (const part of parts) {\n if (part.type !== \"file\") continue;\n const file = convertFilePart(part);\n if (file === null) continue;\n const isImage = file.mimeType.startsWith(\"image/\");\n attachments.push({\n id: String(attachments.length),\n type: isImage ? \"image\" : \"file\",\n name: part.filename ?? \"file\",\n content: [\n isImage\n ? {\n type: \"image\",\n image: file.data,\n ...(part.filename && { filename: part.filename }),\n }\n : file,\n ],\n contentType: file.mimeType,\n status: { type: \"complete\" },\n });\n }\n return attachments;\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 convertEveMessageWithInterruptions(\n message,\n index,\n messages,\n options,\n getInterruptedTurns(options.events),\n );\n\nconst convertEveMessageWithInterruptions = (\n message: EveMessage,\n index: number,\n messages: readonly EveMessage[],\n options: ConvertEveMessagesOptions,\n interruptedTurns: ReadonlyMap<string, MessageStatus>,\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: toUserAttachments(message.parts),\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, interruptedTurns),\n );\n};\n\nexport type InterruptedTurnEventCache = {\n lastEvents: readonly MessageStreamEvent[];\n interruptions: readonly MessageStreamEvent[];\n};\n\nconst isTurnInterruption = (event: MessageStreamEvent) =>\n event.type === \"turn.failed\" || event.type === \"turn.cancelled\";\n\n/**\n * Collects the `turn.failed` and `turn.cancelled` events of an append-only\n * event log, scanning only the events appended since the cached call. The\n * returned array keeps its identity until a new interruption arrives.\n */\nexport const collectInterruptedTurnEvents = (\n events: readonly MessageStreamEvent[],\n cache: InterruptedTurnEventCache,\n): readonly MessageStreamEvent[] => {\n if (events === cache.lastEvents) return cache.interruptions;\n\n const scanned = cache.lastEvents;\n const resumesScan =\n scanned.length === 0 ||\n events[scanned.length - 1] === scanned[scanned.length - 1];\n\n let interruptions = resumesScan ? cache.interruptions : [];\n for (let i = resumesScan ? scanned.length : 0; i < events.length; i++) {\n const event = events[i]!;\n if (isTurnInterruption(event)) interruptions = [...interruptions, event];\n }\n\n cache.lastEvents = events;\n cache.interruptions = interruptions;\n return interruptions;\n};\n\nconst getInterruptedTurns = (\n events: readonly MessageStreamEvent[] | undefined,\n) => {\n const interrupted = new Map<string, MessageStatus>();\n for (const event of events ?? []) {\n if (event.type === \"turn.failed\") {\n interrupted.set(event.data.turnId, {\n type: \"incomplete\",\n reason: \"error\",\n error: { code: event.data.code, message: event.data.message },\n });\n } else if (event.type === \"turn.cancelled\") {\n interrupted.set(event.data.turnId, ASSISTANT_CANCELLED_STATUS);\n }\n }\n return interrupted;\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 const interruptedTurns = getInterruptedTurns(options.events);\n return data.messages.map((message, index, messages) =>\n convertEveMessageWithInterruptions(\n message,\n index,\n messages,\n options,\n interruptedTurns,\n ),\n );\n};\n\n/**\n * Structural subset of the `string | UserContent` message content Eve's `send`\n * API accepts. The helpers declare the shapes they produce and leave\n * assignability to the send call site, checked against the installed version.\n */\nexport type EveMessageContent =\n | string\n | (\n | { readonly type: \"text\"; readonly text: string }\n | {\n readonly type: \"file\";\n readonly data: string;\n readonly mediaType: string;\n readonly filename?: string;\n }\n )[];\n\ntype OutboundPart = AppendMessage[\"content\"][number] & {\n readonly contentType?: string | undefined;\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): EveMessageContent => {\n const content: OutboundPart[] = [\n ...message.content,\n ...(message.attachments?.flatMap((attachment) =>\n attachment.content.map((part) => ({\n ...part,\n contentType: attachment.contentType,\n })),\n ) ?? []),\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 const mediaType = resolveFileMediaType(\n part.data,\n part.mimeType || part.contentType,\n );\n return {\n type: \"file\" as const,\n data:\n part.sourceType === \"id\"\n ? part.data\n : toMediaWireUrl(part.data, mediaType),\n mediaType,\n ...(part.filename && { filename: part.filename }),\n };\n }\n\n case \"image\": {\n const mediaType = resolveImageMediaType(part.image, part.contentType);\n return {\n type: \"file\" as const,\n data: toMediaWireUrl(part.image, mediaType),\n mediaType,\n ...(part.filename && { filename: part.filename }),\n };\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 const source = resolveFilePartSource({ data, mimeType: mediaType });\n return {\n type: \"file\" as const,\n data:\n source.kind === \"url\"\n ? source.url\n : `data:${mediaType};base64,${source.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 * Finds the pending input request answered by the given approval id, so the\n * response mapping can honor the request's display mode.\n */\nexport const findEveInputRequest = (\n data: EveMessageData,\n approvalId: string,\n): EveMessageInputRequest | undefined => {\n for (const message of data.messages) {\n for (const part of message.parts) {\n if (part.type === \"dynamic-tool\" && part.approval?.id === approvalId) {\n return part.toolMetadata?.eve?.inputRequest;\n }\n }\n }\n return undefined;\n};\n\n/**\n * Converts an assistant-ui tool approval response into an Eve input response.\n *\n * When `inputRequest` is known, every returned response carries either an\n * option the request declares or a free-form answer. A literal option match\n * wins, then the `\"approve\"` / `\"cancel\"` option the response's boolean decision\n * names, then the response's `text` (or its `reason`, for a caller that\n * predates the first-class answer field) when the request takes a free-form\n * answer (`display: \"text\"`, `allowFreeform`, or no options at all, and never\n * `display: \"confirmation\"`) and the response is not a refusal.\n * A one-argument call, or a part with no request, still forwards a caller\n * `optionId` as it always has, including ids the request never declared.\n *\n * A response the known request cannot record as the answer it asked for throws\n * instead of being submitted, because eve resolves a request the moment any\n * response for it arrives, and an empty one is recorded as an answer with no\n * content. Not sending leaves the request pending, so the caller can retry or\n * the user can answer it as an ordinary message.\n *\n * With no request to map against — a one-argument call, or a part carrying no\n * `toolMetadata.eve.inputRequest` — the response maps as it always has, to the\n * literal `\"approve\"` / `\"cancel\"` option every eve approval declares. Guessing\n * a display mode is what this mapper stopped doing; an unknown request is not\n * a guess about one.\n */\nexport const toEveInputResponse = (\n response: RespondToToolApprovalOptions,\n inputRequest?: EveMessageInputRequest,\n): InputResponse => {\n const requestId = response.approvalId;\n const options = inputRequest?.options;\n const text = response.text ?? response.reason;\n\n if (response.optionId !== undefined) {\n if (\n !inputRequest ||\n options?.some((option) => option.id === response.optionId)\n ) {\n return {\n requestId,\n optionId: response.optionId,\n ...(text && { text }),\n };\n }\n throw new Error(\n `Eve input request \"${requestId}\" has no option with id \"${response.optionId}\"; respond with one of: ${\n options?.length\n ? options.map((option) => option.id).join(\", \")\n : \"(the request carries no options)\"\n }`,\n );\n }\n\n const decisionOptionId = response.approved ? \"approve\" : \"cancel\";\n // Eve's harness always declares `approve`/`cancel` on a `tool-approval`\n // request, so honoring the kind when the options were dropped keeps a\n // non-spec approval payload answerable rather than stuck. A tool-approval\n // that declares its own options still requires one of them.\n if (\n !inputRequest ||\n (inputRequest.kind === \"tool-approval\" && !options?.length) ||\n options?.some((option) => option.id === decisionOptionId)\n ) {\n return { requestId, optionId: decisionOptionId, ...(text && { text }) };\n }\n\n // Eve recognises an approval by its literal two-option `approve`/`cancel`\n // shape, so past this point the request is a question and the boolean\n // carries no decision eve would act on.\n const acceptsText = acceptsFreeformAnswer(inputRequest);\n if (acceptsText && text && response.approved !== false) {\n return { requestId, text };\n }\n\n throw new Error(\n `Eve input request \"${requestId}\" (${inputRequest.prompt}) was not answered by this response; ${\n options?.length\n ? `respond with one of: ${options.map((option) => option.id).join(\", \")}`\n : !acceptsText\n ? \"the request declares no options to respond with\"\n : response.approved === false\n ? \"a refusal carries no answer for a free-form request; resend with `approved` unset or true and the answer in the response text\"\n : \"pass the answer as the response text, because the request takes a free-form answer\"\n }`,\n );\n};\n"],"mappings":";;;AAsCA,MAAM,4BAA4B;CAChC,MAAM;CACN,QAAQ;AACV;AAEA,MAAM,2BAA2B,EAC/B,MAAM,UACR;AAEA,MAAM,6BAA6B;CACjC,MAAM;CACN,QAAQ;AACV;AAEA,MAAM,uBAAuB;CAC3B,MAAM;CACN,QAAQ;AACV;AAuBA,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,SACA,qBACkB;CAClB,IAAI,QAAQ,SAAS,aAAa,OAAO;CAEzC,MAAM,cACJ,QAAQ,UAAU,WAAW,KAAA,IACzB,KAAA,IACA,iBAAiB,IAAI,QAAQ,SAAS,MAAM;CAClD,IAAI,aAAa,OAAO;CAExB,MAAM,SAAS,UAAU,SAAS,SAAS;CAC3C,MAAM,qBAAqB,QAAQ,MAAM,MACtC,SACC,KAAK,SAAS,kBAAkB,KAAK,UAAU,oBACnD;CAGA,MAAM,0BACJ,QAAQ,UAAU,WAAW,gBAC5B,CAAC,UAAU,QAAQ,UAAU,KAAA,MAC9B,QAAQ,MAAM,MACX,SAAS,KAAK,SAAS,mBAAmB,KAAK,UAAU,UAC5D;CAEF,IAAI,oBACF,OAAO;EAAE,MAAM;EAAmB,QAAQ;CAAa;CAKzD,IAAI,yBACF,OAAO;EAAE,MAAM;EAAmB,QAAQ;CAAY;CAGxD,IAAI,QAAQ,UAAU,WAAW,UAC/B,OAAO;EAAE,MAAM;EAAc,QAAQ;CAAQ;CAG/C,IAAI,UAAU,QAAQ,cAAc,MAClC,OAAO;CAIT,IAAI,QAAQ,UAAU,WAAW,aAAa;EAC5C,IAAI,QAAQ,cAAc,KAAA,GACxB,OAAO;EAET,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;GACL,MAAM;GACN,QAAQ;GACR,OAAO,iBAAiB,QAAQ,KAAK;EACvC;EAEF,OAAO;CACT;CAEA,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,WACZ,gBACA,IAAI,OAAO;EACnB,GAAI,OAAO,SAAS,EAAE,OAAO,OAAO,MAAM;EAC1C,GAAI,OAAO,eAAe,EAAE,aAAa,OAAO,YAAY;CAC9D,EAAE;AACJ;;;;;;;;AASA,MAAM,2BAA2B,iBAC/B,aAAa,SAAS,mBAAmB,CAAC,aAAa,SAAS;AAElE,MAAM,yBAAyB,iBAC7B,aAAa,YAAY,kBACzB,CAAC,wBAAwB,YAAY,MACpC,aAAa,YAAY,UACxB,aAAa,kBAAkB,QAC/B,CAAC,aAAa,SAAS;AAE3B,MAAM,uBACJ,iBACwB;CAIxB,IAAI,wBAAwB,YAAY,GAAG,OAAO;CAElD,QAAQ,aAAa,SAArB;EACE,KAAK,gBACH,OAAO;EACT,KAAK,UACH,OAAO;EACT,KAAK,QACH,OAAO;EACT;GACE,IAAI,aAAa,SAAS,iBAAiB,OAAO;GAClD,OAAO,aAAa,SAAS,SAAS,WAAW;CACrD;AACF;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,eAAe,KAAK,cAAc,KAAK;CAC7C,MAAM,UAAU,oCAAoC,YAAY;CAEhE,OAAO;EACL,IAAI,SAAS;EACb,GAAI,gBAAgB;GAClB,QAAQ,aAAa;GACrB,SAAS,oBAAoB,YAAY;GACzC,GAAI,sBAAsB,YAAY,KAAK,EAAE,eAAe,KAAK;EACnE;EACA,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,kBACkC;CAClC,WAAW,aAAa;CACxB,QAAQ,aAAa;CACrB,MAAM,aAAa;CACnB,GAAI,aAAa,YAAY,KAAA,KAAa,EAAE,SAAS,aAAa,QAAQ;CAC1E,GAAI,aAAa,kBAAkB,KAAA,KAAa,EAC9C,eAAe,aAAa,cAC9B;CACA,GAAI,aAAa,WAAW,EAC1B,SAAS,aAAa,QAAQ,KAAK,YAAY;EAC7C,IAAI,OAAO;EACX,OAAO,OAAO;EACd,GAAI,OAAO,gBAAgB,KAAA,KAAa,EACtC,aAAa,OAAO,YACtB;EACA,GAAI,OAAO,UAAU,KAAA,KAAa,EAAE,OAAO,OAAO,MAAM;CAC1D,EAAE,EACJ;AACF;AAEA,MAAM,0BACJ,SACqC;CACrC,MAAM,eAAe,KAAK,cAAc,KAAK;CAC7C,IAAI,CAAC,cAAc,OAAO,KAAA;CAC1B,OAAO,EAAE,KAAK,EAAE,cAAc,uBAAuB,YAAY,EAAE,EAAE;AACvE;AAEA,MAAM,0BACJ,SACwB;CACxB,MAAM,WAAW,WAAW,IAAI;CAChC,MAAM,mBAAmB,uBAAuB,IAAI;CACpD,MAAM,WAAgC;EACpC,MAAM;EACN,YAAY,KAAK;EACjB,UAAU,KAAK;EACf,MAAM,aAAa,KAAK,KAAK;EAC7B,UAAU,KAAK,UAAU,oBAAoB,KAAK,cAAc,KAAK,KAAK;EAC1E,GAAI,YAAY,EAAE,SAAS;EAC3B,GAAI,oBAAoB,EAAE,iBAAiB;CAC7C;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;AA0BA,MAAM,4BACJ,UAC2C;CAC3C,MAAM;CACN,MAAM;CACN,MAAM;EACJ,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,GAAI,KAAK,gBAAgB,KAAA,KAAa,EAAE,aAAa,KAAK,YAAY;EACtE,GAAI,KAAK,gBAAgB,KAAA,KAAa,EAAE,aAAa,KAAK,YAAY;EACtE,GAAI,KAAK,eAAe,QAAQ,KAAA,KAC9B,eAAe,KAAK,KAAK,cAAc,GAAG,KAAK,EAC7C,KAAK,KAAK,cAAc,IAC1B;EACF,GAAI,KAAK,eAAe,aAAa,KAAA,KAAa,EAChD,UAAU,KAAK,cAAc,SAC/B;EACA,GAAI,KAAK,eAAe,iBAAiB,KAAA,KAAa,EACpD,cAAc,KAAK,cAAc,aACnC;EACA,GAAI,KAAK,eAAe,cAAc,KAAA,KAAa,EACjD,WAAW,KAAK,cAAc,UAChC;EACA,GAAI,KAAK,YAAY,KAAA,KAAa,EAAE,SAAS,KAAK,QAAQ;EAC1D,GAAI,KAAK,WAAW,KAAA,KAAa,EAAE,QAAQ,KAAK,OAAO;CACzD;AACF;AAEA,MAAM,mBACJ,SAC2B;CAC3B,IAAI,KAAK,QAAQ,KAAA,GAAW,OAAO;CACnC,OAAO;EACL,MAAM;EACN,MAAM,KAAK;EACX,UAAU,qBAAqB,KAAK,KAAK,KAAK,SAAS;EACvD,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;EAC/C,GAAI,eAAe,KAAK,KAAK,GAAG,KAAK,EAAE,YAAY,MAAe;CACpE;AACF;AASA,MAAM,qBACJ,UAEA,UAAU,SAAS,EAAE,MAAM,WAAW,IAAI,KAAA;AAE5C,MAAM,wBACJ,SACsC;CACtC,QAAQ,KAAK,MAAb;EACE,KAAK;EACL,KAAK,aAAa;GAChB,MAAM,SAAS,kBAAkB,KAAK,KAAK;GAC3C,OAAO;IACL,MAAM,KAAK;IACX,MAAM,KAAK;IACX,GAAI,UAAU,EAAE,OAAO;GACzB;EACF;EAEA,KAAK,cACH,OAAO;EAET,KAAK,gBACH,OAAO,uBAAuB,IAAI;EAEpC,KAAK,iBACH,OAAO,yBAAyB,IAAI;EAEtC,KAAK,QACH,OAAO,gBAAgB,IAAI;EAE7B,SACE,OAAO;CACX;AACF;AAEA,MAAM,mBACJ,SACiC;CACjC,QAAQ,KAAK,MAAb;EACE,KAAK,QACH,OAAO;GAAE,MAAM;GAAQ,MAAM,KAAK;EAAK;EAEzC,KAAK,QACH,OAAO,gBAAgB,IAAI;EAE7B,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;AAEA,MAAM,qBACJ,UACyB;CACzB,MAAM,cAAoC,CAAC;CAC3C,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,SAAS,QAAQ;EAC1B,MAAM,OAAO,gBAAgB,IAAI;EACjC,IAAI,SAAS,MAAM;EACnB,MAAM,UAAU,KAAK,SAAS,WAAW,QAAQ;EACjD,YAAY,KAAK;GACf,IAAI,OAAO,YAAY,MAAM;GAC7B,MAAM,UAAU,UAAU;GAC1B,MAAM,KAAK,YAAY;GACvB,SAAS,CACP,UACI;IACE,MAAM;IACN,OAAO,KAAK;IACZ,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;GACjD,IACA,IACN;GACA,aAAa,KAAK;GAClB,QAAQ,EAAE,MAAM,WAAW;EAC7B,CAAC;CACH;CACA,OAAO;AACT;;;;AAKA,MAAa,qBACX,SACA,OACA,UACA,UAAqC,CAAC,MAEtC,mCACE,SACA,OACA,UACA,SACA,oBAAoB,QAAQ,MAAM,CACpC;AAEF,MAAM,sCACJ,SACA,OACA,UACA,SACA,qBACkB;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;CAEA,MAAM,OACJ,QAAQ,SAAS,SACb;EACE,MAAM;EACN,IAAI,QAAQ;EACZ;EACA,SAAS,cAAc,QAAQ,KAAK;EACpC,aAAa,kBAAkB,QAAQ,KAAK;EAC5C;CACF,IACA;EACE,MAAM;EACN,IAAI,QAAQ;EACZ;EACA,SAAS,QAAQ,MACd,IAAI,oBAAoB,CAAC,CACzB,QAAQ,SAAS,SAAS,IAAI;EACjC;CACF;CAEN,OAAO,sBACL,MACA,QAAQ,IACR,gBAAgB,SAAS,OAAO,UAAU,SAAS,gBAAgB,CACrE;AACF;AAOA,MAAM,sBAAsB,UAC1B,MAAM,SAAS,iBAAiB,MAAM,SAAS;;;;;;AAOjD,MAAa,gCACX,QACA,UACkC;CAClC,IAAI,WAAW,MAAM,YAAY,OAAO,MAAM;CAE9C,MAAM,UAAU,MAAM;CACtB,MAAM,cACJ,QAAQ,WAAW,KACnB,OAAO,QAAQ,SAAS,OAAO,QAAQ,QAAQ,SAAS;CAE1D,IAAI,gBAAgB,cAAc,MAAM,gBAAgB,CAAC;CACzD,KAAK,IAAI,IAAI,cAAc,QAAQ,SAAS,GAAG,IAAI,OAAO,QAAQ,KAAK;EACrE,MAAM,QAAQ,OAAO;EACrB,IAAI,mBAAmB,KAAK,GAAG,gBAAgB,CAAC,GAAG,eAAe,KAAK;CACzE;CAEA,MAAM,aAAa;CACnB,MAAM,gBAAgB;CACtB,OAAO;AACT;AAEA,MAAM,uBACJ,WACG;CACH,MAAM,8BAAc,IAAI,IAA2B;CACnD,KAAK,MAAM,SAAS,UAAU,CAAC,GAC7B,IAAI,MAAM,SAAS,eACjB,YAAY,IAAI,MAAM,KAAK,QAAQ;EACjC,MAAM;EACN,QAAQ;EACR,OAAO;GAAE,MAAM,MAAM,KAAK;GAAM,SAAS,MAAM,KAAK;EAAQ;CAC9D,CAAC;MACI,IAAI,MAAM,SAAS,kBACxB,YAAY,IAAI,MAAM,KAAK,QAAQ,0BAA0B;CAGjE,OAAO;AACT;;;;AAKA,MAAa,sBACX,MACA,UAAqC,CAAC,MAClB;CACpB,MAAM,mBAAmB,oBAAoB,QAAQ,MAAM;CAC3D,OAAO,KAAK,SAAS,KAAK,SAAS,OAAO,aACxC,mCACE,SACA,OACA,UACA,SACA,gBACF,CACF;AACF;;;;;AA2BA,MAAa,wBACX,YACsB;CAWtB,MAAM,QAAQ,CATZ,GAAG,QAAQ,SACX,GAAI,QAAQ,aAAa,SAAS,eAChC,WAAW,QAAQ,KAAK,UAAU;EAChC,GAAG;EACH,aAAa,WAAW;CAC1B,EAAE,CACJ,KAAK,CAAC,CAGY,CAAC,CAAC,SAAS,SAAS;EACtC,MAAM,OAAO,KAAK;EAClB,QAAQ,MAAR;GACE,KAAK,QACH,OAAO;IAAE,MAAM;IAAiB,MAAM,KAAK;GAAK;GAElD,KAAK,QAAQ;IACX,MAAM,YAAY,qBAChB,KAAK,MACL,KAAK,YAAY,KAAK,WACxB;IACA,OAAO;KACL,MAAM;KACN,MACE,KAAK,eAAe,OAChB,KAAK,OACL,eAAe,KAAK,MAAM,SAAS;KACzC;KACA,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;IACjD;GACF;GAEA,KAAK,SAAS;IACZ,MAAM,YAAY,sBAAsB,KAAK,OAAO,KAAK,WAAW;IACpE,OAAO;KACL,MAAM;KACN,MAAM,eAAe,KAAK,OAAO,SAAS;KAC1C;KACA,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;IACjD;GACF;GAEA,KAAK,SAAS;IAGZ,MAAM,YAAY,SAAS,KAAK,MAAM;IACtC,MAAM,OAAO,KAAK,MAAM;IACxB,MAAM,SAAS,sBAAsB;KAAE;KAAM,UAAU;IAAU,CAAC;IAClE,OAAO;KACL,MAAM;KACN,MACE,OAAO,SAAS,QACZ,OAAO,MACP,QAAQ,UAAU,UAAU,OAAO;KACzC;IACF;GACF;GAEA,KAAK,QACH,OAAO,CAAC;GAEV,SAME,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;;;;;AAMA,MAAa,uBACX,MACA,eACuC;CACvC,KAAK,MAAM,WAAW,KAAK,UACzB,KAAK,MAAM,QAAQ,QAAQ,OACzB,IAAI,KAAK,SAAS,kBAAkB,KAAK,UAAU,OAAO,YACxD,OAAO,KAAK,cAAc,KAAK;AAKvC;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,sBACX,UACA,iBACkB;CAClB,MAAM,YAAY,SAAS;CAC3B,MAAM,UAAU,cAAc;CAC9B,MAAM,OAAO,SAAS,QAAQ,SAAS;CAEvC,IAAI,SAAS,aAAa,KAAA,GAAW;EACnC,IACE,CAAC,gBACD,SAAS,MAAM,WAAW,OAAO,OAAO,SAAS,QAAQ,GAEzD,OAAO;GACL;GACA,UAAU,SAAS;GACnB,GAAI,QAAQ,EAAE,KAAK;EACrB;EAEF,MAAM,IAAI,MACR,sBAAsB,UAAU,2BAA2B,SAAS,SAAS,0BAC3E,SAAS,SACL,QAAQ,KAAK,WAAW,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI,IAC5C,oCAER;CACF;CAEA,MAAM,mBAAmB,SAAS,WAAW,YAAY;CAKzD,IACE,CAAC,gBACA,aAAa,SAAS,mBAAmB,CAAC,SAAS,UACpD,SAAS,MAAM,WAAW,OAAO,OAAO,gBAAgB,GAExD,OAAO;EAAE;EAAW,UAAU;EAAkB,GAAI,QAAQ,EAAE,KAAK;CAAG;CAMxE,MAAM,cAAc,sBAAsB,YAAY;CACtD,IAAI,eAAe,QAAQ,SAAS,aAAa,OAC/C,OAAO;EAAE;EAAW;CAAK;CAG3B,MAAM,IAAI,MACR,sBAAsB,UAAU,KAAK,aAAa,OAAO,uCACvD,SAAS,SACL,wBAAwB,QAAQ,KAAK,WAAW,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI,MACpE,CAAC,cACC,oDACA,SAAS,aAAa,QACpB,kIACA,sFAEZ;AACF"}
@@ -1,7 +1,7 @@
1
1
  //#region src/sdkIdentity.ts
2
2
  const EVE_SDK = {
3
3
  name: "@assistant-ui/eve",
4
- version: "0.0.22"
4
+ version: "0.0.23"
5
5
  };
6
6
  //#endregion
7
7
  export { EVE_SDK };
@@ -1 +1 @@
1
- {"version":3,"file":"useEveAgentRuntime.d.ts","sourceRoot":"","sources":["../src/useEveAgentRuntime.ts"],"names":[],"mappings":"AAUA,OAAO,EAML,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAG5B,MAAM,oBAAoB,CAAC;AAS5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAExB,MAAM,WAAW,CAAC;AAoGnB,KAAK,eAAe,GAAG,IAAI,CAAC,kBAAkB,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,CAAC;AAE3E,MAAM,MAAM,yBAAyB,GAAG,eAAe,GACrD,0BAA0B,GAAG;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EACd;QACE,QAAQ,CAAC,WAAW,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;QACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;QACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;QAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;QAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;KACjD,GACD,SAAS,CAAC;IACd;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CAC7C,CAAC;AA6fJ;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAS,yBAA8B,kDAazE,CAAC"}
1
+ {"version":3,"file":"useEveAgentRuntime.d.ts","sourceRoot":"","sources":["../src/useEveAgentRuntime.ts"],"names":[],"mappings":"AAUA,OAAO,EAML,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAG5B,MAAM,oBAAoB,CAAC;AAU5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAExB,MAAM,WAAW,CAAC;AAuEnB,KAAK,eAAe,GAAG,IAAI,CAAC,kBAAkB,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,CAAC;AAE3E,MAAM,MAAM,yBAAyB,GAAG,eAAe,GACrD,0BAA0B,GAAG;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EACd;QACE,QAAQ,CAAC,WAAW,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;QACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;QACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;QAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;QAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;KACjD,GACD,SAAS,CAAC;IACd;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CAC7C,CAAC;AA6gBJ;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAS,yBAA8B,kDAazE,CAAC"}
@@ -5,6 +5,7 @@ import { createEveCloudSessions } from "./eveCloudSessions.js";
5
5
  import { eveExtras } from "./eveExtras.js";
6
6
  import { EVE_SDK } from "./sdkIdentity.js";
7
7
  import { MessageNotSentError, fromThreadMessageLike, generateId, pickExternalStoreSharedOptions } from "@assistant-ui/core";
8
+ import { invokeUserCallback } from "@assistant-ui/core/internal";
8
9
  import { useCloudThreadListAdapter, useExternalStoreRuntime, useRemoteThreadListRuntime, useRuntimeAdapters } from "@assistant-ui/core/react";
9
10
  import { useAui } from "@assistant-ui/store";
10
11
  import { useCallback, useEffect, useInsertionEffect, useMemo, useRef, useState } from "react";
@@ -18,19 +19,6 @@ const USER_STAGED_STATUS = {
18
19
  const sendCancelledError = new MessageNotSentError("eve send was dropped because the run was cancelled.");
19
20
  const sendAbandonedError = /* @__PURE__ */ new Error("eve send was dropped because the runtime unmounted.");
20
21
  const isDroppedSend = (error) => error === sendCancelledError || error === sendAbandonedError;
21
- const reportEveLifecycleCallbackError = (name, error) => {
22
- console.error(`[assistant-ui/eve] ${name} callback threw an error`, error);
23
- };
24
- const invokeEveLifecycleCallback = (name, callback, value) => {
25
- if (!callback) return;
26
- try {
27
- Promise.resolve(callback(value)).catch((error) => {
28
- reportEveLifecycleCallbackError(name, error);
29
- });
30
- } catch (error) {
31
- reportEveLifecycleCallbackError(name, error);
32
- }
33
- };
34
22
  const hasRunConfig = (runConfig) => runConfig?.custom !== void 0 && Object.keys(runConfig.custom).length > 0;
35
23
  /**
36
24
  * Only the `custom` bag crosses the wire. Eve reads `clientContext` as its own
@@ -66,11 +54,11 @@ const useEveThreadRuntime = (options, cloudSessions) => {
66
54
  const lastFinishStatusRef = useRef(null);
67
55
  const agent = useEveAgent({
68
56
  ...cloudThread ? withCloudThreadSession(agentOptions, cloudThread.sessionId) : agentOptions,
69
- ...onError ? { onError: (error) => invokeEveLifecycleCallback("onError", onError, error) } : {},
70
- ...onEvent ? { onEvent: (event) => invokeEveLifecycleCallback("onEvent", onEvent, event) } : {},
57
+ ...onError ? { onError: (error) => void invokeUserCallback("assistant-ui/eve", "onError", onError, error) } : {},
58
+ ...onEvent ? { onEvent: (event) => void invokeUserCallback("assistant-ui/eve", "onEvent", onEvent, event) } : {},
71
59
  onFinish: (snapshot) => {
72
60
  lastFinishStatusRef.current = snapshot.status;
73
- invokeEveLifecycleCallback("onFinish", onFinish, snapshot);
61
+ invokeUserCallback("assistant-ui/eve", "onFinish", onFinish, snapshot);
74
62
  },
75
63
  ...onSessionChange || cloudThread?.isNew ? { onSessionChange: (session) => {
76
64
  if (cloudThread?.isNew && session === void 0) cloudThread.sessions.reject(cloudThread.id, /* @__PURE__ */ new Error("The eve turn ended before it created a session."));
@@ -78,7 +66,7 @@ const useEveThreadRuntime = (options, cloudSessions) => {
78
66
  cloudThread.sessions.resolve(cloudThread.id, session.sessionId);
79
67
  if (aui.threadListItem.getState().status === "new") aui.threadListItem.initialize().catch(() => {});
80
68
  }
81
- invokeEveLifecycleCallback("onSessionChange", onSessionChange, session);
69
+ invokeUserCallback("assistant-ui/eve", "onSessionChange", onSessionChange, session);
82
70
  } } : {}
83
71
  });
84
72
  if (cloudThread && !("resume" in agent)) throw new Error("useEveAgentRuntime needs eve 0.44.1 or later for `cloud`, because opening a cloud thread resumes its session.");
@@ -1 +1 @@
1
- {"version":3,"file":"useEveAgentRuntime.js","names":[],"sources":["../src/useEveAgentRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport {\n fromThreadMessageLike,\n generateId,\n MessageNotSentError,\n pickExternalStoreSharedOptions,\n type AppendMessage,\n type AttachmentAdapter,\n type DictationAdapter,\n type ExternalStoreSharedOptions,\n type FeedbackAdapter,\n type RealtimeVoiceAdapter,\n type SpeechSynthesisAdapter,\n type ThreadMessage,\n type ToolExecutionStatus,\n} from \"@assistant-ui/core\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useRemoteThreadListRuntime,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport { useReplaySafeEffect } from \"@assistant-ui/store/internal\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type { AssistantCloud } from \"assistant-cloud\";\nimport {\n useEveAgent,\n type EveMessageData,\n type UseEveAgentOptions,\n type UseEveAgentStatus,\n} from \"eve/react\";\nimport {\n collectInterruptedTurnEvents,\n convertEveMessages,\n findEveInputRequest,\n getEveMessageContent,\n toEveInputResponse,\n type InterruptedTurnEventCache,\n} from \"./convertEveMessages\";\nimport {\n collectTurnTimestamps,\n createTurnTimestampCache,\n} from \"./deriveCreatedAt\";\nimport {\n createEveCloudSessions,\n type EveCloudSessions,\n} from \"./eveCloudSessions\";\nimport { eveExtras } from \"./eveExtras\";\nimport { EVE_SDK } from \"./sdkIdentity\";\n\nconst USER_STAGED_STATUS = {\n type: \"complete\",\n reason: \"unknown\",\n} as const;\n\nconst sendCancelledError = new MessageNotSentError(\n \"eve send was dropped because the run was cancelled.\",\n);\n\nconst sendAbandonedError = new Error(\n \"eve send was dropped because the runtime unmounted.\",\n);\n\nconst isDroppedSend = (error: unknown) =>\n error === sendCancelledError || error === sendAbandonedError;\n\ntype EveLifecycleCallbackName =\n | \"onError\"\n | \"onEvent\"\n | \"onFinish\"\n | \"onSessionChange\";\n\nconst reportEveLifecycleCallbackError = (\n name: EveLifecycleCallbackName,\n error: unknown,\n) => {\n console.error(`[assistant-ui/eve] ${name} callback threw an error`, error);\n};\n\nconst invokeEveLifecycleCallback = <T>(\n name: EveLifecycleCallbackName,\n callback: ((value: T) => unknown) | undefined,\n value: T,\n) => {\n if (!callback) return;\n\n try {\n void Promise.resolve(callback(value)).catch((error) => {\n reportEveLifecycleCallbackError(name, error);\n });\n } catch (error) {\n reportEveLifecycleCallbackError(name, error);\n }\n};\n\nconst hasRunConfig = (\n runConfig: AppendMessage[\"runConfig\"],\n): runConfig is NonNullable<AppendMessage[\"runConfig\"]> =>\n runConfig?.custom !== undefined && Object.keys(runConfig.custom).length > 0;\n\ntype EveJsonValue =\n | string\n | number\n | boolean\n | null\n | readonly EveJsonValue[]\n | { readonly [key: string]: EveJsonValue };\n\n/**\n * Structural equivalent of eve's `JsonObject`. Eve demoted its public send\n * payload type once already (`SendTurnPayload`, public through 0.30, internal\n * from 0.31), so the helpers declare the shapes they forward instead of\n * re-exporting eve's option types, and assignability is checked at the send\n * call sites against the installed version.\n */\ntype EveClientContext = { readonly [key: string]: EveJsonValue };\n\n/**\n * Only the `custom` bag crosses the wire. Eve reads `clientContext` as its own\n * namespace and serializes it into a model-visible context message, so sending\n * the assistant-ui envelope would surface a literal `\"custom\"` key in the\n * prompt and to every eve-side handler.\n */\nconst toEveSendOptions = (\n runConfig: AppendMessage[\"runConfig\"],\n): { readonly clientContext: EveClientContext } | undefined =>\n hasRunConfig(runConfig)\n ? { clientContext: runConfig.custom as EveClientContext }\n : undefined;\n\ntype EveAgentOptions = Omit<UseEveAgentOptions<EveMessageData>, \"reducer\">;\n\nexport type UseEveAgentRuntimeOptions = EveAgentOptions &\n ExternalStoreSharedOptions & {\n readonly adapters?:\n | {\n readonly attachments?: AttachmentAdapter | undefined;\n readonly speech?: SpeechSynthesisAdapter | undefined;\n readonly dictation?: DictationAdapter | undefined;\n readonly voice?: RealtimeVoiceAdapter | undefined;\n readonly feedback?: FeedbackAdapter | undefined;\n }\n | undefined;\n /**\n * Backs the thread list with Assistant Cloud. Each cloud thread keeps one Eve session as its external id, saved once the thread's first turn creates the session, and opening a thread resumes it, so `session`, `initialSession`, `initialEvents`, and `resume` are not used. Pass it from the first render: adding or removing it later throws, so remount the runtime, for example with a `key`, to switch.\n */\n readonly cloud?: AssistantCloud | undefined;\n };\n\ntype EveCloudThread = {\n readonly sessions: EveCloudSessions;\n readonly id: string;\n readonly isNew: boolean;\n readonly sessionId: string | undefined;\n};\n\nconst withCloudThreadSession = (\n {\n initialEvents: _initialEvents,\n initialSession: _initialSession,\n resume: _resume,\n session: _session,\n ...options\n }: EveAgentOptions,\n sessionId: string | undefined,\n): EveAgentOptions =>\n sessionId === undefined\n ? options\n : {\n ...options,\n initialSession: { sessionId, streamIndex: 0 },\n resume: true,\n };\n\nconst useEveThreadRuntime = (\n options: UseEveAgentRuntimeOptions,\n cloudSessions: EveCloudSessions | undefined,\n) => {\n const {\n adapters,\n cloud: _cloud,\n isDisabled: _isDisabled,\n isSendDisabled: _isSendDisabled,\n suggestions: _suggestions,\n unstable_capabilities: _unstable_capabilities,\n ...agentOptions\n } = options;\n true satisfies keyof typeof agentOptions &\n keyof ExternalStoreSharedOptions extends never\n ? true\n : never;\n\n const aui = useAui();\n const [cloudThread] = useState((): EveCloudThread | undefined => {\n if (!cloudSessions) return undefined;\n const { id, status, externalId } = aui.threadListItem.getState();\n return {\n sessions: cloudSessions,\n id,\n isNew: status === \"new\",\n sessionId: externalId,\n };\n });\n const [resumesOnMount] = useState(() =>\n cloudThread\n ? cloudThread.sessionId !== undefined\n : agentOptions.resume === true,\n );\n const isSessionless =\n cloudThread !== undefined &&\n !cloudThread.isNew &&\n cloudThread.sessionId === undefined;\n\n const { onError, onEvent, onFinish, onSessionChange } = agentOptions;\n const lastFinishStatusRef = useRef<UseEveAgentStatus | null>(null);\n const agent = useEveAgent({\n ...(cloudThread\n ? withCloudThreadSession(agentOptions, cloudThread.sessionId)\n : agentOptions),\n ...(onError\n ? {\n onError: (error) =>\n invokeEveLifecycleCallback(\"onError\", onError, error),\n }\n : {}),\n ...(onEvent\n ? {\n onEvent: (event) =>\n invokeEveLifecycleCallback(\"onEvent\", onEvent, event),\n }\n : {}),\n onFinish: (snapshot) => {\n lastFinishStatusRef.current = snapshot.status;\n invokeEveLifecycleCallback(\"onFinish\", onFinish, snapshot);\n },\n ...(onSessionChange || cloudThread?.isNew\n ? {\n onSessionChange: (session) => {\n if (cloudThread?.isNew && session === undefined) {\n cloudThread.sessions.reject(\n cloudThread.id,\n new Error(\"The eve turn ended before it created a session.\"),\n );\n } else if (cloudThread?.isNew && session !== undefined) {\n cloudThread.sessions.resolve(cloudThread.id, session.sessionId);\n // A first message staged without a run leaves the thread unsaved, so the run that creates the session saves it.\n if (aui.threadListItem.getState().status === \"new\")\n aui.threadListItem.initialize().catch(() => {});\n }\n invokeEveLifecycleCallback(\n \"onSessionChange\",\n onSessionChange,\n session,\n );\n },\n }\n : {}),\n });\n if (cloudThread && !(\"resume\" in agent)) {\n throw new Error(\n \"useEveAgentRuntime needs eve 0.44.1 or later for `cloud`, because opening a cloud thread resumes its session.\",\n );\n }\n const runtimeAdapters = useRuntimeAdapters();\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const [stagedMessages, setStagedMessages] = useState<ThreadMessage[] | null>(\n null,\n );\n const createdAtByMessageIdRef = useRef(new Map<string, Date>());\n const stagedInputsRef = useRef(\n new Map<\n string,\n { message: AppendMessage; runConfig: AppendMessage[\"runConfig\"] }\n >(),\n );\n\n const hasExecutingTools = Object.values(toolStatuses).some(\n (status) => status?.type === \"executing\",\n );\n const providerIsRunning =\n agent.status === \"submitted\" || agent.status === \"streaming\";\n const isRunning = providerIsRunning || hasExecutingTools;\n\n // Kept apart from the message memo so events that teach no timestamp keep\n // the map identity and skip rebuilding every ThreadMessage.\n const turnTimestampCacheRef = useRef(createTurnTimestampCache());\n const turnTimestamps = useMemo(\n () => collectTurnTimestamps(agent.events, turnTimestampCacheRef.current),\n [agent.events],\n );\n const interruptionCacheRef = useRef<InterruptedTurnEventCache>({\n lastEvents: [],\n interruptions: [],\n });\n const interruptionEvents = useMemo(\n () =>\n collectInterruptedTurnEvents(agent.events, interruptionCacheRef.current),\n [agent.events],\n );\n\n const convertedMessages = useMemo(() => {\n const createdAtByMessageId = createdAtByMessageIdRef.current;\n const messageIds = new Set(\n agent.data.messages.map((message) => message.id),\n );\n for (const messageId of createdAtByMessageId.keys()) {\n if (!messageIds.has(messageId)) createdAtByMessageId.delete(messageId);\n }\n\n return convertEveMessages(agent.data, {\n isRunning,\n error: agent.error,\n events: interruptionEvents,\n getCreatedAt: (message) => {\n const turnId = message.metadata?.turnId;\n const durable =\n turnId === undefined\n ? undefined\n : turnTimestamps.get(turnId)?.[message.role];\n if (durable !== undefined) return durable;\n\n const existing = createdAtByMessageId.get(message.id);\n if (existing) return existing;\n\n const createdAt = new Date();\n createdAtByMessageId.set(message.id, createdAt);\n return createdAt;\n },\n });\n }, [agent.data, agent.error, interruptionEvents, isRunning, turnTimestamps]);\n\n const messages = stagedMessages ?? convertedMessages;\n const messagesRef = useRef(messages);\n const agentRef = useRef(agent);\n // Descendant layout effects can dispatch against these refs synchronously;\n // this matches useA2ARuntime's commit-scoped ref publication.\n useInsertionEffect(() => {\n messagesRef.current = messages;\n agentRef.current = agent;\n }, [agent, messages]);\n\n // Upstream `EveAgentStore` `send` and `respond` reject while a turn is in\n // flight and only resolve once the turn's stream parks, so a pending chain\n // link is exactly an active turn; chaining every dispatch serializes them\n // without watching status.\n const sendChainRef = useRef<Promise<void>>(Promise.resolve());\n const sendEpochRef = useRef(0);\n // A cancel drops the queued send but keeps its draft for a later promotion;\n // only a reset discards the draft with the session, so the two need separate\n // counters. A queued read has no draft and outlives a cancel, so it waits on\n // the reset counter instead.\n const resetEpochRef = useRef(0);\n const isMountedRef = useRef(true);\n const runtimeRef = useRef<ReturnType<typeof useExternalStoreRuntime> | null>(\n null,\n );\n\n const enqueueSend = useCallback(\n (\n dispatch: () => Promise<void>,\n epochRef: { current: number } = sendEpochRef,\n ) => {\n const epoch = epochRef.current;\n const next = sendChainRef.current.then(() => {\n if (!isMountedRef.current) throw sendAbandonedError;\n if (epoch !== epochRef.current) throw sendCancelledError;\n return dispatch();\n });\n sendChainRef.current = next.catch(() => {});\n return next;\n },\n [],\n );\n\n // The store outlives the component (useEveAgent holds it in a ref with no\n // cleanup), so queued sends must not fire server turns after unmount. The\n // flag separates that teardown from a user cancel, and is re-armed in setup\n // for a remounted tree.\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n // A replay (Fast Refresh, StrictMode) must not cancel the sends queued behind the active turn.\n useReplaySafeEffect(\n () => () => {\n sendEpochRef.current += 1;\n },\n [],\n );\n\n // A replay rides the send chain because `resume()` rejects during a turn and upstream refuses sends while it runs; the reset counter keeps a cancel from dropping a refetch, and the hook's own replay on mount joins this one since upstream shares concurrent `resume()` calls.\n const enqueueResume = useCallback(\n () =>\n enqueueSend(async () => {\n const live = agentRef.current;\n if (live.session === undefined || !(\"resume\" in live)) return;\n await live.resume();\n }, resetEpochRef),\n [enqueueSend],\n );\n\n useEffect(() => {\n if (!resumesOnMount) return;\n // StrictMode's second setup queues the replay again, so only the setup that survives queues it.\n let active = true;\n queueMicrotask(() => {\n if (active) enqueueResume().catch(() => {});\n });\n return () => {\n active = false;\n };\n }, [enqueueResume, resumesOnMount]);\n\n useReplaySafeEffect(() => {\n if (!cloudThread?.isNew) return;\n return () => cloudThread.sessions.release(cloudThread.id);\n }, [cloudThread]);\n\n useEffect(() => {\n if (stagedInputsRef.current.size === 0) return;\n const baseIds = new Set(convertedMessages.map((message) => message.id));\n const remaining = messagesRef.current.filter(\n (message) =>\n stagedInputsRef.current.has(message.id) && !baseIds.has(message.id),\n );\n setStagedMessages(\n remaining.length > 0 ? [...convertedMessages, ...remaining] : null,\n );\n }, [convertedMessages]);\n\n const getStagedRun = (parentId: string | null) => {\n if (!parentId || !stagedInputsRef.current.has(parentId)) return null;\n const staged: {\n message: ThreadMessage;\n input: { message: AppendMessage; runConfig: AppendMessage[\"runConfig\"] };\n }[] = [];\n for (const message of messagesRef.current) {\n const input = stagedInputsRef.current.get(message.id);\n if (input) staged.push({ message, input });\n if (message.id === parentId) break;\n }\n return staged;\n };\n\n const stageUserMessage = (message: AppendMessage) => {\n const threadMessage = fromThreadMessageLike(\n message,\n generateId(),\n USER_STAGED_STATUS,\n );\n stagedInputsRef.current.set(threadMessage.id, {\n message,\n runConfig: message.runConfig,\n });\n const nextMessages = [...messagesRef.current, threadMessage];\n messagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n };\n\n const reset = useCallback(() => {\n // A cloud thread keeps the session its external id names, so a fresh session starts a new thread and leaves this one in the list.\n if (cloudThread) {\n aui.threads.switchToNewThread();\n return;\n }\n runtimeRef.current?.thread.unstable_notifySessionReset();\n // Sends parked behind an active turn captured the pre-reset epoch, so the\n // epoch has to advance before the session is torn down or they dispatch\n // into the new one; `lastFinishStatusRef` is run-scoped for the same\n // reason.\n sendEpochRef.current += 1;\n resetEpochRef.current += 1;\n lastFinishStatusRef.current = null;\n setStagedMessages(null);\n stagedInputsRef.current.clear();\n setToolStatuses({});\n agent.reset();\n }, [agent, aui, cloudThread]);\n\n const extras = useMemo(\n () =>\n eveExtras.provide({\n error: agent.error,\n events: agent.events,\n session: agent.session,\n reset,\n }),\n [agent.error, agent.events, agent.session, reset],\n );\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n messages,\n isRunning: providerIsRunning,\n isLoading: agent.status === \"resuming\",\n extras,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n adapters: {\n attachments: adapters?.attachments ?? runtimeAdapters?.attachments,\n speech: adapters?.speech,\n dictation: adapters?.dictation,\n voice: adapters?.voice,\n feedback: adapters?.feedback,\n },\n onNew: async (message) => {\n if (isSessionless)\n throw new MessageNotSentError(\n \"This thread has no eve session to send to.\",\n );\n if (!(message.startRun ?? message.role === \"user\")) {\n // Saving a new cloud thread waits for its first session, which a staged message never creates.\n if (cloudThread?.isNew)\n cloudThread.sessions.reject(\n cloudThread.id,\n new Error(\"The thread's first message was staged without a run.\"),\n );\n stageUserMessage(message);\n return;\n }\n try {\n await enqueueSend(() =>\n agent.send(\n getEveMessageContent(message),\n toEveSendOptions(message.runConfig),\n ),\n );\n } catch (error) {\n // A cancelled send never reached the session, so it rethrows for the\n // composer to take the draft back; an unmounted one has no composer\n // left to restore.\n if (error === sendAbandonedError) return;\n throw error;\n }\n },\n ...(stagedMessages\n ? {\n onReload: async (parentId: string | null, config) => {\n const stagedRun = getStagedRun(parentId);\n if (!stagedRun)\n throw new Error(\"Runtime does not support reloading messages.\");\n const epoch = sendEpochRef.current;\n const resetEpoch = resetEpochRef.current;\n for (const { message: stagedMessage, input } of stagedRun) {\n if (epoch !== sendEpochRef.current) return;\n const previousMessages = messagesRef.current;\n stagedInputsRef.current.delete(stagedMessage.id);\n const nextMessages = previousMessages.filter(\n (message) => message.id !== stagedMessage.id,\n );\n messagesRef.current = nextMessages;\n setStagedMessages(\n stagedInputsRef.current.size > 0 ? nextMessages : null,\n );\n // The reload config belongs to the message being reloaded; the\n // drafts promoted ahead of it keep the config they were staged\n // with, or reloading the tail would rewrite their context too.\n const runConfig =\n stagedMessage.id === parentId && hasRunConfig(config.runConfig)\n ? config.runConfig\n : input.runConfig;\n try {\n await enqueueSend(() =>\n agent.send(\n getEveMessageContent(input.message),\n toEveSendOptions(runConfig),\n ),\n );\n } catch (error) {\n // A reset discarded the draft along with the session, so\n // restoring it here would refloat a message the thread no\n // longer has. A cancel still restores.\n if (resetEpoch !== resetEpochRef.current) return;\n stagedInputsRef.current.set(stagedMessage.id, input);\n messagesRef.current = previousMessages;\n setStagedMessages(previousMessages);\n if (isDroppedSend(error)) return;\n throw error;\n }\n if (lastFinishStatusRef.current === \"error\") return;\n }\n },\n }\n : {}),\n onCancel: async () => {\n sendEpochRef.current += 1;\n // Eve 0.38 replaced the binding's local-abort `stop()` with the durable\n // `cancel()`, so the adapter detects which side of that break the host's\n // eve provides instead of pinning the peer range to one of them.\n const controls = agent as\n | { readonly cancel: () => Promise<unknown> }\n | { readonly stop: () => void };\n if (\"cancel\" in controls) {\n await controls.cancel();\n } else {\n controls.stop();\n }\n },\n // Hosts below eve 0.44.1 expose no `resume`; leaving the capability absent\n // keeps `threads.reloadMainThread()` on core's no-capability no-op.\n ...(\"resume\" in agent\n ? {\n onRefetchThread: () =>\n enqueueResume().catch((error) => {\n if (isDroppedSend(error)) return;\n throw error;\n }),\n }\n : {}),\n onRespondToToolApproval: (response) => {\n // Eve resolves a request the moment any response for it arrives, and an\n // empty one is recorded as an answer with no content. Mapping before the\n // send is enqueued keeps an unmappable response unsent, so the request\n // stays answerable; the mapper's error reaches the caller as the seam's\n // rejection.\n const inputResponse = toEveInputResponse(\n response,\n findEveInputRequest(agent.data, response.approvalId),\n );\n return enqueueSend(() => agent.respond([inputResponse])).catch(\n (error) => {\n if (!isDroppedSend(error)) throw error;\n },\n );\n },\n });\n runtimeRef.current = runtime;\n\n return runtime;\n};\n\nconst useEveCloudRuntime = (\n cloud: AssistantCloud,\n options: UseEveAgentRuntimeOptions,\n) => {\n const [sessions] = useState(createEveCloudSessions);\n const adapter = useCloudThreadListAdapter({\n cloud,\n sdk: EVE_SDK,\n upsert: true,\n create: async (threadId) => ({ externalId: await sessions.wait(threadId) }),\n });\n return useRemoteThreadListRuntime({\n adapter,\n allowNesting: true,\n runtimeHook: function RuntimeHook() {\n return useEveThreadRuntime(options, sessions);\n },\n });\n};\n\n/**\n * Connects Eve's `useEveAgent` hook to assistant-ui's runtime contract.\n *\n * The runtime renders Eve messages, forwards new user messages to the Eve\n * session, supports cancellation, and maps Eve input requests to assistant-ui\n * tool approval UI.\n */\nexport const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {\n const [hasCloud] = useState(options.cloud !== undefined);\n if ((options.cloud !== undefined) !== hasCloud) {\n throw new Error(\n \"useEveAgentRuntime cannot add or remove `cloud` after it mounts; remount it, for example with a `key`, to switch.\",\n );\n }\n if (!options.cloud) {\n // oxlint-disable-next-line react-hooks/rules-of-hooks -- `cloud` stays set or unset for the runtime's lifetime, so every render takes the same branch\n return useEveThreadRuntime(options, undefined);\n }\n // oxlint-disable-next-line react-hooks/rules-of-hooks -- `cloud` stays set or unset for the runtime's lifetime, so every render takes the same branch\n return useEveCloudRuntime(options.cloud, options);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useEveAgentRuntime.js","names":[],"sources":["../src/useEveAgentRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport {\n fromThreadMessageLike,\n generateId,\n MessageNotSentError,\n pickExternalStoreSharedOptions,\n type AppendMessage,\n type AttachmentAdapter,\n type DictationAdapter,\n type ExternalStoreSharedOptions,\n type FeedbackAdapter,\n type RealtimeVoiceAdapter,\n type SpeechSynthesisAdapter,\n type ThreadMessage,\n type ToolExecutionStatus,\n} from \"@assistant-ui/core\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useRemoteThreadListRuntime,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport { invokeUserCallback } from \"@assistant-ui/core/internal\";\nimport { useReplaySafeEffect } from \"@assistant-ui/store/internal\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type { AssistantCloud } from \"assistant-cloud\";\nimport {\n useEveAgent,\n type EveMessageData,\n type UseEveAgentOptions,\n type UseEveAgentStatus,\n} from \"eve/react\";\nimport {\n collectInterruptedTurnEvents,\n convertEveMessages,\n findEveInputRequest,\n getEveMessageContent,\n toEveInputResponse,\n type InterruptedTurnEventCache,\n} from \"./convertEveMessages\";\nimport {\n collectTurnTimestamps,\n createTurnTimestampCache,\n} from \"./deriveCreatedAt\";\nimport {\n createEveCloudSessions,\n type EveCloudSessions,\n} from \"./eveCloudSessions\";\nimport { eveExtras } from \"./eveExtras\";\nimport { EVE_SDK } from \"./sdkIdentity\";\n\nconst USER_STAGED_STATUS = {\n type: \"complete\",\n reason: \"unknown\",\n} as const;\n\nconst sendCancelledError = new MessageNotSentError(\n \"eve send was dropped because the run was cancelled.\",\n);\n\nconst sendAbandonedError = new Error(\n \"eve send was dropped because the runtime unmounted.\",\n);\n\nconst isDroppedSend = (error: unknown) =>\n error === sendCancelledError || error === sendAbandonedError;\n\nconst hasRunConfig = (\n runConfig: AppendMessage[\"runConfig\"],\n): runConfig is NonNullable<AppendMessage[\"runConfig\"]> =>\n runConfig?.custom !== undefined && Object.keys(runConfig.custom).length > 0;\n\ntype EveJsonValue =\n | string\n | number\n | boolean\n | null\n | readonly EveJsonValue[]\n | { readonly [key: string]: EveJsonValue };\n\n/**\n * Structural equivalent of eve's `JsonObject`. Eve demoted its public send\n * payload type once already (`SendTurnPayload`, public through 0.30, internal\n * from 0.31), so the helpers declare the shapes they forward instead of\n * re-exporting eve's option types, and assignability is checked at the send\n * call sites against the installed version.\n */\ntype EveClientContext = { readonly [key: string]: EveJsonValue };\n\n/**\n * Only the `custom` bag crosses the wire. Eve reads `clientContext` as its own\n * namespace and serializes it into a model-visible context message, so sending\n * the assistant-ui envelope would surface a literal `\"custom\"` key in the\n * prompt and to every eve-side handler.\n */\nconst toEveSendOptions = (\n runConfig: AppendMessage[\"runConfig\"],\n): { readonly clientContext: EveClientContext } | undefined =>\n hasRunConfig(runConfig)\n ? { clientContext: runConfig.custom as EveClientContext }\n : undefined;\n\ntype EveAgentOptions = Omit<UseEveAgentOptions<EveMessageData>, \"reducer\">;\n\nexport type UseEveAgentRuntimeOptions = EveAgentOptions &\n ExternalStoreSharedOptions & {\n readonly adapters?:\n | {\n readonly attachments?: AttachmentAdapter | undefined;\n readonly speech?: SpeechSynthesisAdapter | undefined;\n readonly dictation?: DictationAdapter | undefined;\n readonly voice?: RealtimeVoiceAdapter | undefined;\n readonly feedback?: FeedbackAdapter | undefined;\n }\n | undefined;\n /**\n * Backs the thread list with Assistant Cloud. Each cloud thread keeps one Eve session as its external id, saved once the thread's first turn creates the session, and opening a thread resumes it, so `session`, `initialSession`, `initialEvents`, and `resume` are not used. Pass it from the first render: adding or removing it later throws, so remount the runtime, for example with a `key`, to switch.\n */\n readonly cloud?: AssistantCloud | undefined;\n };\n\ntype EveCloudThread = {\n readonly sessions: EveCloudSessions;\n readonly id: string;\n readonly isNew: boolean;\n readonly sessionId: string | undefined;\n};\n\nconst withCloudThreadSession = (\n {\n initialEvents: _initialEvents,\n initialSession: _initialSession,\n resume: _resume,\n session: _session,\n ...options\n }: EveAgentOptions,\n sessionId: string | undefined,\n): EveAgentOptions =>\n sessionId === undefined\n ? options\n : {\n ...options,\n initialSession: { sessionId, streamIndex: 0 },\n resume: true,\n };\n\nconst useEveThreadRuntime = (\n options: UseEveAgentRuntimeOptions,\n cloudSessions: EveCloudSessions | undefined,\n) => {\n const {\n adapters,\n cloud: _cloud,\n isDisabled: _isDisabled,\n isSendDisabled: _isSendDisabled,\n suggestions: _suggestions,\n unstable_capabilities: _unstable_capabilities,\n ...agentOptions\n } = options;\n true satisfies keyof typeof agentOptions &\n keyof ExternalStoreSharedOptions extends never\n ? true\n : never;\n\n const aui = useAui();\n const [cloudThread] = useState((): EveCloudThread | undefined => {\n if (!cloudSessions) return undefined;\n const { id, status, externalId } = aui.threadListItem.getState();\n return {\n sessions: cloudSessions,\n id,\n isNew: status === \"new\",\n sessionId: externalId,\n };\n });\n const [resumesOnMount] = useState(() =>\n cloudThread\n ? cloudThread.sessionId !== undefined\n : agentOptions.resume === true,\n );\n const isSessionless =\n cloudThread !== undefined &&\n !cloudThread.isNew &&\n cloudThread.sessionId === undefined;\n\n const { onError, onEvent, onFinish, onSessionChange } = agentOptions;\n const lastFinishStatusRef = useRef<UseEveAgentStatus | null>(null);\n const agent = useEveAgent({\n ...(cloudThread\n ? withCloudThreadSession(agentOptions, cloudThread.sessionId)\n : agentOptions),\n ...(onError\n ? {\n onError: (error) =>\n void invokeUserCallback(\n \"assistant-ui/eve\",\n \"onError\",\n onError,\n error,\n ),\n }\n : {}),\n ...(onEvent\n ? {\n onEvent: (event) =>\n void invokeUserCallback(\n \"assistant-ui/eve\",\n \"onEvent\",\n onEvent,\n event,\n ),\n }\n : {}),\n onFinish: (snapshot) => {\n lastFinishStatusRef.current = snapshot.status;\n void invokeUserCallback(\n \"assistant-ui/eve\",\n \"onFinish\",\n onFinish,\n snapshot,\n );\n },\n ...(onSessionChange || cloudThread?.isNew\n ? {\n onSessionChange: (session) => {\n if (cloudThread?.isNew && session === undefined) {\n cloudThread.sessions.reject(\n cloudThread.id,\n new Error(\"The eve turn ended before it created a session.\"),\n );\n } else if (cloudThread?.isNew && session !== undefined) {\n cloudThread.sessions.resolve(cloudThread.id, session.sessionId);\n // A first message staged without a run leaves the thread unsaved, so the run that creates the session saves it.\n if (aui.threadListItem.getState().status === \"new\")\n aui.threadListItem.initialize().catch(() => {});\n }\n void invokeUserCallback(\n \"assistant-ui/eve\",\n \"onSessionChange\",\n onSessionChange,\n session,\n );\n },\n }\n : {}),\n });\n if (cloudThread && !(\"resume\" in agent)) {\n throw new Error(\n \"useEveAgentRuntime needs eve 0.44.1 or later for `cloud`, because opening a cloud thread resumes its session.\",\n );\n }\n const runtimeAdapters = useRuntimeAdapters();\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const [stagedMessages, setStagedMessages] = useState<ThreadMessage[] | null>(\n null,\n );\n const createdAtByMessageIdRef = useRef(new Map<string, Date>());\n const stagedInputsRef = useRef(\n new Map<\n string,\n { message: AppendMessage; runConfig: AppendMessage[\"runConfig\"] }\n >(),\n );\n\n const hasExecutingTools = Object.values(toolStatuses).some(\n (status) => status?.type === \"executing\",\n );\n const providerIsRunning =\n agent.status === \"submitted\" || agent.status === \"streaming\";\n const isRunning = providerIsRunning || hasExecutingTools;\n\n // Kept apart from the message memo so events that teach no timestamp keep\n // the map identity and skip rebuilding every ThreadMessage.\n const turnTimestampCacheRef = useRef(createTurnTimestampCache());\n const turnTimestamps = useMemo(\n () => collectTurnTimestamps(agent.events, turnTimestampCacheRef.current),\n [agent.events],\n );\n const interruptionCacheRef = useRef<InterruptedTurnEventCache>({\n lastEvents: [],\n interruptions: [],\n });\n const interruptionEvents = useMemo(\n () =>\n collectInterruptedTurnEvents(agent.events, interruptionCacheRef.current),\n [agent.events],\n );\n\n const convertedMessages = useMemo(() => {\n const createdAtByMessageId = createdAtByMessageIdRef.current;\n const messageIds = new Set(\n agent.data.messages.map((message) => message.id),\n );\n for (const messageId of createdAtByMessageId.keys()) {\n if (!messageIds.has(messageId)) createdAtByMessageId.delete(messageId);\n }\n\n return convertEveMessages(agent.data, {\n isRunning,\n error: agent.error,\n events: interruptionEvents,\n getCreatedAt: (message) => {\n const turnId = message.metadata?.turnId;\n const durable =\n turnId === undefined\n ? undefined\n : turnTimestamps.get(turnId)?.[message.role];\n if (durable !== undefined) return durable;\n\n const existing = createdAtByMessageId.get(message.id);\n if (existing) return existing;\n\n const createdAt = new Date();\n createdAtByMessageId.set(message.id, createdAt);\n return createdAt;\n },\n });\n }, [agent.data, agent.error, interruptionEvents, isRunning, turnTimestamps]);\n\n const messages = stagedMessages ?? convertedMessages;\n const messagesRef = useRef(messages);\n const agentRef = useRef(agent);\n // Descendant layout effects can dispatch against these refs synchronously;\n // this matches useA2ARuntime's commit-scoped ref publication.\n useInsertionEffect(() => {\n messagesRef.current = messages;\n agentRef.current = agent;\n }, [agent, messages]);\n\n // Upstream `EveAgentStore` `send` and `respond` reject while a turn is in\n // flight and only resolve once the turn's stream parks, so a pending chain\n // link is exactly an active turn; chaining every dispatch serializes them\n // without watching status.\n const sendChainRef = useRef<Promise<void>>(Promise.resolve());\n const sendEpochRef = useRef(0);\n // A cancel drops the queued send but keeps its draft for a later promotion;\n // only a reset discards the draft with the session, so the two need separate\n // counters. A queued read has no draft and outlives a cancel, so it waits on\n // the reset counter instead.\n const resetEpochRef = useRef(0);\n const isMountedRef = useRef(true);\n const runtimeRef = useRef<ReturnType<typeof useExternalStoreRuntime> | null>(\n null,\n );\n\n const enqueueSend = useCallback(\n (\n dispatch: () => Promise<void>,\n epochRef: { current: number } = sendEpochRef,\n ) => {\n const epoch = epochRef.current;\n const next = sendChainRef.current.then(() => {\n if (!isMountedRef.current) throw sendAbandonedError;\n if (epoch !== epochRef.current) throw sendCancelledError;\n return dispatch();\n });\n sendChainRef.current = next.catch(() => {});\n return next;\n },\n [],\n );\n\n // The store outlives the component (useEveAgent holds it in a ref with no\n // cleanup), so queued sends must not fire server turns after unmount. The\n // flag separates that teardown from a user cancel, and is re-armed in setup\n // for a remounted tree.\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n // A replay (Fast Refresh, StrictMode) must not cancel the sends queued behind the active turn.\n useReplaySafeEffect(\n () => () => {\n sendEpochRef.current += 1;\n },\n [],\n );\n\n // A replay rides the send chain because `resume()` rejects during a turn and upstream refuses sends while it runs; the reset counter keeps a cancel from dropping a refetch, and the hook's own replay on mount joins this one since upstream shares concurrent `resume()` calls.\n const enqueueResume = useCallback(\n () =>\n enqueueSend(async () => {\n const live = agentRef.current;\n if (live.session === undefined || !(\"resume\" in live)) return;\n await live.resume();\n }, resetEpochRef),\n [enqueueSend],\n );\n\n useEffect(() => {\n if (!resumesOnMount) return;\n // StrictMode's second setup queues the replay again, so only the setup that survives queues it.\n let active = true;\n queueMicrotask(() => {\n if (active) enqueueResume().catch(() => {});\n });\n return () => {\n active = false;\n };\n }, [enqueueResume, resumesOnMount]);\n\n useReplaySafeEffect(() => {\n if (!cloudThread?.isNew) return;\n return () => cloudThread.sessions.release(cloudThread.id);\n }, [cloudThread]);\n\n useEffect(() => {\n if (stagedInputsRef.current.size === 0) return;\n const baseIds = new Set(convertedMessages.map((message) => message.id));\n const remaining = messagesRef.current.filter(\n (message) =>\n stagedInputsRef.current.has(message.id) && !baseIds.has(message.id),\n );\n setStagedMessages(\n remaining.length > 0 ? [...convertedMessages, ...remaining] : null,\n );\n }, [convertedMessages]);\n\n const getStagedRun = (parentId: string | null) => {\n if (!parentId || !stagedInputsRef.current.has(parentId)) return null;\n const staged: {\n message: ThreadMessage;\n input: { message: AppendMessage; runConfig: AppendMessage[\"runConfig\"] };\n }[] = [];\n for (const message of messagesRef.current) {\n const input = stagedInputsRef.current.get(message.id);\n if (input) staged.push({ message, input });\n if (message.id === parentId) break;\n }\n return staged;\n };\n\n const stageUserMessage = (message: AppendMessage) => {\n const threadMessage = fromThreadMessageLike(\n message,\n generateId(),\n USER_STAGED_STATUS,\n );\n stagedInputsRef.current.set(threadMessage.id, {\n message,\n runConfig: message.runConfig,\n });\n const nextMessages = [...messagesRef.current, threadMessage];\n messagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n };\n\n const reset = useCallback(() => {\n // A cloud thread keeps the session its external id names, so a fresh session starts a new thread and leaves this one in the list.\n if (cloudThread) {\n aui.threads.switchToNewThread();\n return;\n }\n runtimeRef.current?.thread.unstable_notifySessionReset();\n // Sends parked behind an active turn captured the pre-reset epoch, so the\n // epoch has to advance before the session is torn down or they dispatch\n // into the new one; `lastFinishStatusRef` is run-scoped for the same\n // reason.\n sendEpochRef.current += 1;\n resetEpochRef.current += 1;\n lastFinishStatusRef.current = null;\n setStagedMessages(null);\n stagedInputsRef.current.clear();\n setToolStatuses({});\n agent.reset();\n }, [agent, aui, cloudThread]);\n\n const extras = useMemo(\n () =>\n eveExtras.provide({\n error: agent.error,\n events: agent.events,\n session: agent.session,\n reset,\n }),\n [agent.error, agent.events, agent.session, reset],\n );\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n messages,\n isRunning: providerIsRunning,\n isLoading: agent.status === \"resuming\",\n extras,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n adapters: {\n attachments: adapters?.attachments ?? runtimeAdapters?.attachments,\n speech: adapters?.speech,\n dictation: adapters?.dictation,\n voice: adapters?.voice,\n feedback: adapters?.feedback,\n },\n onNew: async (message) => {\n if (isSessionless)\n throw new MessageNotSentError(\n \"This thread has no eve session to send to.\",\n );\n if (!(message.startRun ?? message.role === \"user\")) {\n // Saving a new cloud thread waits for its first session, which a staged message never creates.\n if (cloudThread?.isNew)\n cloudThread.sessions.reject(\n cloudThread.id,\n new Error(\"The thread's first message was staged without a run.\"),\n );\n stageUserMessage(message);\n return;\n }\n try {\n await enqueueSend(() =>\n agent.send(\n getEveMessageContent(message),\n toEveSendOptions(message.runConfig),\n ),\n );\n } catch (error) {\n // A cancelled send never reached the session, so it rethrows for the\n // composer to take the draft back; an unmounted one has no composer\n // left to restore.\n if (error === sendAbandonedError) return;\n throw error;\n }\n },\n ...(stagedMessages\n ? {\n onReload: async (parentId: string | null, config) => {\n const stagedRun = getStagedRun(parentId);\n if (!stagedRun)\n throw new Error(\"Runtime does not support reloading messages.\");\n const epoch = sendEpochRef.current;\n const resetEpoch = resetEpochRef.current;\n for (const { message: stagedMessage, input } of stagedRun) {\n if (epoch !== sendEpochRef.current) return;\n const previousMessages = messagesRef.current;\n stagedInputsRef.current.delete(stagedMessage.id);\n const nextMessages = previousMessages.filter(\n (message) => message.id !== stagedMessage.id,\n );\n messagesRef.current = nextMessages;\n setStagedMessages(\n stagedInputsRef.current.size > 0 ? nextMessages : null,\n );\n // The reload config belongs to the message being reloaded; the\n // drafts promoted ahead of it keep the config they were staged\n // with, or reloading the tail would rewrite their context too.\n const runConfig =\n stagedMessage.id === parentId && hasRunConfig(config.runConfig)\n ? config.runConfig\n : input.runConfig;\n try {\n await enqueueSend(() =>\n agent.send(\n getEveMessageContent(input.message),\n toEveSendOptions(runConfig),\n ),\n );\n } catch (error) {\n // A reset discarded the draft along with the session, so\n // restoring it here would refloat a message the thread no\n // longer has. A cancel still restores.\n if (resetEpoch !== resetEpochRef.current) return;\n stagedInputsRef.current.set(stagedMessage.id, input);\n messagesRef.current = previousMessages;\n setStagedMessages(previousMessages);\n if (isDroppedSend(error)) return;\n throw error;\n }\n if (lastFinishStatusRef.current === \"error\") return;\n }\n },\n }\n : {}),\n onCancel: async () => {\n sendEpochRef.current += 1;\n // Eve 0.38 replaced the binding's local-abort `stop()` with the durable\n // `cancel()`, so the adapter detects which side of that break the host's\n // eve provides instead of pinning the peer range to one of them.\n const controls = agent as\n | { readonly cancel: () => Promise<unknown> }\n | { readonly stop: () => void };\n if (\"cancel\" in controls) {\n await controls.cancel();\n } else {\n controls.stop();\n }\n },\n // Hosts below eve 0.44.1 expose no `resume`; leaving the capability absent\n // keeps `threads.reloadMainThread()` on core's no-capability no-op.\n ...(\"resume\" in agent\n ? {\n onRefetchThread: () =>\n enqueueResume().catch((error) => {\n if (isDroppedSend(error)) return;\n throw error;\n }),\n }\n : {}),\n onRespondToToolApproval: (response) => {\n // Eve resolves a request the moment any response for it arrives, and an\n // empty one is recorded as an answer with no content. Mapping before the\n // send is enqueued keeps an unmappable response unsent, so the request\n // stays answerable; the mapper's error reaches the caller as the seam's\n // rejection.\n const inputResponse = toEveInputResponse(\n response,\n findEveInputRequest(agent.data, response.approvalId),\n );\n return enqueueSend(() => agent.respond([inputResponse])).catch(\n (error) => {\n if (!isDroppedSend(error)) throw error;\n },\n );\n },\n });\n runtimeRef.current = runtime;\n\n return runtime;\n};\n\nconst useEveCloudRuntime = (\n cloud: AssistantCloud,\n options: UseEveAgentRuntimeOptions,\n) => {\n const [sessions] = useState(createEveCloudSessions);\n const adapter = useCloudThreadListAdapter({\n cloud,\n sdk: EVE_SDK,\n upsert: true,\n create: async (threadId) => ({ externalId: await sessions.wait(threadId) }),\n });\n return useRemoteThreadListRuntime({\n adapter,\n allowNesting: true,\n runtimeHook: function RuntimeHook() {\n return useEveThreadRuntime(options, sessions);\n },\n });\n};\n\n/**\n * Connects Eve's `useEveAgent` hook to assistant-ui's runtime contract.\n *\n * The runtime renders Eve messages, forwards new user messages to the Eve\n * session, supports cancellation, and maps Eve input requests to assistant-ui\n * tool approval UI.\n */\nexport const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {\n const [hasCloud] = useState(options.cloud !== undefined);\n if ((options.cloud !== undefined) !== hasCloud) {\n throw new Error(\n \"useEveAgentRuntime cannot add or remove `cloud` after it mounts; remount it, for example with a `key`, to switch.\",\n );\n }\n if (!options.cloud) {\n // oxlint-disable-next-line react-hooks/rules-of-hooks -- `cloud` stays set or unset for the runtime's lifetime, so every render takes the same branch\n return useEveThreadRuntime(options, undefined);\n }\n // oxlint-disable-next-line react-hooks/rules-of-hooks -- `cloud` stays set or unset for the runtime's lifetime, so every render takes the same branch\n return useEveCloudRuntime(options.cloud, options);\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.22",
3
+ "version": "0.0.23",
4
4
  "description": "Eve runtime adapter for assistant-ui",
5
5
  "keywords": [
6
6
  "eve",
@@ -28,8 +28,8 @@
28
28
  ],
29
29
  "sideEffects": false,
30
30
  "dependencies": {
31
- "@assistant-ui/core": "^0.3.24",
32
- "@assistant-ui/store": "^0.3.17",
31
+ "@assistant-ui/core": "^0.3.25",
32
+ "@assistant-ui/store": "^0.3.18",
33
33
  "assistant-cloud": "*"
34
34
  },
35
35
  "peerDependencies": {
@@ -48,8 +48,8 @@
48
48
  "@testing-library/dom": "^10.4.2",
49
49
  "@testing-library/react": "^16.3.3",
50
50
  "@types/react": "^19.3.0",
51
- "eve": "^0.70.1",
52
- "jsdom": "^30.0.1",
51
+ "eve": "^0.74.0",
52
+ "jsdom": "^30.1.2",
53
53
  "react": "^19.3.0",
54
54
  "react-dom": "^19.3.0",
55
55
  "vitest": "^5.0.3"
@@ -213,6 +213,61 @@ describe("convertEveMessages", () => {
213
213
  });
214
214
  });
215
215
 
216
+ it("keeps streaming dynamic tool args incomplete until input is available", () => {
217
+ const streamingData = {
218
+ messages: [
219
+ {
220
+ id: "a1",
221
+ role: "assistant",
222
+ parts: [
223
+ {
224
+ type: "dynamic-tool",
225
+ state: "input-streaming",
226
+ toolCallId: "call_1",
227
+ toolName: "search",
228
+ input: undefined,
229
+ inputText: "",
230
+ },
231
+ ],
232
+ },
233
+ ],
234
+ } satisfies EveMessageData;
235
+
236
+ const [streamingMessage] = convertEveMessages(streamingData);
237
+
238
+ expect(streamingMessage?.content[0]).toMatchObject({
239
+ type: "tool-call",
240
+ args: {},
241
+ argsText: "",
242
+ });
243
+
244
+ const availableData = {
245
+ messages: [
246
+ {
247
+ id: "a1",
248
+ role: "assistant",
249
+ parts: [
250
+ {
251
+ type: "dynamic-tool",
252
+ state: "input-available",
253
+ toolCallId: "call_1",
254
+ toolName: "search",
255
+ input: { query: "badge" },
256
+ },
257
+ ],
258
+ },
259
+ ],
260
+ } satisfies EveMessageData;
261
+
262
+ const [availableMessage] = convertEveMessages(availableData);
263
+
264
+ expect(availableMessage?.content[0]).toMatchObject({
265
+ type: "tool-call",
266
+ args: { query: "badge" },
267
+ argsText: '{"query":"badge"}',
268
+ });
269
+ });
270
+
216
271
  it.each([
217
272
  [
218
273
  "a confirmation request",
@@ -705,7 +760,7 @@ describe("convertEveMessages", () => {
705
760
  ]);
706
761
  });
707
762
 
708
- it("defaults a file part with a missing mediaType to unknown/unknown", () => {
763
+ it("defaults a file part with a missing mediaType to application/octet-stream", () => {
709
764
  const data = {
710
765
  messages: [
711
766
  {
@@ -722,7 +777,7 @@ describe("convertEveMessages", () => {
722
777
  {
723
778
  type: "file",
724
779
  data: "https://example.com/blob",
725
- mimeType: "unknown/unknown",
780
+ mimeType: "application/octet-stream",
726
781
  sourceType: "url",
727
782
  },
728
783
  ]);
@@ -735,16 +790,66 @@ describe("convertEveMessages", () => {
735
790
  {
736
791
  type: "file",
737
792
  data: "https://example.com/blob",
738
- mimeType: "unknown/unknown",
793
+ mimeType: "application/octet-stream",
739
794
  sourceType: "url",
740
795
  },
741
796
  ],
742
- contentType: "unknown/unknown",
797
+ contentType: "application/octet-stream",
743
798
  status: { type: "complete" },
744
799
  },
745
800
  ]);
746
801
  });
747
802
 
803
+ it("reads the media type from a data URL when eve omits mediaType", () => {
804
+ const data = {
805
+ messages: [
806
+ {
807
+ id: "u1",
808
+ role: "user",
809
+ parts: [{ type: "file", url: "data:image/png;base64,iVBORw0KGgo=" }],
810
+ },
811
+ ],
812
+ } as unknown as EveMessageData;
813
+
814
+ const [message] = convertEveMessages(data);
815
+
816
+ expect(message?.content).toEqual([
817
+ {
818
+ type: "file",
819
+ data: "data:image/png;base64,iVBORw0KGgo=",
820
+ mimeType: "image/png",
821
+ },
822
+ ]);
823
+ expect(message?.attachments?.map((a) => [a.type, a.contentType])).toEqual([
824
+ ["image", "image/png"],
825
+ ]);
826
+ });
827
+
828
+ it("prefers an explicit mediaType over the data URL declaration", () => {
829
+ const data = {
830
+ messages: [
831
+ {
832
+ id: "u1",
833
+ role: "user",
834
+ parts: [
835
+ {
836
+ type: "file",
837
+ url: "data:application/octet-stream;base64,JVBERi0=",
838
+ mediaType: "application/pdf",
839
+ },
840
+ ],
841
+ },
842
+ ],
843
+ } satisfies EveMessageData;
844
+
845
+ const [message] = convertEveMessages(data);
846
+
847
+ expect(message?.content[0]).toMatchObject({
848
+ type: "file",
849
+ mimeType: "application/pdf",
850
+ });
851
+ });
852
+
748
853
  it("converts an assistant file part into a file content part", () => {
749
854
  const data = {
750
855
  messages: [
@@ -1877,6 +1982,174 @@ describe("getEveMessageContent", () => {
1877
1982
  expect(getEveMessageContent(message)).toBe("Hello");
1878
1983
  });
1879
1984
 
1985
+ it("declares the data URL subtype of an image part", () => {
1986
+ const message = {
1987
+ ...baseAppendMessage,
1988
+ content: [{ type: "image", image: "data:image/jpeg;base64,/9j/4AAQ" }],
1989
+ } as unknown as AppendMessage;
1990
+
1991
+ expect(getEveMessageContent(message)).toEqual([
1992
+ {
1993
+ type: "file",
1994
+ data: "data:image/jpeg;base64,/9j/4AAQ",
1995
+ mediaType: "image/jpeg",
1996
+ },
1997
+ ]);
1998
+ });
1999
+
2000
+ it("sniffs an image part behind a generic envelope and rebuilds it", () => {
2001
+ const message = {
2002
+ ...baseAppendMessage,
2003
+ content: [
2004
+ {
2005
+ type: "image",
2006
+ image:
2007
+ "data:application/octet-stream;base64,iVBORw0KGgoAAAANSUhEUgAAAAE=",
2008
+ },
2009
+ ],
2010
+ } as unknown as AppendMessage;
2011
+
2012
+ expect(getEveMessageContent(message)).toEqual([
2013
+ {
2014
+ type: "file",
2015
+ data: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAE=",
2016
+ mediaType: "image/png",
2017
+ },
2018
+ ]);
2019
+ });
2020
+
2021
+ it("floors an http image part to image/png instead of a wildcard", () => {
2022
+ const message = {
2023
+ ...baseAppendMessage,
2024
+ content: [
2025
+ {
2026
+ type: "image",
2027
+ image: "https://example.com/photo",
2028
+ filename: "photo",
2029
+ },
2030
+ ],
2031
+ } as unknown as AppendMessage;
2032
+
2033
+ expect(getEveMessageContent(message)).toEqual([
2034
+ {
2035
+ type: "file",
2036
+ data: "https://example.com/photo",
2037
+ mediaType: "image/png",
2038
+ filename: "photo",
2039
+ },
2040
+ ]);
2041
+ });
2042
+
2043
+ it("declares an image attachment's content type for a url payload", () => {
2044
+ const message = {
2045
+ ...baseAppendMessage,
2046
+ content: [],
2047
+ attachments: [
2048
+ {
2049
+ id: "1",
2050
+ type: "image",
2051
+ name: "photo.jpg",
2052
+ contentType: "image/jpeg",
2053
+ content: [{ type: "image", image: "https://example.com/photo.jpg" }],
2054
+ status: { type: "complete" },
2055
+ },
2056
+ ],
2057
+ } as unknown as AppendMessage;
2058
+
2059
+ expect(getEveMessageContent(message)).toEqual([
2060
+ {
2061
+ type: "file",
2062
+ data: "https://example.com/photo.jpg",
2063
+ mediaType: "image/jpeg",
2064
+ },
2065
+ ]);
2066
+ });
2067
+
2068
+ it.each([
2069
+ {
2070
+ name: "an untyped URL",
2071
+ data: "https://example.com/file",
2072
+ mimeType: "",
2073
+ mediaType: "application/octet-stream",
2074
+ wireData: "https://example.com/file",
2075
+ },
2076
+ {
2077
+ name: "a data URL without an explicit type",
2078
+ data: "data:application/pdf;base64,JVBERi0=",
2079
+ mimeType: "",
2080
+ mediaType: "application/pdf",
2081
+ wireData: "data:application/pdf;base64,JVBERi0=",
2082
+ },
2083
+ {
2084
+ name: "a data URL with a conflicting envelope",
2085
+ data: "data:application/octet-stream;base64,JVBERi0=",
2086
+ mimeType: "application/pdf",
2087
+ mediaType: "application/pdf",
2088
+ wireData: "data:application/pdf;base64,JVBERi0=",
2089
+ },
2090
+ ])(
2091
+ "resolves file media types for $name",
2092
+ ({ data, mimeType, mediaType, wireData }) => {
2093
+ const message = {
2094
+ ...baseAppendMessage,
2095
+ content: [{ type: "file", data, mimeType, filename: "report.pdf" }],
2096
+ } satisfies AppendMessage;
2097
+
2098
+ expect(getEveMessageContent(message)).toEqual([
2099
+ { type: "file", data: wireData, mediaType, filename: "report.pdf" },
2100
+ ]);
2101
+ },
2102
+ );
2103
+
2104
+ it("uses a file attachment's content type when its part has none", () => {
2105
+ const message = {
2106
+ ...baseAppendMessage,
2107
+ content: [],
2108
+ attachments: [
2109
+ {
2110
+ id: "file-1",
2111
+ type: "file",
2112
+ name: "report.pdf",
2113
+ contentType: "application/pdf",
2114
+ content: [
2115
+ {
2116
+ type: "file",
2117
+ data: "https://example.com/report.pdf",
2118
+ mimeType: "",
2119
+ },
2120
+ ],
2121
+ status: { type: "complete" },
2122
+ },
2123
+ ],
2124
+ } satisfies AppendMessage;
2125
+
2126
+ expect(getEveMessageContent(message)).toEqual([
2127
+ {
2128
+ type: "file",
2129
+ data: "https://example.com/report.pdf",
2130
+ mediaType: "application/pdf",
2131
+ },
2132
+ ]);
2133
+ });
2134
+
2135
+ it("preserves opaque file references", () => {
2136
+ const message = {
2137
+ ...baseAppendMessage,
2138
+ content: [
2139
+ {
2140
+ type: "file",
2141
+ data: "file_abc123",
2142
+ mimeType: "application/pdf",
2143
+ sourceType: "id",
2144
+ },
2145
+ ],
2146
+ } satisfies AppendMessage;
2147
+
2148
+ expect(getEveMessageContent(message)).toEqual([
2149
+ { type: "file", data: "file_abc123", mediaType: "application/pdf" },
2150
+ ]);
2151
+ });
2152
+
1880
2153
  it("converts an audio part into a file part with the format-derived media type", () => {
1881
2154
  const message = {
1882
2155
  ...baseAppendMessage,
@@ -1927,6 +2200,26 @@ describe("getEveMessageContent", () => {
1927
2200
  ]);
1928
2201
  });
1929
2202
 
2203
+ it("rebuilds a media-less audio data URL from the typed format", () => {
2204
+ const message = {
2205
+ ...baseAppendMessage,
2206
+ content: [
2207
+ {
2208
+ type: "audio",
2209
+ audio: { data: "data:;base64,QUJD", format: "mp3" },
2210
+ },
2211
+ ],
2212
+ } as unknown as AppendMessage;
2213
+
2214
+ expect(getEveMessageContent(message)).toEqual([
2215
+ {
2216
+ type: "file",
2217
+ data: "data:audio/mp3;base64,QUJD",
2218
+ mediaType: "audio/mp3",
2219
+ },
2220
+ ]);
2221
+ });
2222
+
1930
2223
  it("forwards an http audio source instead of wrapping it in a data URL", () => {
1931
2224
  const message = {
1932
2225
  ...baseAppendMessage,
@@ -19,7 +19,10 @@ import {
19
19
  } from "@assistant-ui/core";
20
20
  import {
21
21
  httpUrlPattern,
22
+ resolveFileMediaType,
22
23
  resolveFilePartSource,
24
+ resolveImageMediaType,
25
+ toMediaWireUrl,
23
26
  } from "@assistant-ui/core/internal";
24
27
  import type {
25
28
  EveAuthorizationOutcome,
@@ -286,7 +289,7 @@ const convertDynamicToolPart = (
286
289
  toolCallId: part.toolCallId,
287
290
  toolName: part.toolName,
288
291
  args: toJsonObject(part.input),
289
- argsText: stringifyArgs(part.input),
292
+ argsText: part.state === "input-streaming" ? "" : stringifyArgs(part.input),
290
293
  ...(approval && { approval }),
291
294
  ...(providerMetadata && { providerMetadata }),
292
295
  };
@@ -373,7 +376,7 @@ const convertFilePart = (
373
376
  return {
374
377
  type: "file",
375
378
  data: part.url,
376
- mimeType: part.mediaType ?? "unknown/unknown",
379
+ mimeType: resolveFileMediaType(part.url, part.mediaType),
377
380
  ...(part.filename && { filename: part.filename }),
378
381
  ...(httpUrlPattern.test(part.url) && { sourceType: "url" as const }),
379
382
  };
@@ -621,6 +624,10 @@ export type EveMessageContent =
621
624
  }
622
625
  )[];
623
626
 
627
+ type OutboundPart = AppendMessage["content"][number] & {
628
+ readonly contentType?: string | undefined;
629
+ };
630
+
624
631
  /**
625
632
  * Converts an assistant-ui append message into the message payload accepted by
626
633
  * Eve's `send` API.
@@ -628,9 +635,14 @@ export type EveMessageContent =
628
635
  export const getEveMessageContent = (
629
636
  message: AppendMessage,
630
637
  ): EveMessageContent => {
631
- const content = [
638
+ const content: OutboundPart[] = [
632
639
  ...message.content,
633
- ...(message.attachments?.flatMap((attachment) => attachment.content) ?? []),
640
+ ...(message.attachments?.flatMap((attachment) =>
641
+ attachment.content.map((part) => ({
642
+ ...part,
643
+ contentType: attachment.contentType,
644
+ })),
645
+ ) ?? []),
634
646
  ];
635
647
 
636
648
  const parts = content.flatMap((part) => {
@@ -639,21 +651,31 @@ export const getEveMessageContent = (
639
651
  case "text":
640
652
  return { type: "text" as const, text: part.text };
641
653
 
642
- case "file":
654
+ case "file": {
655
+ const mediaType = resolveFileMediaType(
656
+ part.data,
657
+ part.mimeType || part.contentType,
658
+ );
643
659
  return {
644
660
  type: "file" as const,
645
- data: part.data,
646
- mediaType: part.mimeType,
661
+ data:
662
+ part.sourceType === "id"
663
+ ? part.data
664
+ : toMediaWireUrl(part.data, mediaType),
665
+ mediaType,
647
666
  ...(part.filename && { filename: part.filename }),
648
667
  };
668
+ }
649
669
 
650
- case "image":
670
+ case "image": {
671
+ const mediaType = resolveImageMediaType(part.image, part.contentType);
651
672
  return {
652
673
  type: "file" as const,
653
- data: part.image,
654
- mediaType: "image/*",
674
+ data: toMediaWireUrl(part.image, mediaType),
675
+ mediaType,
655
676
  ...(part.filename && { filename: part.filename }),
656
677
  };
678
+ }
657
679
 
658
680
  case "audio": {
659
681
  // A data URL's own media type wins over `mediaType` downstream, so the
@@ -29,6 +29,7 @@ import {
29
29
  useRemoteThreadListRuntime,
30
30
  useRuntimeAdapters,
31
31
  } from "@assistant-ui/core/react";
32
+ import { invokeUserCallback } from "@assistant-ui/core/internal";
32
33
  import { useReplaySafeEffect } from "@assistant-ui/store/internal";
33
34
  import { useAui } from "@assistant-ui/store";
34
35
  import type { AssistantCloud } from "assistant-cloud";
@@ -73,35 +74,6 @@ const sendAbandonedError = new Error(
73
74
  const isDroppedSend = (error: unknown) =>
74
75
  error === sendCancelledError || error === sendAbandonedError;
75
76
 
76
- type EveLifecycleCallbackName =
77
- | "onError"
78
- | "onEvent"
79
- | "onFinish"
80
- | "onSessionChange";
81
-
82
- const reportEveLifecycleCallbackError = (
83
- name: EveLifecycleCallbackName,
84
- error: unknown,
85
- ) => {
86
- console.error(`[assistant-ui/eve] ${name} callback threw an error`, error);
87
- };
88
-
89
- const invokeEveLifecycleCallback = <T>(
90
- name: EveLifecycleCallbackName,
91
- callback: ((value: T) => unknown) | undefined,
92
- value: T,
93
- ) => {
94
- if (!callback) return;
95
-
96
- try {
97
- void Promise.resolve(callback(value)).catch((error) => {
98
- reportEveLifecycleCallbackError(name, error);
99
- });
100
- } catch (error) {
101
- reportEveLifecycleCallbackError(name, error);
102
- }
103
- };
104
-
105
77
  const hasRunConfig = (
106
78
  runConfig: AppendMessage["runConfig"],
107
79
  ): runConfig is NonNullable<AppendMessage["runConfig"]> =>
@@ -229,18 +201,33 @@ const useEveThreadRuntime = (
229
201
  ...(onError
230
202
  ? {
231
203
  onError: (error) =>
232
- invokeEveLifecycleCallback("onError", onError, error),
204
+ void invokeUserCallback(
205
+ "assistant-ui/eve",
206
+ "onError",
207
+ onError,
208
+ error,
209
+ ),
233
210
  }
234
211
  : {}),
235
212
  ...(onEvent
236
213
  ? {
237
214
  onEvent: (event) =>
238
- invokeEveLifecycleCallback("onEvent", onEvent, event),
215
+ void invokeUserCallback(
216
+ "assistant-ui/eve",
217
+ "onEvent",
218
+ onEvent,
219
+ event,
220
+ ),
239
221
  }
240
222
  : {}),
241
223
  onFinish: (snapshot) => {
242
224
  lastFinishStatusRef.current = snapshot.status;
243
- invokeEveLifecycleCallback("onFinish", onFinish, snapshot);
225
+ void invokeUserCallback(
226
+ "assistant-ui/eve",
227
+ "onFinish",
228
+ onFinish,
229
+ snapshot,
230
+ );
244
231
  },
245
232
  ...(onSessionChange || cloudThread?.isNew
246
233
  ? {
@@ -256,7 +243,8 @@ const useEveThreadRuntime = (
256
243
  if (aui.threadListItem.getState().status === "new")
257
244
  aui.threadListItem.initialize().catch(() => {});
258
245
  }
259
- invokeEveLifecycleCallback(
246
+ void invokeUserCallback(
247
+ "assistant-ui/eve",
260
248
  "onSessionChange",
261
249
  onSessionChange,
262
250
  session,