@assistant-ui/eve 0.0.7 → 0.0.9
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 +32 -3
- package/dist/convertEveMessages.d.ts.map +1 -1
- package/dist/convertEveMessages.js +75 -4
- package/dist/convertEveMessages.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/useEveAgentRuntime.d.ts.map +1 -1
- package/dist/useEveAgentRuntime.js +63 -16
- package/dist/useEveAgentRuntime.js.map +1 -1
- package/package.json +14 -10
- package/src/convertEveMessages.test.ts +1041 -5
- package/src/convertEveMessages.ts +155 -6
- package/src/index.ts +5 -1
- package/src/useEveAgentRuntime.test.tsx +622 -0
- package/src/useEveAgentRuntime.ts +90 -24
- package/LICENSE +0 -21
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fromThreadMessageLike,
|
|
3
|
+
toAssistantError,
|
|
3
4
|
type AppendMessage,
|
|
5
|
+
type CompleteAttachment,
|
|
6
|
+
type DataMessagePart,
|
|
7
|
+
type FileMessagePart,
|
|
4
8
|
type MessageStatus,
|
|
5
9
|
type RespondToToolApprovalOptions,
|
|
6
10
|
type ThreadAssistantMessagePart,
|
|
@@ -12,6 +16,8 @@ import {
|
|
|
12
16
|
} from "@assistant-ui/core";
|
|
13
17
|
import { httpUrlPattern, parseDataUrl } from "@assistant-ui/core/internal";
|
|
14
18
|
import type {
|
|
19
|
+
EveAuthorizationOutcome,
|
|
20
|
+
EveAuthorizationPart,
|
|
15
21
|
EveDynamicToolPart,
|
|
16
22
|
EveMessage,
|
|
17
23
|
EveMessageData,
|
|
@@ -29,6 +35,11 @@ const ASSISTANT_RUNNING_STATUS = {
|
|
|
29
35
|
type: "running",
|
|
30
36
|
} satisfies MessageStatus;
|
|
31
37
|
|
|
38
|
+
const ASSISTANT_CANCELLED_STATUS = {
|
|
39
|
+
type: "incomplete",
|
|
40
|
+
reason: "cancelled",
|
|
41
|
+
} satisfies MessageStatus;
|
|
42
|
+
|
|
32
43
|
const USER_FALLBACK_STATUS = {
|
|
33
44
|
type: "complete",
|
|
34
45
|
reason: "unknown",
|
|
@@ -37,9 +48,15 @@ const USER_FALLBACK_STATUS = {
|
|
|
37
48
|
export type ConvertEveMessagesOptions = {
|
|
38
49
|
/**
|
|
39
50
|
* Marks the last assistant message as running while Eve is submitting or
|
|
40
|
-
* streaming.
|
|
51
|
+
* streaming. When omitted, a message carrying Eve's `"streaming"` marker is
|
|
52
|
+
* treated as running; pass `false` to settle interrupted messages to a
|
|
53
|
+
* terminal status.
|
|
41
54
|
*/
|
|
42
55
|
readonly isRunning?: boolean | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The Eve session error, mapped onto the assistant message it interrupted.
|
|
58
|
+
*/
|
|
59
|
+
readonly error?: unknown;
|
|
43
60
|
readonly getCreatedAt?: ((message: EveMessage) => Date) | undefined;
|
|
44
61
|
};
|
|
45
62
|
|
|
@@ -68,26 +85,55 @@ const toMessageStatus = (
|
|
|
68
85
|
): MessageStatus => {
|
|
69
86
|
if (message.role !== "assistant") return USER_FALLBACK_STATUS;
|
|
70
87
|
|
|
88
|
+
const isLast = index === messages.length - 1;
|
|
71
89
|
const hasPendingApproval = message.parts.some(
|
|
72
90
|
(part) =>
|
|
73
91
|
part.type === "dynamic-tool" && part.state === "approval-requested",
|
|
74
92
|
);
|
|
93
|
+
// Scoped to the stale-marker case so a terminalized or errored turn keeps its
|
|
94
|
+
// own status; the hold only claims the fall-through that reads as cancelled.
|
|
95
|
+
const hasPendingAuthorization =
|
|
96
|
+
message.metadata?.status === "streaming" &&
|
|
97
|
+
(!isLast || options.error === undefined) &&
|
|
98
|
+
message.parts.some(
|
|
99
|
+
(part) => part.type === "authorization" && part.state === "required",
|
|
100
|
+
);
|
|
75
101
|
|
|
76
102
|
if (hasPendingApproval) {
|
|
77
103
|
return { type: "requires-action", reason: "tool-calls" };
|
|
78
104
|
}
|
|
79
105
|
|
|
106
|
+
// A connector authorization is answered outside the thread, so the turn is
|
|
107
|
+
// held on external input rather than on a tool call awaiting a result.
|
|
108
|
+
if (hasPendingAuthorization) {
|
|
109
|
+
return { type: "requires-action", reason: "interrupt" };
|
|
110
|
+
}
|
|
111
|
+
|
|
80
112
|
if (message.metadata?.status === "failed") {
|
|
81
113
|
return { type: "incomplete", reason: "error" };
|
|
82
114
|
}
|
|
83
115
|
|
|
84
|
-
if (
|
|
85
|
-
message.metadata?.status === "streaming" ||
|
|
86
|
-
(options.isRunning === true && index === messages.length - 1)
|
|
87
|
-
) {
|
|
116
|
+
if (isLast && options.isRunning === true) {
|
|
88
117
|
return ASSISTANT_RUNNING_STATUS;
|
|
89
118
|
}
|
|
90
119
|
|
|
120
|
+
// Eve's default reducer never terminalizes the "streaming" marker on
|
|
121
|
+
// cancellation or turn/session failure, so liveness comes from isRunning and
|
|
122
|
+
// a leftover marker means the turn was interrupted.
|
|
123
|
+
if (message.metadata?.status === "streaming") {
|
|
124
|
+
if (options.isRunning === undefined) {
|
|
125
|
+
return ASSISTANT_RUNNING_STATUS;
|
|
126
|
+
}
|
|
127
|
+
if (isLast && options.error !== undefined) {
|
|
128
|
+
return {
|
|
129
|
+
type: "incomplete",
|
|
130
|
+
reason: "error",
|
|
131
|
+
error: toAssistantError(options.error),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return ASSISTANT_CANCELLED_STATUS;
|
|
135
|
+
}
|
|
136
|
+
|
|
91
137
|
return ASSISTANT_COMPLETE_STATUS;
|
|
92
138
|
};
|
|
93
139
|
|
|
@@ -176,6 +222,71 @@ const convertDynamicToolPart = (
|
|
|
176
222
|
}
|
|
177
223
|
};
|
|
178
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Payload of the `authorization` data part the Eve runtime emits for a
|
|
227
|
+
* connector authorization challenge. `state` discriminates a pending challenge
|
|
228
|
+
* from a settled one; every other field is present only when Eve projected it.
|
|
229
|
+
* This converter drops a `url` whose scheme is not `http(s)`, so a renderer can
|
|
230
|
+
* link to it without checking the scheme itself; Eve itself passes through
|
|
231
|
+
* whatever the connector supplied. Eve's
|
|
232
|
+
* `turnId` and `stepIndex` part identity is omitted: a renderer receives the
|
|
233
|
+
* one part it renders, in Eve's own step order, so display never has to
|
|
234
|
+
* re-identify a challenge among several.
|
|
235
|
+
*/
|
|
236
|
+
export type EveAuthorizationData = {
|
|
237
|
+
readonly state: EveAuthorizationPart["state"];
|
|
238
|
+
readonly name: string;
|
|
239
|
+
readonly displayName?: string;
|
|
240
|
+
readonly description?: string;
|
|
241
|
+
readonly url?: string;
|
|
242
|
+
readonly userCode?: string;
|
|
243
|
+
readonly instructions?: string;
|
|
244
|
+
readonly expiresAt?: string;
|
|
245
|
+
readonly outcome?: EveAuthorizationOutcome;
|
|
246
|
+
readonly reason?: string;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const convertAuthorizationPart = (
|
|
250
|
+
part: EveAuthorizationPart,
|
|
251
|
+
): DataMessagePart<EveAuthorizationData> => ({
|
|
252
|
+
type: "data",
|
|
253
|
+
name: "authorization",
|
|
254
|
+
data: {
|
|
255
|
+
state: part.state,
|
|
256
|
+
name: part.name,
|
|
257
|
+
...(part.displayName !== undefined && { displayName: part.displayName }),
|
|
258
|
+
...(part.description !== undefined && { description: part.description }),
|
|
259
|
+
...(part.authorization?.url !== undefined &&
|
|
260
|
+
httpUrlPattern.test(part.authorization.url) && {
|
|
261
|
+
url: part.authorization.url,
|
|
262
|
+
}),
|
|
263
|
+
...(part.authorization?.userCode !== undefined && {
|
|
264
|
+
userCode: part.authorization.userCode,
|
|
265
|
+
}),
|
|
266
|
+
...(part.authorization?.instructions !== undefined && {
|
|
267
|
+
instructions: part.authorization.instructions,
|
|
268
|
+
}),
|
|
269
|
+
...(part.authorization?.expiresAt !== undefined && {
|
|
270
|
+
expiresAt: part.authorization.expiresAt,
|
|
271
|
+
}),
|
|
272
|
+
...(part.outcome !== undefined && { outcome: part.outcome }),
|
|
273
|
+
...(part.reason !== undefined && { reason: part.reason }),
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const convertFilePart = (
|
|
278
|
+
part: Extract<EveMessagePart, { type: "file" }>,
|
|
279
|
+
): FileMessagePart | null => {
|
|
280
|
+
if (part.url === undefined) return null;
|
|
281
|
+
return {
|
|
282
|
+
type: "file",
|
|
283
|
+
data: part.url,
|
|
284
|
+
mimeType: part.mediaType ?? "unknown/unknown",
|
|
285
|
+
...(part.filename && { filename: part.filename }),
|
|
286
|
+
...(httpUrlPattern.test(part.url) && { sourceType: "url" as const }),
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
|
|
179
290
|
const convertAssistantPart = (
|
|
180
291
|
part: EveMessagePart,
|
|
181
292
|
): ThreadAssistantMessagePart | null => {
|
|
@@ -190,6 +301,12 @@ const convertAssistantPart = (
|
|
|
190
301
|
case "dynamic-tool":
|
|
191
302
|
return convertDynamicToolPart(part);
|
|
192
303
|
|
|
304
|
+
case "authorization":
|
|
305
|
+
return convertAuthorizationPart(part);
|
|
306
|
+
|
|
307
|
+
case "file":
|
|
308
|
+
return convertFilePart(part);
|
|
309
|
+
|
|
193
310
|
default:
|
|
194
311
|
return null;
|
|
195
312
|
}
|
|
@@ -202,6 +319,9 @@ const convertUserPart = (
|
|
|
202
319
|
case "text":
|
|
203
320
|
return { type: "text", text: part.text };
|
|
204
321
|
|
|
322
|
+
case "file":
|
|
323
|
+
return convertFilePart(part);
|
|
324
|
+
|
|
205
325
|
default:
|
|
206
326
|
return null;
|
|
207
327
|
}
|
|
@@ -214,6 +334,35 @@ const toUserContent = (
|
|
|
214
334
|
return content.length > 0 ? content : [{ type: "text", text: "" }];
|
|
215
335
|
};
|
|
216
336
|
|
|
337
|
+
const toUserAttachments = (
|
|
338
|
+
parts: readonly EveMessagePart[],
|
|
339
|
+
): CompleteAttachment[] => {
|
|
340
|
+
const attachments: CompleteAttachment[] = [];
|
|
341
|
+
for (const part of parts) {
|
|
342
|
+
if (part.type !== "file") continue;
|
|
343
|
+
const file = convertFilePart(part);
|
|
344
|
+
if (file === null) continue;
|
|
345
|
+
const isImage = file.mimeType.startsWith("image/");
|
|
346
|
+
attachments.push({
|
|
347
|
+
id: String(attachments.length),
|
|
348
|
+
type: isImage ? "image" : "file",
|
|
349
|
+
name: part.filename ?? "file",
|
|
350
|
+
content: [
|
|
351
|
+
isImage
|
|
352
|
+
? {
|
|
353
|
+
type: "image",
|
|
354
|
+
image: file.data,
|
|
355
|
+
...(part.filename && { filename: part.filename }),
|
|
356
|
+
}
|
|
357
|
+
: file,
|
|
358
|
+
],
|
|
359
|
+
contentType: file.mimeType,
|
|
360
|
+
status: { type: "complete" },
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return attachments;
|
|
364
|
+
};
|
|
365
|
+
|
|
217
366
|
/**
|
|
218
367
|
* Converts a single Eve message into an assistant-ui thread message.
|
|
219
368
|
*/
|
|
@@ -238,7 +387,7 @@ export const convertEveMessage = (
|
|
|
238
387
|
id: message.id,
|
|
239
388
|
createdAt,
|
|
240
389
|
content: toUserContent(message.parts),
|
|
241
|
-
attachments:
|
|
390
|
+
attachments: toUserAttachments(message.parts),
|
|
242
391
|
metadata,
|
|
243
392
|
}
|
|
244
393
|
: {
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,11 @@ export {
|
|
|
4
4
|
convertEveMessage,
|
|
5
5
|
convertEveMessages,
|
|
6
6
|
getEveMessageContent,
|
|
7
|
+
toEveInputResponse,
|
|
8
|
+
} from "./convertEveMessages";
|
|
9
|
+
export type {
|
|
10
|
+
ConvertEveMessagesOptions,
|
|
11
|
+
EveAuthorizationData,
|
|
7
12
|
} from "./convertEveMessages";
|
|
8
|
-
export type { ConvertEveMessagesOptions } from "./convertEveMessages";
|
|
9
13
|
export { useEveAgentRuntime } from "./useEveAgentRuntime";
|
|
10
14
|
export type { UseEveAgentRuntimeOptions } from "./useEveAgentRuntime";
|