@assistant-ui/eve 0.0.13 → 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.
- package/dist/convertEveMessages.d.ts +45 -5
- package/dist/convertEveMessages.d.ts.map +1 -1
- package/dist/convertEveMessages.js +91 -14
- package/dist/convertEveMessages.js.map +1 -1
- package/dist/eveExtras.d.ts +7 -0
- package/dist/eveExtras.d.ts.map +1 -0
- package/dist/eveExtras.js +7 -0
- package/dist/eveExtras.js.map +1 -0
- package/dist/hooks.d.ts +25 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +32 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/useEveAgentRuntime.d.ts.map +1 -1
- package/dist/useEveAgentRuntime.js +61 -26
- package/dist/useEveAgentRuntime.js.map +1 -1
- package/package.json +7 -6
- package/src/convertEveMessages.test.ts +698 -6
- package/src/convertEveMessages.ts +185 -14
- package/src/eveExtras.ts +10 -0
- package/src/hooks.test.tsx +84 -0
- package/src/hooks.ts +36 -0
- package/src/index.ts +3 -0
- package/src/useEveAgentRuntime.refetch.test.tsx +89 -0
- package/src/useEveAgentRuntime.test.tsx +1037 -167
- package/src/useEveAgentRuntime.ts +148 -42
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppendMessage, RespondToToolApprovalOptions, ThreadMessage } from "@assistant-ui/core";
|
|
2
|
-
import { EveAuthorizationOutcome, EveAuthorizationPart, EveMessage, EveMessageData } from "eve/react";
|
|
3
|
-
import { InputResponse
|
|
2
|
+
import { EveAuthorizationOutcome, EveAuthorizationPart, EveMessage, EveMessageData, EveMessageInputRequest } from "eve/react";
|
|
3
|
+
import { InputResponse } from "eve/client";
|
|
4
4
|
//#region src/convertEveMessages.d.ts
|
|
5
5
|
type ConvertEveMessagesOptions = {
|
|
6
6
|
/**
|
|
@@ -47,15 +47,55 @@ declare const convertEveMessage: (message: EveMessage, index: number, messages:
|
|
|
47
47
|
* Converts the full Eve message data object into assistant-ui thread messages.
|
|
48
48
|
*/
|
|
49
49
|
declare const convertEveMessages: (data: EveMessageData, options?: ConvertEveMessagesOptions) => ThreadMessage[];
|
|
50
|
+
/**
|
|
51
|
+
* Structural subset of the `string | UserContent` message content Eve's `send`
|
|
52
|
+
* API accepts. The helpers declare the shapes they produce and leave
|
|
53
|
+
* assignability to the send call site, checked against the installed version.
|
|
54
|
+
*/
|
|
55
|
+
type EveMessageContent = string | ({
|
|
56
|
+
readonly type: "text";
|
|
57
|
+
readonly text: string;
|
|
58
|
+
} | {
|
|
59
|
+
readonly type: "file";
|
|
60
|
+
readonly data: string;
|
|
61
|
+
readonly mediaType: string;
|
|
62
|
+
readonly filename?: string;
|
|
63
|
+
})[];
|
|
50
64
|
/**
|
|
51
65
|
* Converts an assistant-ui append message into the message payload accepted by
|
|
52
66
|
* Eve's `send` API.
|
|
53
67
|
*/
|
|
54
|
-
declare const getEveMessageContent: (message: AppendMessage) =>
|
|
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;
|
|
55
74
|
/**
|
|
56
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.
|
|
57
97
|
*/
|
|
58
|
-
declare const toEveInputResponse: (response: RespondToToolApprovalOptions) => InputResponse;
|
|
98
|
+
declare const toEveInputResponse: (response: RespondToToolApprovalOptions, inputRequest?: EveMessageInputRequest) => InputResponse;
|
|
59
99
|
//#endregion
|
|
60
|
-
export { ConvertEveMessagesOptions, EveAuthorizationData, convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse };
|
|
100
|
+
export { ConvertEveMessagesOptions, EveAuthorizationData, EveMessageContent, convertEveMessage, convertEveMessages, findEveInputRequest, getEveMessageContent, toEveInputResponse };
|
|
61
101
|
//# sourceMappingURL=convertEveMessages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convertEveMessages.d.ts","names":[],"sources":["../src/convertEveMessages.ts"],"mappings":";;;;
|
|
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,
|
|
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",
|
|
@@ -61,7 +61,7 @@ const toolApprovalOptionsFromInputRequest = (inputRequest) => {
|
|
|
61
61
|
if (!options || options.length === 0) return void 0;
|
|
62
62
|
return options.map((option) => ({
|
|
63
63
|
id: option.id,
|
|
64
|
-
kind: option.id === "approve" ? "allow-once" : option.id === "
|
|
64
|
+
kind: option.id === "approve" ? "allow-once" : option.id === "cancel" ? "reject-once" : `_${option.id}`,
|
|
65
65
|
...option.label && { label: option.label },
|
|
66
66
|
...option.description && { description: option.description }
|
|
67
67
|
}));
|
|
@@ -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 {
|
|
@@ -133,13 +153,18 @@ const convertFilePart = (part) => {
|
|
|
133
153
|
...httpUrlPattern.test(part.url) && { sourceType: "url" }
|
|
134
154
|
};
|
|
135
155
|
};
|
|
156
|
+
const partStateToStatus = (state) => state === "done" ? { type: "complete" } : void 0;
|
|
136
157
|
const convertAssistantPart = (part) => {
|
|
137
158
|
switch (part.type) {
|
|
138
159
|
case "text":
|
|
139
|
-
case "reasoning":
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
160
|
+
case "reasoning": {
|
|
161
|
+
const status = partStateToStatus(part.state);
|
|
162
|
+
return {
|
|
163
|
+
type: part.type,
|
|
164
|
+
text: part.text,
|
|
165
|
+
...status && { status }
|
|
166
|
+
};
|
|
167
|
+
}
|
|
143
168
|
case "step-start": return null;
|
|
144
169
|
case "dynamic-tool": return convertDynamicToolPart(part);
|
|
145
170
|
case "authorization": return convertAuthorizationPart(part);
|
|
@@ -242,9 +267,13 @@ const getEveMessageContent = (message) => {
|
|
|
242
267
|
case "audio": {
|
|
243
268
|
const mediaType = `audio/${part.audio.format}`;
|
|
244
269
|
const data = part.audio.data;
|
|
270
|
+
const source = resolveFilePartSource({
|
|
271
|
+
data,
|
|
272
|
+
mimeType: mediaType
|
|
273
|
+
});
|
|
245
274
|
return {
|
|
246
275
|
type: "file",
|
|
247
|
-
data:
|
|
276
|
+
data: source.kind === "url" ? source.url : `data:${mediaType};base64,${source.data}`,
|
|
248
277
|
mediaType
|
|
249
278
|
};
|
|
250
279
|
}
|
|
@@ -256,14 +285,62 @@ const getEveMessageContent = (message) => {
|
|
|
256
285
|
return parts;
|
|
257
286
|
};
|
|
258
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
|
+
/**
|
|
259
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.
|
|
260
317
|
*/
|
|
261
|
-
const toEveInputResponse = (response) =>
|
|
262
|
-
requestId
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
+
};
|
|
266
343
|
//#endregion
|
|
267
|
-
export { convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse };
|
|
344
|
+
export { convertEveMessage, convertEveMessages, findEveInputRequest, getEveMessageContent, toEveInputResponse };
|
|
268
345
|
|
|
269
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 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, SendTurnPayload } from \"eve/client\";\n\nconst ASSISTANT_COMPLETE_STATUS = {\n type: \"complete\",\n reason: \"stop\",\n} satisfies MessageStatus;\n\nconst ASSISTANT_RUNNING_STATUS = {\n type: \"running\",\n} satisfies MessageStatus;\n\nconst 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 === \"deny\"\n ? \"reject-once\"\n : `_${option.id}`,\n ...(option.label && { label: option.label }),\n ...(option.description && { description: option.description }),\n }));\n};\n\nconst toApproval = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart[\"approval\"] | undefined => {\n if (\n part.state !== \"approval-requested\" &&\n part.state !== \"approval-responded\" &&\n part.state !== \"output-available\" &&\n part.state !== \"output-error\" &&\n part.state !== \"output-denied\"\n ) {\n return undefined;\n }\n\n const approval = part.approval;\n if (!approval) return undefined;\n const options = toolApprovalOptionsFromInputRequest(\n part.toolMetadata?.eve?.inputRequest,\n );\n\n return {\n id: approval.id,\n ...(approval.approved !== undefined && { approved: approval.approved }),\n ...(approval.reason && { reason: approval.reason }),\n ...(approval.isAutomatic !== undefined && {\n isAutomatic: approval.isAutomatic,\n }),\n ...(options && { options }),\n };\n};\n\nconst convertDynamicToolPart = (\n part: EveDynamicToolPart,\n): ToolCallMessagePart => {\n const approval = toApproval(part);\n const toolCall: ToolCallMessagePart = {\n type: \"tool-call\",\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n args: toJsonObject(part.input),\n argsText: stringifyArgs(part.input),\n ...(approval && { approval }),\n };\n\n switch (part.state) {\n case \"output-available\":\n return { ...toolCall, result: part.output };\n\n case \"output-error\":\n return {\n ...toolCall,\n result: { error: part.errorText },\n isError: true,\n };\n\n case \"output-denied\":\n return {\n ...toolCall,\n result: { error: part.approval?.reason ?? \"Tool approval denied\" },\n isError: true,\n };\n\n default:\n return toolCall;\n }\n};\n\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\nconst convertAssistantPart = (\n part: EveMessagePart,\n): ThreadAssistantMessagePart | null => {\n switch (part.type) {\n case \"text\":\n case \"reasoning\":\n return { type: part.type, text: part.text };\n\n case \"step-start\":\n return null;\n\n case \"dynamic-tool\":\n return convertDynamicToolPart(part);\n\n 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 * Converts an assistant-ui append message into the message payload accepted by\n * Eve's `send` API.\n */\nexport const getEveMessageContent = (\n message: AppendMessage,\n): NonNullable<SendTurnPayload[\"message\"]> => {\n const content = [\n ...message.content,\n ...(message.attachments?.flatMap((attachment) => attachment.content) ?? []),\n ];\n\n const parts = content.flatMap((part) => {\n const type = part.type;\n switch (type) {\n case \"text\":\n return { type: \"text\" as const, text: part.text };\n\n case \"file\":\n return {\n type: \"file\" as const,\n data: part.data,\n mediaType: part.mimeType,\n ...(part.filename && { filename: part.filename }),\n };\n\n case \"image\":\n return {\n type: \"file\" as const,\n data: part.image,\n mediaType: \"image/*\",\n ...(part.filename && { filename: part.filename }),\n };\n\n case \"audio\": {\n // A data URL's own media type wins over `mediaType` downstream, so the\n // envelope is rebuilt from the typed format rather than forwarded.\n const mediaType = `audio/${part.audio.format}`;\n const data = part.audio.data;\n return {\n type: \"file\" as const,\n data: httpUrlPattern.test(data)\n ? data\n : `data:${mediaType};base64,${parseDataUrl(data)?.data ?? data}`,\n mediaType,\n };\n }\n\n case \"data\":\n return [];\n\n default: {\n const _exhaustiveCheck:\n | \"generative-ui\"\n | \"reasoning\"\n | \"source\"\n | \"tool-call\" = type;\n throw new Error(\n `Unsupported eve append message part type: ${_exhaustiveCheck}`,\n );\n }\n }\n });\n\n if (parts.length === 1 && parts[0]?.type === \"text\") {\n return parts[0].text;\n }\n\n return parts;\n};\n\n/**\n * Converts an assistant-ui tool approval response into an Eve input response.\n */\nexport const toEveInputResponse = (\n response: RespondToToolApprovalOptions,\n): InputResponse => ({\n requestId: response.approvalId,\n optionId: response.optionId ?? (response.approved ? \"approve\" : \"deny\"),\n ...(response.reason && { text: response.reason }),\n});\n"],"mappings":"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"}
|
|
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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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { EveMessageData, UseEveAgentHelpers } from "eve/react";
|
|
2
|
+
//#region src/eveExtras.d.ts
|
|
3
|
+
type EveRuntimeExtras = Pick<UseEveAgentHelpers<EveMessageData>, "error" | "events" | "session" | "reset">;
|
|
4
|
+
declare const eveExtras: import("@assistant-ui/core/react").RuntimeExtras<EveRuntimeExtras>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { EveRuntimeExtras, eveExtras };
|
|
7
|
+
//# sourceMappingURL=eveExtras.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eveExtras.d.ts","names":[],"sources":["../src/eveExtras.ts"],"mappings":";;KAGY,mBAAmB,KAC7B,mBAAmB;cAIR,8CAAS,cAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eveExtras.js","names":[],"sources":["../src/eveExtras.ts"],"sourcesContent":["import { createRuntimeExtras } from \"@assistant-ui/core/react\";\nimport type { EveMessageData, UseEveAgentHelpers } from \"eve/react\";\n\nexport type EveRuntimeExtras = Pick<\n UseEveAgentHelpers<EveMessageData>,\n \"error\" | \"events\" | \"session\" | \"reset\"\n>;\n\nexport const eveExtras =\n createRuntimeExtras<EveRuntimeExtras>(\"useEveAgentRuntime\");\n"],"mappings":";;AAQA,MAAa,YACX,oBAAsC,oBAAoB"}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { EveRuntimeExtras } from "./eveExtras.js";
|
|
2
|
+
//#region src/hooks.d.ts
|
|
3
|
+
/** Read the last Eve session error from the runtime extras. */
|
|
4
|
+
declare const useEveError: () => Error | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Read the current Eve session cursor from the runtime extras. Persist it to
|
|
7
|
+
* resume the session later via `initialSession`. `undefined` when no session
|
|
8
|
+
* exists yet or outside an Eve runtime.
|
|
9
|
+
*/
|
|
10
|
+
declare const useEveSession: () => EveRuntimeExtras["session"] | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Read the authoritative Eve server event stream from the runtime extras.
|
|
13
|
+
* Defaults to an empty array outside an Eve runtime.
|
|
14
|
+
*/
|
|
15
|
+
declare const useEveEvents: () => EveRuntimeExtras["events"];
|
|
16
|
+
/**
|
|
17
|
+
* Returns a function that resets the Eve session: aborts any in-flight turn,
|
|
18
|
+
* recreates the owned session, and clears events and projected data. Pending
|
|
19
|
+
* staged messages and tool execution state are discarded. Safe to render
|
|
20
|
+
* outside an Eve runtime; invoking the returned function there throws.
|
|
21
|
+
*/
|
|
22
|
+
declare const useEveReset: () => () => void;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { useEveError, useEveEvents, useEveReset, useEveSession };
|
|
25
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","names":[],"sources":["../src/hooks.ts"],"mappings":";;;cASa,mBAAW;;;;;;cAOX,qBAAoB;;;;;cAOpB,oBAAmB;;;;;;;cASnB"}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { eveExtras } from "./eveExtras.js";
|
|
3
|
+
import { useAui } from "@assistant-ui/store";
|
|
4
|
+
//#region src/hooks.ts
|
|
5
|
+
const EMPTY_EVENTS = Object.freeze([]);
|
|
6
|
+
/** Read the last Eve session error from the runtime extras. */
|
|
7
|
+
const useEveError = () => eveExtras.use((e) => e.error, void 0);
|
|
8
|
+
/**
|
|
9
|
+
* Read the current Eve session cursor from the runtime extras. Persist it to
|
|
10
|
+
* resume the session later via `initialSession`. `undefined` when no session
|
|
11
|
+
* exists yet or outside an Eve runtime.
|
|
12
|
+
*/
|
|
13
|
+
const useEveSession = () => eveExtras.use((e) => e.session, void 0);
|
|
14
|
+
/**
|
|
15
|
+
* Read the authoritative Eve server event stream from the runtime extras.
|
|
16
|
+
* Defaults to an empty array outside an Eve runtime.
|
|
17
|
+
*/
|
|
18
|
+
const useEveEvents = () => eveExtras.use((e) => e.events, EMPTY_EVENTS);
|
|
19
|
+
/**
|
|
20
|
+
* Returns a function that resets the Eve session: aborts any in-flight turn,
|
|
21
|
+
* recreates the owned session, and clears events and projected data. Pending
|
|
22
|
+
* staged messages and tool execution state are discarded. Safe to render
|
|
23
|
+
* outside an Eve runtime; invoking the returned function there throws.
|
|
24
|
+
*/
|
|
25
|
+
const useEveReset = () => {
|
|
26
|
+
const aui = useAui();
|
|
27
|
+
return () => eveExtras.get(aui).reset();
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { useEveError, useEveEvents, useEveReset, useEveSession };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","names":[],"sources":["../src/hooks.ts"],"sourcesContent":["\"use client\";\n\nimport { useAui } from \"@assistant-ui/store\";\nimport { eveExtras } from \"./eveExtras\";\nimport type { EveRuntimeExtras } from \"./eveExtras\";\n\nconst EMPTY_EVENTS: EveRuntimeExtras[\"events\"] = Object.freeze([]);\n\n/** Read the last Eve session error from the runtime extras. */\nexport const useEveError = () => eveExtras.use((e) => e.error, undefined);\n\n/**\n * Read the current Eve session cursor from the runtime extras. Persist it to\n * resume the session later via `initialSession`. `undefined` when no session\n * exists yet or outside an Eve runtime.\n */\nexport const useEveSession = (): EveRuntimeExtras[\"session\"] | undefined =>\n eveExtras.use((e) => e.session, undefined);\n\n/**\n * Read the authoritative Eve server event stream from the runtime extras.\n * Defaults to an empty array outside an Eve runtime.\n */\nexport const useEveEvents = (): EveRuntimeExtras[\"events\"] =>\n eveExtras.use((e) => e.events, EMPTY_EVENTS);\n\n/**\n * Returns a function that resets the Eve session: aborts any in-flight turn,\n * recreates the owned session, and clears events and projected data. Pending\n * staged messages and tool execution state are discarded. Safe to render\n * outside an Eve runtime; invoking the returned function there throws.\n */\nexport const useEveReset = () => {\n const aui = useAui();\n return () => eveExtras.get(aui).reset();\n};\n"],"mappings":";;;;AAMA,MAAM,eAA2C,OAAO,OAAO,CAAC,CAAC;;AAGjE,MAAa,oBAAoB,UAAU,KAAK,MAAM,EAAE,OAAO,KAAA,CAAS;;;;;;AAOxE,MAAa,sBACX,UAAU,KAAK,MAAM,EAAE,SAAS,KAAA,CAAS;;;;;AAM3C,MAAa,qBACX,UAAU,KAAK,MAAM,EAAE,QAAQ,YAAY;;;;;;;AAQ7C,MAAa,oBAAoB;CAC/B,MAAM,MAAM,OAAO;CACnB,aAAa,UAAU,IAAI,GAAG,CAAC,CAAC,MAAM;AACxC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/// <reference types="@assistant-ui/core/react" />
|
|
2
|
-
import { ConvertEveMessagesOptions, EveAuthorizationData, convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse } from "./convertEveMessages.js";
|
|
2
|
+
import { ConvertEveMessagesOptions, EveAuthorizationData, EveMessageContent, convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse } from "./convertEveMessages.js";
|
|
3
|
+
import { EveRuntimeExtras } from "./eveExtras.js";
|
|
4
|
+
import { useEveError, useEveEvents, useEveReset, useEveSession } from "./hooks.js";
|
|
3
5
|
import { UseEveAgentRuntimeOptions, useEveAgentRuntime } from "./useEveAgentRuntime.js";
|
|
4
|
-
export { type ConvertEveMessagesOptions, type EveAuthorizationData, type UseEveAgentRuntimeOptions, convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse, useEveAgentRuntime };
|
|
6
|
+
export { type ConvertEveMessagesOptions, type EveAuthorizationData, type EveMessageContent, type EveRuntimeExtras, type UseEveAgentRuntimeOptions, convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse, useEveAgentRuntime, useEveError, useEveEvents, useEveReset, useEveSession };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse } from "./convertEveMessages.js";
|
|
2
|
+
import { useEveError, useEveEvents, useEveReset, useEveSession } from "./hooks.js";
|
|
2
3
|
import { useEveAgentRuntime } from "./useEveAgentRuntime.js";
|
|
3
|
-
export { convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse, useEveAgentRuntime };
|
|
4
|
+
export { convertEveMessage, convertEveMessages, getEveMessageContent, toEveInputResponse, useEveAgentRuntime, useEveError, useEveEvents, useEveReset, useEveSession };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEveAgentRuntime.d.ts","names":[],"sources":["../src/useEveAgentRuntime.ts"],"mappings":";;;
|
|
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,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { convertEveMessages, getEveMessageContent, toEveInputResponse } from "./convertEveMessages.js";
|
|
2
|
+
import { convertEveMessages, findEveInputRequest, getEveMessageContent, toEveInputResponse } from "./convertEveMessages.js";
|
|
3
|
+
import { eveExtras } from "./eveExtras.js";
|
|
3
4
|
import { MessageNotSentError, fromThreadMessageLike, generateId, pickExternalStoreSharedOptions } from "@assistant-ui/core";
|
|
4
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { useExternalStoreRuntime, useRuntimeAdapters } from "@assistant-ui/core/react";
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
7
|
import { useEveAgent } from "eve/react";
|
|
7
8
|
//#region src/useEveAgentRuntime.ts
|
|
8
9
|
const USER_STAGED_STATUS = {
|
|
@@ -32,7 +33,7 @@ const hasRunConfig = (runConfig) => runConfig?.custom !== void 0 && Object.keys(
|
|
|
32
33
|
* the assistant-ui envelope would surface a literal `"custom"` key in the
|
|
33
34
|
* prompt and to every eve-side handler.
|
|
34
35
|
*/
|
|
35
|
-
const
|
|
36
|
+
const toEveSendOptions = (runConfig) => hasRunConfig(runConfig) ? { clientContext: runConfig.custom } : void 0;
|
|
36
37
|
/**
|
|
37
38
|
* Connects Eve's `useEveAgent` hook to assistant-ui's runtime contract.
|
|
38
39
|
*
|
|
@@ -60,7 +61,8 @@ const useEveAgentRuntime = (options = {}) => {
|
|
|
60
61
|
const createdAtByMessageIdRef = useRef(/* @__PURE__ */ new Map());
|
|
61
62
|
const stagedInputsRef = useRef(/* @__PURE__ */ new Map());
|
|
62
63
|
const hasExecutingTools = Object.values(toolStatuses).some((status) => status?.type === "executing");
|
|
63
|
-
const
|
|
64
|
+
const providerIsRunning = agent.status === "submitted" || agent.status === "streaming";
|
|
65
|
+
const isRunning = providerIsRunning || hasExecutingTools;
|
|
64
66
|
const convertedMessages = useMemo(() => {
|
|
65
67
|
const createdAtByMessageId = createdAtByMessageIdRef.current;
|
|
66
68
|
const messageIds = new Set(agent.data.messages.map((message) => message.id));
|
|
@@ -84,14 +86,19 @@ const useEveAgentRuntime = (options = {}) => {
|
|
|
84
86
|
const messages = stagedMessages ?? convertedMessages;
|
|
85
87
|
const messagesRef = useRef(messages);
|
|
86
88
|
messagesRef.current = messages;
|
|
89
|
+
const agentRef = useRef(agent);
|
|
90
|
+
agentRef.current = agent;
|
|
87
91
|
const sendChainRef = useRef(Promise.resolve());
|
|
88
92
|
const sendEpochRef = useRef(0);
|
|
93
|
+
const resetEpochRef = useRef(0);
|
|
89
94
|
const isMountedRef = useRef(true);
|
|
90
|
-
const
|
|
91
|
-
|
|
95
|
+
const runtimeRef = useRef(null);
|
|
96
|
+
const enqueueSend = (dispatch, epochRef = sendEpochRef) => {
|
|
97
|
+
const epoch = epochRef.current;
|
|
92
98
|
const next = sendChainRef.current.then(() => {
|
|
93
|
-
if (
|
|
94
|
-
|
|
99
|
+
if (!isMountedRef.current) throw sendAbandonedError;
|
|
100
|
+
if (epoch !== epochRef.current) throw sendCancelledError;
|
|
101
|
+
return dispatch();
|
|
95
102
|
});
|
|
96
103
|
sendChainRef.current = next.catch(() => {});
|
|
97
104
|
return next;
|
|
@@ -132,10 +139,32 @@ const useEveAgentRuntime = (options = {}) => {
|
|
|
132
139
|
messagesRef.current = nextMessages;
|
|
133
140
|
setStagedMessages(nextMessages);
|
|
134
141
|
};
|
|
135
|
-
|
|
142
|
+
const reset = useCallback(() => {
|
|
143
|
+
runtimeRef.current?.thread.unstable_notifySessionReset();
|
|
144
|
+
sendEpochRef.current += 1;
|
|
145
|
+
resetEpochRef.current += 1;
|
|
146
|
+
lastFinishStatusRef.current = null;
|
|
147
|
+
setStagedMessages(null);
|
|
148
|
+
stagedInputsRef.current.clear();
|
|
149
|
+
setToolStatuses({});
|
|
150
|
+
agent.reset();
|
|
151
|
+
}, [agent]);
|
|
152
|
+
const extras = useMemo(() => eveExtras.provide({
|
|
153
|
+
error: agent.error,
|
|
154
|
+
events: agent.events,
|
|
155
|
+
session: agent.session,
|
|
156
|
+
reset
|
|
157
|
+
}), [
|
|
158
|
+
agent.error,
|
|
159
|
+
agent.events,
|
|
160
|
+
agent.session,
|
|
161
|
+
reset
|
|
162
|
+
]);
|
|
163
|
+
const runtime = useExternalStoreRuntime({
|
|
136
164
|
...pickExternalStoreSharedOptions(options),
|
|
137
165
|
messages,
|
|
138
|
-
isRunning,
|
|
166
|
+
isRunning: providerIsRunning,
|
|
167
|
+
extras,
|
|
139
168
|
unstable_enableToolInvocations: true,
|
|
140
169
|
setToolStatuses,
|
|
141
170
|
adapters: {
|
|
@@ -151,10 +180,7 @@ const useEveAgentRuntime = (options = {}) => {
|
|
|
151
180
|
return;
|
|
152
181
|
}
|
|
153
182
|
try {
|
|
154
|
-
await enqueueSend(
|
|
155
|
-
message: getEveMessageContent(message),
|
|
156
|
-
...toEveClientContext(message.runConfig)
|
|
157
|
-
});
|
|
183
|
+
await enqueueSend(() => agent.send(getEveMessageContent(message), toEveSendOptions(message.runConfig)));
|
|
158
184
|
} catch (error) {
|
|
159
185
|
if (error === sendAbandonedError) return;
|
|
160
186
|
throw error;
|
|
@@ -164,6 +190,7 @@ const useEveAgentRuntime = (options = {}) => {
|
|
|
164
190
|
const stagedRun = getStagedRun(parentId);
|
|
165
191
|
if (!stagedRun) throw new Error("Runtime does not support reloading messages.");
|
|
166
192
|
const epoch = sendEpochRef.current;
|
|
193
|
+
const resetEpoch = resetEpochRef.current;
|
|
167
194
|
for (const { message: stagedMessage, input } of stagedRun) {
|
|
168
195
|
if (epoch !== sendEpochRef.current) return;
|
|
169
196
|
const previousMessages = messagesRef.current;
|
|
@@ -173,11 +200,9 @@ const useEveAgentRuntime = (options = {}) => {
|
|
|
173
200
|
setStagedMessages(stagedInputsRef.current.size > 0 ? nextMessages : null);
|
|
174
201
|
const runConfig = stagedMessage.id === parentId && hasRunConfig(config.runConfig) ? config.runConfig : input.runConfig;
|
|
175
202
|
try {
|
|
176
|
-
await enqueueSend(
|
|
177
|
-
message: getEveMessageContent(input.message),
|
|
178
|
-
...toEveClientContext(runConfig)
|
|
179
|
-
});
|
|
203
|
+
await enqueueSend(() => agent.send(getEveMessageContent(input.message), toEveSendOptions(runConfig)));
|
|
180
204
|
} catch (error) {
|
|
205
|
+
if (resetEpoch !== resetEpochRef.current) return;
|
|
181
206
|
stagedInputsRef.current.set(stagedMessage.id, input);
|
|
182
207
|
messagesRef.current = previousMessages;
|
|
183
208
|
setStagedMessages(previousMessages);
|
|
@@ -187,19 +212,29 @@ const useEveAgentRuntime = (options = {}) => {
|
|
|
187
212
|
if (lastFinishStatusRef.current === "error") return;
|
|
188
213
|
}
|
|
189
214
|
} } : {},
|
|
190
|
-
onCancel: () => {
|
|
215
|
+
onCancel: async () => {
|
|
191
216
|
sendEpochRef.current += 1;
|
|
192
|
-
agent
|
|
193
|
-
|
|
217
|
+
const controls = agent;
|
|
218
|
+
if ("cancel" in controls) await controls.cancel();
|
|
219
|
+
else controls.stop();
|
|
194
220
|
},
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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) => {
|
|
199
232
|
if (!isDroppedSend(error)) throw error;
|
|
200
|
-
}
|
|
233
|
+
});
|
|
201
234
|
}
|
|
202
235
|
});
|
|
236
|
+
runtimeRef.current = runtime;
|
|
237
|
+
return runtime;
|
|
203
238
|
};
|
|
204
239
|
//#endregion
|
|
205
240
|
export { useEveAgentRuntime };
|