@assistant-ui/eve 0.0.14 → 0.0.15

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,5 +1,5 @@
1
1
  import { AppendMessage, RespondToToolApprovalOptions, ThreadMessage } from "@assistant-ui/core";
2
- import { EveAuthorizationOutcome, EveAuthorizationPart, EveMessage, EveMessageData } from "eve/react";
2
+ import { EveAuthorizationOutcome, EveAuthorizationPart, EveMessage, EveMessageData, EveMessageInputRequest } from "eve/react";
3
3
  import { InputResponse } from "eve/client";
4
4
  //#region src/convertEveMessages.d.ts
5
5
  type ConvertEveMessagesOptions = {
@@ -66,10 +66,36 @@ type EveMessageContent = string | ({
66
66
  * Eve's `send` API.
67
67
  */
68
68
  declare const getEveMessageContent: (message: AppendMessage) => EveMessageContent;
69
+ /**
70
+ * Finds the pending input request answered by the given approval id, so the
71
+ * response mapping can honor the request's display mode.
72
+ */
73
+ declare const findEveInputRequest: (data: EveMessageData, approvalId: string) => EveMessageInputRequest | undefined;
69
74
  /**
70
75
  * Converts an assistant-ui tool approval response into an Eve input response.
76
+ *
77
+ * When `inputRequest` is known, every returned response carries either an
78
+ * option the request declares or a free-form answer. A literal option match
79
+ * wins, then the `"approve"` / `"cancel"` option the response's boolean decision
80
+ * names, then the response's `reason` text when the request takes a free-form
81
+ * answer (`display: "text"`, `allowFreeform`, or no options at all, and never
82
+ * `display: "confirmation"`) and the response is not a refusal.
83
+ * A one-argument call, or a part with no request, still forwards a caller
84
+ * `optionId` as it always has, including ids the request never declared.
85
+ *
86
+ * A response the known request cannot record as the answer it asked for throws
87
+ * instead of being submitted, because eve resolves a request the moment any
88
+ * response for it arrives, and an empty one is recorded as an answer with no
89
+ * content. Not sending leaves the request pending, so the caller can retry or
90
+ * the user can answer it as an ordinary message.
91
+ *
92
+ * With no request to map against — a one-argument call, or a part carrying no
93
+ * `toolMetadata.eve.inputRequest` — the response maps as it always has, to the
94
+ * literal `"approve"` / `"cancel"` option every eve approval declares. Guessing
95
+ * a display mode is what this mapper stopped doing; an unknown request is not
96
+ * a guess about one.
71
97
  */
72
- declare const toEveInputResponse: (response: RespondToToolApprovalOptions) => InputResponse;
98
+ declare const toEveInputResponse: (response: RespondToToolApprovalOptions, inputRequest?: EveMessageInputRequest) => InputResponse;
73
99
  //#endregion
74
- export { ConvertEveMessagesOptions, EveAuthorizationData, EveMessageContent, convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse };
100
+ export { ConvertEveMessagesOptions, EveAuthorizationData, EveMessageContent, convertEveMessage, convertEveMessages, findEveInputRequest, getEveMessageContent, toEveInputResponse };
75
101
  //# sourceMappingURL=convertEveMessages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"convertEveMessages.d.ts","names":[],"sources":["../src/convertEveMessages.ts"],"mappings":";;;;KAgDY;;;;;;;WAOD;;;;WAIA;WACA,iBAAiB,SAAS,eAAe;;;;;;;;;;;;;KAgLxC;WACD,OAAO;WACP;WACA;WACA;WACA;WACA;WACA;WACA;WACA,UAAU;WACV;;;;;cA6IE,oBACX,SAAS,YACT,eACA,mBAAmB,cACnB,UAAS,8BACR;;;;cAuCU,qBACX,MAAM,gBACN,UAAS,8BACR;;;;;;KAUS;WAGO;WAAuB;;WAEvB;WACA;WACA;WACA;;;;;;cAQN,uBACX,SAAS,kBACR;;;;cAoEU,qBACX,UAAU,iCACT"}
1
+ {"version":3,"file":"convertEveMessages.d.ts","names":[],"sources":["../src/convertEveMessages.ts"],"mappings":";;;;KAoDY;;;;;;;WAOD;;;;WAIA;WACA,iBAAiB,SAAS,eAAe;;;;;;;;;;;;;KAgNxC;WACD,OAAO;WACP;WACA;WACA;WACA;WACA;WACA;WACA;WACA,UAAU;WACV;;;;;cA6IE,oBACX,SAAS,YACT,eACA,mBAAmB,cACnB,UAAS,8BACR;;;;cAuCU,qBACX,MAAM,gBACN,UAAS,8BACR;;;;;;KAUS;WAGO;WAAuB;;WAEvB;WACA;WACA;WACA;;;;;;cAQN,uBACX,SAAS,kBACR;;;;;cAuEU,sBACX,MAAM,gBACN,uBACC;;;;;;;;;;;;;;;;;;;;;;;;;cAmCU,qBACX,UAAU,8BACV,eAAe,2BACd"}
@@ -1,5 +1,5 @@
1
1
  import { fromThreadMessageLike, toAssistantError } from "@assistant-ui/core";
2
- import { httpUrlPattern, parseDataUrl } from "@assistant-ui/core/internal";
2
+ import { httpUrlPattern, resolveFilePartSource } from "@assistant-ui/core/internal";
3
3
  //#region src/convertEveMessages.ts
4
4
  const ASSISTANT_COMPLETE_STATUS = {
5
5
  type: "complete",
@@ -79,15 +79,35 @@ const toApproval = (part) => {
79
79
  ...options && { options }
80
80
  };
81
81
  };
82
+ const toJsonSafeInputRequest = (inputRequest) => ({
83
+ requestId: inputRequest.requestId,
84
+ prompt: inputRequest.prompt,
85
+ kind: inputRequest.kind,
86
+ ...inputRequest.display !== void 0 && { display: inputRequest.display },
87
+ ...inputRequest.allowFreeform !== void 0 && { allowFreeform: inputRequest.allowFreeform },
88
+ ...inputRequest.options && { options: inputRequest.options.map((option) => ({
89
+ id: option.id,
90
+ label: option.label,
91
+ ...option.description !== void 0 && { description: option.description },
92
+ ...option.style !== void 0 && { style: option.style }
93
+ })) }
94
+ });
95
+ const toToolProviderMetadata = (part) => {
96
+ const inputRequest = part.toolMetadata?.eve?.inputRequest;
97
+ if (!inputRequest) return void 0;
98
+ return { eve: { inputRequest: toJsonSafeInputRequest(inputRequest) } };
99
+ };
82
100
  const convertDynamicToolPart = (part) => {
83
101
  const approval = toApproval(part);
102
+ const providerMetadata = toToolProviderMetadata(part);
84
103
  const toolCall = {
85
104
  type: "tool-call",
86
105
  toolCallId: part.toolCallId,
87
106
  toolName: part.toolName,
88
107
  args: toJsonObject(part.input),
89
108
  argsText: stringifyArgs(part.input),
90
- ...approval && { approval }
109
+ ...approval && { approval },
110
+ ...providerMetadata && { providerMetadata }
91
111
  };
92
112
  switch (part.state) {
93
113
  case "output-available": return {
@@ -247,9 +267,13 @@ const getEveMessageContent = (message) => {
247
267
  case "audio": {
248
268
  const mediaType = `audio/${part.audio.format}`;
249
269
  const data = part.audio.data;
270
+ const source = resolveFilePartSource({
271
+ data,
272
+ mimeType: mediaType
273
+ });
250
274
  return {
251
275
  type: "file",
252
- data: httpUrlPattern.test(data) ? data : `data:${mediaType};base64,${parseDataUrl(data)?.data ?? data}`,
276
+ data: source.kind === "url" ? source.url : `data:${mediaType};base64,${source.data}`,
253
277
  mediaType
254
278
  };
255
279
  }
@@ -261,14 +285,62 @@ const getEveMessageContent = (message) => {
261
285
  return parts;
262
286
  };
263
287
  /**
288
+ * Finds the pending input request answered by the given approval id, so the
289
+ * response mapping can honor the request's display mode.
290
+ */
291
+ const findEveInputRequest = (data, approvalId) => {
292
+ for (const message of data.messages) for (const part of message.parts) if (part.type === "dynamic-tool" && part.approval?.id === approvalId) return part.toolMetadata?.eve?.inputRequest;
293
+ };
294
+ /**
264
295
  * Converts an assistant-ui tool approval response into an Eve input response.
296
+ *
297
+ * When `inputRequest` is known, every returned response carries either an
298
+ * option the request declares or a free-form answer. A literal option match
299
+ * wins, then the `"approve"` / `"cancel"` option the response's boolean decision
300
+ * names, then the response's `reason` text when the request takes a free-form
301
+ * answer (`display: "text"`, `allowFreeform`, or no options at all, and never
302
+ * `display: "confirmation"`) and the response is not a refusal.
303
+ * A one-argument call, or a part with no request, still forwards a caller
304
+ * `optionId` as it always has, including ids the request never declared.
305
+ *
306
+ * A response the known request cannot record as the answer it asked for throws
307
+ * instead of being submitted, because eve resolves a request the moment any
308
+ * response for it arrives, and an empty one is recorded as an answer with no
309
+ * content. Not sending leaves the request pending, so the caller can retry or
310
+ * the user can answer it as an ordinary message.
311
+ *
312
+ * With no request to map against — a one-argument call, or a part carrying no
313
+ * `toolMetadata.eve.inputRequest` — the response maps as it always has, to the
314
+ * literal `"approve"` / `"cancel"` option every eve approval declares. Guessing
315
+ * a display mode is what this mapper stopped doing; an unknown request is not
316
+ * a guess about one.
265
317
  */
266
- const toEveInputResponse = (response) => ({
267
- requestId: response.approvalId,
268
- optionId: response.optionId ?? (response.approved ? "approve" : "cancel"),
269
- ...response.reason && { text: response.reason }
270
- });
318
+ const toEveInputResponse = (response, inputRequest) => {
319
+ const requestId = response.approvalId;
320
+ const options = inputRequest?.options;
321
+ const text = response.reason;
322
+ if (response.optionId !== void 0) {
323
+ if (!inputRequest || options?.some((option) => option.id === response.optionId)) return {
324
+ requestId,
325
+ optionId: response.optionId,
326
+ ...text && { text }
327
+ };
328
+ throw new Error(`Eve input request "${requestId}" has no option with id "${response.optionId}"; respond with one of: ${options?.length ? options.map((option) => option.id).join(", ") : "(the request carries no options)"}`);
329
+ }
330
+ const decisionOptionId = response.approved ? "approve" : "cancel";
331
+ if (!inputRequest || inputRequest.kind === "tool-approval" && !options?.length || options?.some((option) => option.id === decisionOptionId)) return {
332
+ requestId,
333
+ optionId: decisionOptionId,
334
+ ...text && { text }
335
+ };
336
+ const acceptsText = inputRequest.display !== "confirmation" && (inputRequest.display === "text" || inputRequest.allowFreeform === true || !options?.length);
337
+ if (acceptsText && text && response.approved !== false) return {
338
+ requestId,
339
+ text
340
+ };
341
+ throw new Error(`Eve input request "${requestId}" (${inputRequest.prompt}) was not answered by this response; ${options?.length ? `respond with one of: ${options.map((option) => option.id).join(", ")}` : !acceptsText ? "the request declares no options to respond with" : response.approved === false ? "a refusal carries no answer for a free-form request; resend with `approved` unset or true and the answer in the response reason" : "pass the answer as the response reason, because the request takes a free-form answer"}`);
342
+ };
271
343
  //#endregion
272
- export { convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse };
344
+ export { convertEveMessage, convertEveMessages, findEveInputRequest, getEveMessageContent, toEveInputResponse };
273
345
 
274
346
  //# sourceMappingURL=convertEveMessages.js.map
@@ -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 RespondToToolApprovalOptions,\n type ThreadAssistantMessagePart,\n type ThreadMessage,\n type ThreadMessageLike,\n type ThreadUserMessagePart,\n type ToolApprovalOption,\n type ToolCallMessagePart,\n} from \"@assistant-ui/core\";\nimport { httpUrlPattern, parseDataUrl } from \"@assistant-ui/core/internal\";\nimport type {\n EveAuthorizationOutcome,\n EveAuthorizationPart,\n EveDynamicToolPart,\n EveMessage,\n EveMessageData,\n EveMessageInputRequest,\n EveMessagePart,\n} from \"eve/react\";\nimport type { InputResponse } 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\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst toJsonObject = (value: unknown): ToolCallMessagePart[\"args\"] => {\n if (isRecord(value)) return value as ToolCallMessagePart[\"args\"];\n if (value === undefined) return {};\n return { value } as ToolCallMessagePart[\"args\"];\n};\n\nconst stringifyArgs = (value: unknown): string => {\n try {\n return JSON.stringify(value ?? {});\n } catch {\n return \"\";\n }\n};\n\nconst toMessageStatus = (\n message: EveMessage,\n index: number,\n messages: readonly EveMessage[],\n options: ConvertEveMessagesOptions,\n): MessageStatus => {\n if (message.role !== \"assistant\") return USER_FALLBACK_STATUS;\n\n const 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 // Eve's default reducer never terminalizes the \"streaming\" marker on\n // cancellation or turn/session failure, so liveness comes from isRunning and\n // a leftover marker means the turn was interrupted.\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\nconst toApproval = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart[\"approval\"] | undefined => {\n if (\n part.state !== \"approval-requested\" &&\n part.state !== \"approval-responded\" &&\n part.state !== \"output-available\" &&\n part.state !== \"output-error\" &&\n part.state !== \"output-denied\"\n ) {\n return undefined;\n }\n\n const approval = part.approval;\n if (!approval) return undefined;\n const options = toolApprovalOptionsFromInputRequest(\n part.toolMetadata?.eve?.inputRequest,\n );\n\n return {\n id: approval.id,\n ...(approval.approved !== undefined && { approved: approval.approved }),\n ...(approval.reason && { reason: approval.reason }),\n ...(approval.isAutomatic !== undefined && {\n isAutomatic: approval.isAutomatic,\n }),\n ...(options && { options }),\n };\n};\n\nconst convertDynamicToolPart = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart => {\n const approval = toApproval(part);\n const toolCall: ToolCallMessagePart = {\n type: \"tool-call\",\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n args: toJsonObject(part.input),\n argsText: stringifyArgs(part.input),\n ...(approval && { approval }),\n };\n\n switch (part.state) {\n case \"output-available\":\n return { ...toolCall, result: part.output };\n\n case \"output-error\":\n return {\n ...toolCall,\n result: { error: part.errorText },\n isError: true,\n };\n\n case \"output-denied\":\n return {\n ...toolCall,\n result: { error: part.approval?.reason ?? \"Tool approval denied\" },\n isError: true,\n };\n\n default:\n return toolCall;\n }\n};\n\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 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),\n );\n};\n\n/**\n * Converts the full Eve message data object into assistant-ui thread messages.\n */\nexport const convertEveMessages = (\n data: EveMessageData,\n options: ConvertEveMessagesOptions = {},\n): ThreadMessage[] =>\n data.messages.map((message, index, messages) =>\n convertEveMessage(message, index, messages, options),\n );\n\n/**\n * 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 return {\n type: \"file\" as const,\n data: httpUrlPattern.test(data)\n ? data\n : `data:${mediaType};base64,${parseDataUrl(data)?.data ?? data}`,\n mediaType,\n };\n }\n\n case \"data\":\n return [];\n\n default: {\n const _exhaustiveCheck:\n | \"generative-ui\"\n | \"reasoning\"\n | \"source\"\n | \"tool-call\" = type;\n throw new Error(\n `Unsupported eve append message part type: ${_exhaustiveCheck}`,\n );\n }\n }\n });\n\n if (parts.length === 1 && parts[0]?.type === \"text\") {\n return parts[0].text;\n }\n\n return parts;\n};\n\n/**\n * Converts an assistant-ui tool approval response into an Eve input response.\n */\nexport const toEveInputResponse = (\n response: RespondToToolApprovalOptions,\n): InputResponse => ({\n requestId: response.approvalId,\n optionId: response.optionId ?? (response.approved ? \"approve\" : \"cancel\"),\n ...(response.reason && { text: response.reason }),\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"convertEveMessages.js","names":["_exhaustiveCheck"],"sources":["../src/convertEveMessages.ts"],"sourcesContent":["import {\n fromThreadMessageLike,\n 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 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\";\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\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst toJsonObject = (value: unknown): ToolCallMessagePart[\"args\"] => {\n if (isRecord(value)) return value as ToolCallMessagePart[\"args\"];\n if (value === undefined) return {};\n return { value } as ToolCallMessagePart[\"args\"];\n};\n\nconst stringifyArgs = (value: unknown): string => {\n try {\n return JSON.stringify(value ?? {});\n } catch {\n return \"\";\n }\n};\n\nconst toMessageStatus = (\n message: EveMessage,\n index: number,\n messages: readonly EveMessage[],\n options: ConvertEveMessagesOptions,\n): MessageStatus => {\n if (message.role !== \"assistant\") return USER_FALLBACK_STATUS;\n\n const 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 // Eve's default reducer never terminalizes the \"streaming\" marker on\n // cancellation or turn/session failure, so liveness comes from isRunning and\n // a leftover marker means the turn was interrupted.\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\nconst toApproval = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart[\"approval\"] | undefined => {\n if (\n part.state !== \"approval-requested\" &&\n part.state !== \"approval-responded\" &&\n part.state !== \"output-available\" &&\n part.state !== \"output-error\" &&\n part.state !== \"output-denied\"\n ) {\n return undefined;\n }\n\n const approval = part.approval;\n if (!approval) return undefined;\n const options = toolApprovalOptionsFromInputRequest(\n part.toolMetadata?.eve?.inputRequest,\n );\n\n return {\n id: approval.id,\n ...(approval.approved !== undefined && { approved: approval.approved }),\n ...(approval.reason && { reason: approval.reason }),\n ...(approval.isAutomatic !== undefined && {\n isAutomatic: approval.isAutomatic,\n }),\n ...(options && { options }),\n };\n};\n\nconst 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 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),\n );\n};\n\n/**\n * Converts the full Eve message data object into assistant-ui thread messages.\n */\nexport const convertEveMessages = (\n data: EveMessageData,\n options: ConvertEveMessagesOptions = {},\n): ThreadMessage[] =>\n data.messages.map((message, index, messages) =>\n convertEveMessage(message, index, messages, options),\n );\n\n/**\n * 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 `reason` text 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.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 =\n inputRequest.display !== \"confirmation\" &&\n (inputRequest.display === \"text\" ||\n inputRequest.allowFreeform === true ||\n !options?.length);\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 reason\"\n : \"pass the answer as the response reason, because the request takes a free-form answer\"\n }`,\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useEveAgentRuntime.d.ts","names":[],"sources":["../src/useEveAgentRuntime.ts"],"mappings":";;;KAmHY,4BAA4B,KACtC,mBAAmB,8BAGnB;WACW;aAEM,cAAc;aACd,SAAS;aACT,YAAY;aACZ,QAAQ;aACR,WAAW;;;;;;;;;;cAYjB,qBAAsB,UAAS,2DAA8B"}
1
+ {"version":3,"file":"useEveAgentRuntime.d.ts","names":[],"sources":["../src/useEveAgentRuntime.ts"],"mappings":";;;KAoHY,4BAA4B,KACtC,mBAAmB,8BAGnB;WACW;aAEM,cAAc;aACd,SAAS;aACT,YAAY;aACZ,QAAQ;aACR,WAAW;;;;;;;;;;cAYjB,qBAAsB,UAAS,2DAA8B"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { convertEveMessages, getEveMessageContent, toEveInputResponse } from "./convertEveMessages.js";
2
+ import { convertEveMessages, findEveInputRequest, getEveMessageContent, toEveInputResponse } from "./convertEveMessages.js";
3
3
  import { eveExtras } from "./eveExtras.js";
4
4
  import { MessageNotSentError, fromThreadMessageLike, generateId, pickExternalStoreSharedOptions } from "@assistant-ui/core";
5
5
  import { useExternalStoreRuntime, useRuntimeAdapters } from "@assistant-ui/core/react";
@@ -61,7 +61,8 @@ const useEveAgentRuntime = (options = {}) => {
61
61
  const createdAtByMessageIdRef = useRef(/* @__PURE__ */ new Map());
62
62
  const stagedInputsRef = useRef(/* @__PURE__ */ new Map());
63
63
  const hasExecutingTools = Object.values(toolStatuses).some((status) => status?.type === "executing");
64
- const isRunning = agent.status === "submitted" || agent.status === "streaming" || hasExecutingTools;
64
+ const providerIsRunning = agent.status === "submitted" || agent.status === "streaming";
65
+ const isRunning = providerIsRunning || hasExecutingTools;
65
66
  const convertedMessages = useMemo(() => {
66
67
  const createdAtByMessageId = createdAtByMessageIdRef.current;
67
68
  const messageIds = new Set(agent.data.messages.map((message) => message.id));
@@ -85,15 +86,18 @@ const useEveAgentRuntime = (options = {}) => {
85
86
  const messages = stagedMessages ?? convertedMessages;
86
87
  const messagesRef = useRef(messages);
87
88
  messagesRef.current = messages;
89
+ const agentRef = useRef(agent);
90
+ agentRef.current = agent;
88
91
  const sendChainRef = useRef(Promise.resolve());
89
92
  const sendEpochRef = useRef(0);
90
93
  const resetEpochRef = useRef(0);
91
94
  const isMountedRef = useRef(true);
92
95
  const runtimeRef = useRef(null);
93
- const enqueueSend = (dispatch) => {
94
- const epoch = sendEpochRef.current;
96
+ const enqueueSend = (dispatch, epochRef = sendEpochRef) => {
97
+ const epoch = epochRef.current;
95
98
  const next = sendChainRef.current.then(() => {
96
- if (epoch !== sendEpochRef.current) throw isMountedRef.current ? sendCancelledError : sendAbandonedError;
99
+ if (!isMountedRef.current) throw sendAbandonedError;
100
+ if (epoch !== epochRef.current) throw sendCancelledError;
97
101
  return dispatch();
98
102
  });
99
103
  sendChainRef.current = next.catch(() => {});
@@ -159,7 +163,7 @@ const useEveAgentRuntime = (options = {}) => {
159
163
  const runtime = useExternalStoreRuntime({
160
164
  ...pickExternalStoreSharedOptions(options),
161
165
  messages,
162
- isRunning,
166
+ isRunning: providerIsRunning,
163
167
  extras,
164
168
  unstable_enableToolInvocations: true,
165
169
  setToolStatuses,
@@ -208,17 +212,25 @@ const useEveAgentRuntime = (options = {}) => {
208
212
  if (lastFinishStatusRef.current === "error") return;
209
213
  }
210
214
  } } : {},
211
- onCancel: () => {
215
+ onCancel: async () => {
212
216
  sendEpochRef.current += 1;
213
- agent.stop();
214
- return Promise.resolve();
217
+ const controls = agent;
218
+ if ("cancel" in controls) await controls.cancel();
219
+ else controls.stop();
215
220
  },
216
- onRespondToToolApproval: async (response) => {
217
- try {
218
- await enqueueSend(() => agent.respond([toEveInputResponse(response)]));
219
- } catch (error) {
221
+ ..."resume" in agent ? { onRefetchThread: () => enqueueSend(async () => {
222
+ const live = agentRef.current;
223
+ if (live.session === void 0) return;
224
+ await live.resume();
225
+ }, resetEpochRef).catch((error) => {
226
+ if (isDroppedSend(error)) return;
227
+ throw error;
228
+ }) } : {},
229
+ onRespondToToolApproval: (response) => {
230
+ const inputResponse = toEveInputResponse(response, findEveInputRequest(agent.data, response.approvalId));
231
+ return enqueueSend(() => agent.respond([inputResponse])).catch((error) => {
220
232
  if (!isDroppedSend(error)) throw error;
221
- }
233
+ });
222
234
  }
223
235
  });
224
236
  runtimeRef.current = runtime;
@@ -1 +1 @@
1
- {"version":3,"file":"useEveAgentRuntime.js","names":[],"sources":["../src/useEveAgentRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } 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 useExternalStoreRuntime,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport {\n useEveAgent,\n type EveMessageData,\n type UseEveAgentOptions,\n type UseEveAgentStatus,\n} from \"eve/react\";\nimport {\n convertEveMessages,\n getEveMessageContent,\n toEveInputResponse,\n} from \"./convertEveMessages\";\nimport { eveExtras } from \"./eveExtras\";\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\nexport type UseEveAgentRuntimeOptions = Omit<\n UseEveAgentOptions<EveMessageData>,\n \"reducer\"\n> &\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\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 {\n adapters,\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 { onError, onEvent, onFinish, onSessionChange } = agentOptions;\n const lastFinishStatusRef = useRef<UseEveAgentStatus | null>(null);\n const agent = useEveAgent({\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\n ? {\n onSessionChange: (session) =>\n invokeEveLifecycleCallback(\n \"onSessionChange\",\n onSessionChange,\n session,\n ),\n }\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 isRunning =\n agent.status === \"submitted\" ||\n agent.status === \"streaming\" ||\n hasExecutingTools;\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 getCreatedAt: (message) => {\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, isRunning]);\n\n const messages = stagedMessages ?? convertedMessages;\n const messagesRef = useRef(messages);\n messagesRef.current = 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.\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 = (dispatch: () => Promise<void>) => {\n const epoch = sendEpochRef.current;\n const next = sendChainRef.current.then(() => {\n if (epoch !== sendEpochRef.current)\n throw isMountedRef.current ? sendCancelledError : sendAbandonedError;\n return dispatch();\n });\n sendChainRef.current = next.catch(() => {});\n return next;\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 sendEpochRef.current += 1;\n };\n }, []);\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 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]);\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,\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 (!(message.startRun ?? message.role === \"user\")) {\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: () => {\n sendEpochRef.current += 1;\n agent.stop();\n return Promise.resolve();\n },\n onRespondToToolApproval: async (response) => {\n try {\n await enqueueSend(() => agent.respond([toEveInputResponse(response)]));\n } catch (error) {\n if (!isDroppedSend(error)) throw error;\n }\n },\n });\n runtimeRef.current = runtime;\n\n return runtime;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useEveAgentRuntime.js","names":[],"sources":["../src/useEveAgentRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } 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 useExternalStoreRuntime,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport {\n useEveAgent,\n type EveMessageData,\n type UseEveAgentOptions,\n type UseEveAgentStatus,\n} from \"eve/react\";\nimport {\n convertEveMessages,\n findEveInputRequest,\n getEveMessageContent,\n toEveInputResponse,\n} from \"./convertEveMessages\";\nimport { eveExtras } from \"./eveExtras\";\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\nexport type UseEveAgentRuntimeOptions = Omit<\n UseEveAgentOptions<EveMessageData>,\n \"reducer\"\n> &\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\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 {\n adapters,\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 { onError, onEvent, onFinish, onSessionChange } = agentOptions;\n const lastFinishStatusRef = useRef<UseEveAgentStatus | null>(null);\n const agent = useEveAgent({\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\n ? {\n onSessionChange: (session) =>\n invokeEveLifecycleCallback(\n \"onSessionChange\",\n onSessionChange,\n session,\n ),\n }\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 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 getCreatedAt: (message) => {\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, isRunning]);\n\n const messages = stagedMessages ?? convertedMessages;\n const messagesRef = useRef(messages);\n messagesRef.current = messages;\n const agentRef = useRef(agent);\n agentRef.current = agent;\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 = (\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 // 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 sendEpochRef.current += 1;\n };\n }, []);\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 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]);\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 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 (!(message.startRun ?? message.role === \"user\")) {\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 // `resume()` rejects during a turn, so the replay rides the send\n // chain like every other dispatch and runs once the turn parks; a\n // cancel of that turn does not drop it, or the caller would be told\n // the thread was refetched when it was not. Upstream shares one\n // replay across concurrent `resume()` calls, so a refetch\n // dispatched while `resume: true` is replaying on mount joins that\n // read instead of taking a fresh one. The session is read at\n // dispatch time because nothing durable exists before the first\n // send lands.\n enqueueSend(async () => {\n const live = agentRef.current;\n if (live.session === undefined) return;\n await live.resume();\n }, resetEpochRef).catch((error) => {\n if (isDroppedSend(error)) return;\n throw error;\n }),\n }\n : {}),\n onRespondToToolApproval: (response) => {\n // Eve leaves an unanswered request pending, so an unmappable response\n // must stay answerable. Mapping before the first await lets the mapper's\n // own error surface synchronously to the caller that rendered the\n // controls, the only signal the void `respondToApproval` seam carries.\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"],"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.14",
3
+ "version": "0.0.15",
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.14",
32
- "@assistant-ui/store": "^0.3.10"
31
+ "@assistant-ui/core": "^0.3.16",
32
+ "@assistant-ui/store": "^0.3.11"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@types/react": "*",
@@ -42,15 +42,15 @@
42
42
  }
43
43
  },
44
44
  "devDependencies": {
45
+ "@assistant-ui/x-buildutils": "0.0.24",
45
46
  "@testing-library/dom": "^10.4.1",
46
47
  "@testing-library/react": "^16.3.2",
47
48
  "@types/react": "^19.2.18",
48
- "eve": "^0.37.0",
49
+ "eve": "^0.44.4",
49
50
  "jsdom": "^30.0.1",
50
51
  "react": "^19.2.8",
51
52
  "react-dom": "^19.2.8",
52
- "vitest": "^4.1.10",
53
- "@assistant-ui/x-buildutils": "0.0.23"
53
+ "vitest": "^4.1.11"
54
54
  },
55
55
  "publishConfig": {
56
56
  "access": "public",