@assistant-ui/eve 0.0.24 → 0.0.25
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 +40 -51
- package/dist/convertEveMessages.d.ts.map +1 -1
- package/dist/convertEveMessages.js +146 -117
- package/dist/convertEveMessages.js.map +1 -1
- package/dist/deriveCreatedAt.d.ts +9 -9
- package/dist/deriveCreatedAt.d.ts.map +1 -1
- package/dist/deriveCreatedAt.js +25 -34
- package/dist/deriveCreatedAt.js.map +1 -1
- package/dist/eveCloudSessions.js +1 -2
- package/dist/eveExtras.d.ts +4 -2
- package/dist/eveExtras.d.ts.map +1 -1
- package/dist/eveExtras.js +1 -2
- package/dist/eveExtras.js.map +1 -1
- package/dist/hooks.d.ts +4 -3
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +5 -5
- package/dist/hooks.js.map +1 -1
- package/dist/sdkIdentity.js +2 -3
- package/dist/useEveAgentRuntime.d.ts +3 -5
- package/dist/useEveAgentRuntime.d.ts.map +1 -1
- package/dist/useEveAgentRuntime.js +34 -23
- package/dist/useEveAgentRuntime.js.map +1 -1
- package/package.json +10 -10
- package/src/convertEveMessages.test.ts +925 -729
- package/src/convertEveMessages.ts +282 -227
- package/src/deriveCreatedAt.test.ts +90 -88
- package/src/deriveCreatedAt.ts +32 -36
- package/src/eveExtras.ts +4 -4
- package/src/hooks.ts +4 -3
- package/src/testUtils.ts +189 -8
- package/src/useEveAgentRuntime.cloud.test.tsx +105 -136
- package/src/useEveAgentRuntime.eveResume.test.tsx +22 -104
- package/src/useEveAgentRuntime.fast-refresh.test.tsx +195 -37
- package/src/useEveAgentRuntime.interruptedTurn.test.tsx +86 -40
- package/src/useEveAgentRuntime.refetch.test.tsx +19 -58
- package/src/useEveAgentRuntime.test.tsx +372 -291
- package/src/useEveAgentRuntime.ts +100 -113
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type AppendMessage, type RespondToToolApprovalOptions, type ThreadMessage } from "@assistant-ui/core";
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type
|
|
2
|
+
import type { EveMessage, Interaction } from "eve/react";
|
|
3
|
+
import type { ConversationState, SessionStreamEvent } from "eve/client";
|
|
4
|
+
import { type Answer } from "eve/events";
|
|
5
5
|
export type ConvertEveMessagesOptions = {
|
|
6
6
|
/**
|
|
7
|
-
* Marks the last assistant message as running while Eve
|
|
8
|
-
*
|
|
7
|
+
* Marks the last assistant message as running while Eve has a turn in
|
|
8
|
+
* flight. When omitted, a message carrying Eve's `"streaming"` marker is
|
|
9
9
|
* treated as running; pass `false` to settle interrupted messages to a
|
|
10
10
|
* terminal status.
|
|
11
11
|
*/
|
|
@@ -16,25 +16,22 @@ export type ConvertEveMessagesOptions = {
|
|
|
16
16
|
readonly error?: unknown;
|
|
17
17
|
readonly getCreatedAt?: ((message: EveMessage) => Date) | undefined;
|
|
18
18
|
/**
|
|
19
|
-
* The session's stream events. A
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* The session's stream events. A failed or cancelled turn marks its
|
|
20
|
+
* assistant message as incomplete, with a failure's code and message as its
|
|
21
|
+
* error.
|
|
22
22
|
*/
|
|
23
|
-
readonly events?: readonly
|
|
23
|
+
readonly events?: readonly SessionStreamEvent[] | undefined;
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
|
-
* Payload of the `authorization` data part the Eve runtime emits for a
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* `turnId` and `stepIndex` part identity is omitted: a renderer receives the
|
|
33
|
-
* one part it renders, in Eve's own step order, so display never has to
|
|
34
|
-
* re-identify a challenge among several.
|
|
26
|
+
* Payload of the `authorization` data part the Eve runtime emits for a sign-in
|
|
27
|
+
* interaction. `state` discriminates a pending sign-in from a settled one;
|
|
28
|
+
* every other field is present only when Eve projected it. This converter
|
|
29
|
+
* drops a `url` whose scheme is not `http(s)`, so a renderer can link to it
|
|
30
|
+
* without checking the scheme itself; Eve itself passes through whatever the
|
|
31
|
+
* connector supplied.
|
|
35
32
|
*/
|
|
36
33
|
export type EveAuthorizationData = {
|
|
37
|
-
readonly state:
|
|
34
|
+
readonly state: "required" | "completed";
|
|
38
35
|
readonly name: string;
|
|
39
36
|
readonly displayName?: string;
|
|
40
37
|
readonly description?: string;
|
|
@@ -42,7 +39,7 @@ export type EveAuthorizationData = {
|
|
|
42
39
|
readonly userCode?: string;
|
|
43
40
|
readonly instructions?: string;
|
|
44
41
|
readonly expiresAt?: string;
|
|
45
|
-
readonly outcome?:
|
|
42
|
+
readonly outcome?: "authorized" | "declined" | "failed" | "timed-out" | "cancelled";
|
|
46
43
|
readonly reason?: string;
|
|
47
44
|
};
|
|
48
45
|
/**
|
|
@@ -50,23 +47,18 @@ export type EveAuthorizationData = {
|
|
|
50
47
|
*/
|
|
51
48
|
export declare const convertEveMessage: (message: EveMessage, index: number, messages: readonly EveMessage[], options?: ConvertEveMessagesOptions) => ThreadMessage;
|
|
52
49
|
export type InterruptedTurnEventCache = {
|
|
53
|
-
lastEvents: readonly
|
|
54
|
-
interruptions: readonly
|
|
50
|
+
lastEvents: readonly SessionStreamEvent[];
|
|
51
|
+
interruptions: readonly SessionStreamEvent[];
|
|
55
52
|
};
|
|
53
|
+
export declare const collectInterruptedTurnEvents: (events: readonly SessionStreamEvent[], cache: InterruptedTurnEventCache) => readonly SessionStreamEvent[];
|
|
56
54
|
/**
|
|
57
|
-
*
|
|
58
|
-
* event log, scanning only the events appended since the cached call. The
|
|
59
|
-
* returned array keeps its identity until a new interruption arrives.
|
|
55
|
+
* Converts Eve's conversation messages into assistant-ui thread messages.
|
|
60
56
|
*/
|
|
61
|
-
export declare const
|
|
57
|
+
export declare const convertEveMessages: (data: Pick<ConversationState, "messages">, options?: ConvertEveMessagesOptions) => ThreadMessage[];
|
|
62
58
|
/**
|
|
63
|
-
*
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Structural subset of the `string | UserContent` message content Eve's `send`
|
|
68
|
-
* API accepts. The helpers declare the shapes they produce and leave
|
|
69
|
-
* assignability to the send call site, checked against the installed version.
|
|
59
|
+
* Structural subset of the message content Eve's `send` API accepts. The
|
|
60
|
+
* helpers declare the shapes they produce and leave assignability to the send
|
|
61
|
+
* call site, checked against the installed version.
|
|
70
62
|
*/
|
|
71
63
|
export type EveMessageContent = string | ({
|
|
72
64
|
readonly type: "text";
|
|
@@ -83,34 +75,31 @@ export type EveMessageContent = string | ({
|
|
|
83
75
|
*/
|
|
84
76
|
export declare const getEveMessageContent: (message: AppendMessage) => EveMessageContent;
|
|
85
77
|
/**
|
|
86
|
-
* Finds the
|
|
87
|
-
*
|
|
78
|
+
* Finds the request of the interaction answered by the given approval id, so
|
|
79
|
+
* the answer mapping can honor the request's display mode.
|
|
88
80
|
*/
|
|
89
|
-
export declare const findEveInputRequest: (data:
|
|
81
|
+
export declare const findEveInputRequest: (data: Pick<ConversationState, "messages">, approvalId: string) => Interaction["request"] | undefined;
|
|
90
82
|
/**
|
|
91
|
-
* Converts an assistant-ui tool approval response into an Eve
|
|
83
|
+
* Converts an assistant-ui tool approval response into an Eve `Answer`.
|
|
92
84
|
*
|
|
93
|
-
* When `inputRequest` is known, every returned
|
|
94
|
-
*
|
|
95
|
-
*
|
|
85
|
+
* When `inputRequest` is known, every returned answer carries an option the
|
|
86
|
+
* request declares, the literal `"approve"` / `"deny"` for an approval that
|
|
87
|
+
* declares no options, or a free-form answer. A literal option match wins,
|
|
88
|
+
* then the `"approve"` / `"deny"` option the response's boolean decision
|
|
96
89
|
* names, then the response's `text` (or its `reason`, for a caller that
|
|
97
90
|
* predates the first-class answer field) when the request takes a free-form
|
|
98
91
|
* answer (`display: "text"`, `allowFreeform`, or no options at all, and never
|
|
99
92
|
* `display: "confirmation"`) and the response is not a refusal.
|
|
100
|
-
* A one-argument call, or a part with no request, still forwards a caller
|
|
101
|
-
* `optionId` as it always has, including ids the request never declared.
|
|
102
93
|
*
|
|
103
94
|
* A response the known request cannot record as the answer it asked for throws
|
|
104
|
-
* instead of being submitted, because
|
|
105
|
-
*
|
|
106
|
-
* content. Not sending leaves the
|
|
95
|
+
* instead of being submitted, because Eve resolves an interaction the moment
|
|
96
|
+
* any answer for it arrives, and an empty one is recorded as an answer with no
|
|
97
|
+
* content. Not sending leaves the interaction open, so the caller can retry or
|
|
107
98
|
* the user can answer it as an ordinary message.
|
|
108
99
|
*
|
|
109
|
-
* With no request to map against
|
|
110
|
-
* `
|
|
111
|
-
*
|
|
112
|
-
* a display mode is what this mapper stopped doing; an unknown request is not
|
|
113
|
-
* a guess about one.
|
|
100
|
+
* With no request to map against, a one-argument call forwards a caller
|
|
101
|
+
* `optionId` as is and otherwise maps the decision to the literal `"approve"` /
|
|
102
|
+
* `"deny"` option every Eve approval declares.
|
|
114
103
|
*/
|
|
115
|
-
export declare const toEveInputResponse: (response: RespondToToolApprovalOptions, inputRequest?:
|
|
104
|
+
export declare const toEveInputResponse: (response: RespondToToolApprovalOptions, inputRequest?: Interaction["request"]) => Answer;
|
|
116
105
|
//# sourceMappingURL=convertEveMessages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convertEveMessages.d.ts","sourceRoot":"","sources":["../src/convertEveMessages.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,aAAa,EAOlB,KAAK,4BAA4B,EAEjC,KAAK,aAAa,EAMnB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"convertEveMessages.d.ts","sourceRoot":"","sources":["../src/convertEveMessages.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fromThreadMessageLike, toAssistantError } from "@assistant-ui/core";
|
|
2
2
|
import { httpUrlPattern, resolveFileMediaType, resolveFilePartSource, resolveImageMediaType, toMediaWireUrl } from "@assistant-ui/core/internal";
|
|
3
|
-
|
|
3
|
+
import { APPROVE_OPTION_ID, DENY_OPTION_ID } from "eve/events";
|
|
4
|
+
// src/convertEveMessages.ts
|
|
4
5
|
const ASSISTANT_COMPLETE_STATUS = {
|
|
5
6
|
type: "complete",
|
|
6
7
|
reason: "stop"
|
|
@@ -32,8 +33,9 @@ const toMessageStatus = (message, index, messages, options, interruptedTurns) =>
|
|
|
32
33
|
const interrupted = message.metadata?.turnId === void 0 ? void 0 : interruptedTurns.get(message.metadata.turnId);
|
|
33
34
|
if (interrupted) return interrupted;
|
|
34
35
|
const isLast = index === messages.length - 1;
|
|
35
|
-
const
|
|
36
|
-
const
|
|
36
|
+
const interactions = message.parts.flatMap((part) => part.type === "interaction" ? [part] : part.type === "tool" && part.interaction ? [part.interaction] : []);
|
|
37
|
+
const hasPendingApproval = interactions.some((interaction) => interaction.status === "open" && interaction.request.kind !== "sign-in");
|
|
38
|
+
const hasPendingAuthorization = message.metadata?.status === "streaming" && (!isLast || options.error === void 0) && interactions.some((interaction) => interaction.status === "open" && interaction.request.kind === "sign-in");
|
|
37
39
|
if (hasPendingApproval) return {
|
|
38
40
|
type: "requires-action",
|
|
39
41
|
reason: "tool-calls"
|
|
@@ -63,19 +65,19 @@ const toolApprovalOptionsFromInputRequest = (inputRequest) => {
|
|
|
63
65
|
if (!options || options.length === 0) return void 0;
|
|
64
66
|
return options.map((option) => ({
|
|
65
67
|
id: option.id,
|
|
66
|
-
kind: option.id ===
|
|
68
|
+
kind: option.id === APPROVE_OPTION_ID ? "allow-once" : option.id === DENY_OPTION_ID ? "reject-once" : `_${option.id}`,
|
|
67
69
|
...option.label && { label: option.label },
|
|
68
70
|
...option.description && { description: option.description }
|
|
69
71
|
}));
|
|
70
72
|
};
|
|
71
73
|
/**
|
|
72
|
-
* Eve resolves
|
|
73
|
-
* shapes it accepts decide both what the renderer may offer and what
|
|
74
|
+
* Eve resolves an interaction the moment any answer for it arrives, so the
|
|
75
|
+
* answer shapes it accepts decide both what the renderer may offer and what
|
|
74
76
|
* {@link toEveInputResponse} may submit. A request with no options and no
|
|
75
77
|
* `display` takes a typed answer, which is why the absence of both is not the
|
|
76
78
|
* same as a confirmation.
|
|
77
79
|
*/
|
|
78
|
-
const isDroppedOptionApproval = (inputRequest) => inputRequest.kind === "
|
|
80
|
+
const isDroppedOptionApproval = (inputRequest) => inputRequest.kind === "approval" && !inputRequest.options?.length;
|
|
79
81
|
const acceptsFreeformAnswer = (inputRequest) => inputRequest.display !== "confirmation" && !isDroppedOptionApproval(inputRequest) && (inputRequest.display === "text" || inputRequest.allowFreeform === true || !inputRequest.options?.length);
|
|
80
82
|
const toolApprovalDisplay = (inputRequest) => {
|
|
81
83
|
if (isDroppedOptionApproval(inputRequest)) return "decision";
|
|
@@ -84,93 +86,119 @@ const toolApprovalDisplay = (inputRequest) => {
|
|
|
84
86
|
case "select": return "select";
|
|
85
87
|
case "text": return "text";
|
|
86
88
|
default:
|
|
87
|
-
if (inputRequest.kind === "
|
|
89
|
+
if (inputRequest.kind === "approval") return "decision";
|
|
88
90
|
return inputRequest.options?.length ? "select" : "text";
|
|
89
91
|
}
|
|
90
92
|
};
|
|
91
|
-
const toApproval = (
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const options = toolApprovalOptionsFromInputRequest(inputRequest);
|
|
93
|
+
const toApproval = (interaction) => {
|
|
94
|
+
const request = interaction.request;
|
|
95
|
+
const options = toolApprovalOptionsFromInputRequest(request);
|
|
96
|
+
const settled = interaction.status === "settled";
|
|
97
|
+
const approved = interaction.outcome === "accepted" ? true : interaction.outcome === "declined" ? false : void 0;
|
|
97
98
|
return {
|
|
98
|
-
id:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
},
|
|
104
|
-
...
|
|
105
|
-
...
|
|
106
|
-
...
|
|
99
|
+
id: interaction.interactionId,
|
|
100
|
+
prompt: request.prompt,
|
|
101
|
+
display: toolApprovalDisplay(request),
|
|
102
|
+
...acceptsFreeformAnswer(request) && { allowFreeform: true },
|
|
103
|
+
...settled && approved !== void 0 && { approved },
|
|
104
|
+
...settled && approved === void 0 && { resolution: interaction.outcome === "expired" ? "expired" : "cancelled" },
|
|
105
|
+
...interaction.reason && { reason: interaction.reason },
|
|
106
|
+
...settled && interaction.answer?.optionId !== void 0 && { optionId: interaction.answer.optionId },
|
|
107
|
+
...settled && interaction.answer?.text !== void 0 && { text: interaction.answer.text },
|
|
107
108
|
...options && { options }
|
|
108
109
|
};
|
|
109
110
|
};
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
...option.style !== void 0 && { style: option.style }
|
|
121
|
-
})) }
|
|
122
|
-
});
|
|
123
|
-
const toToolProviderMetadata = (part) => {
|
|
124
|
-
const inputRequest = part.toolMetadata?.eve?.inputRequest;
|
|
125
|
-
if (!inputRequest) return void 0;
|
|
126
|
-
return { eve: { inputRequest: toJsonSafeInputRequest(inputRequest) } };
|
|
111
|
+
const toToolProviderMetadata = (interaction) => {
|
|
112
|
+
const request = interaction.request;
|
|
113
|
+
return { eve: { interaction: {
|
|
114
|
+
interactionId: interaction.interactionId,
|
|
115
|
+
prompt: request.prompt,
|
|
116
|
+
kind: request.kind,
|
|
117
|
+
...request.display !== void 0 && { display: request.display },
|
|
118
|
+
...request.allowFreeform !== void 0 && { allowFreeform: request.allowFreeform },
|
|
119
|
+
...request.options && { options: request.options.map((option) => ({ ...option })) }
|
|
120
|
+
} } };
|
|
127
121
|
};
|
|
128
|
-
const
|
|
129
|
-
const approval = toApproval(part);
|
|
130
|
-
const providerMetadata = toToolProviderMetadata(part);
|
|
122
|
+
const convertToolPart = (part) => {
|
|
131
123
|
const toolCall = {
|
|
132
124
|
type: "tool-call",
|
|
133
|
-
toolCallId: part.
|
|
134
|
-
toolName: part.
|
|
135
|
-
args: toJsonObject(part.input),
|
|
136
|
-
argsText: part.state === "
|
|
137
|
-
...
|
|
138
|
-
|
|
125
|
+
toolCallId: part.callId,
|
|
126
|
+
toolName: part.name,
|
|
127
|
+
...part.state !== "streaming" && { args: toJsonObject(part.input) },
|
|
128
|
+
argsText: part.state === "streaming" ? part.inputText : stringifyArgs(part.input),
|
|
129
|
+
...part.interaction && {
|
|
130
|
+
approval: toApproval(part.interaction),
|
|
131
|
+
providerMetadata: toToolProviderMetadata(part.interaction)
|
|
132
|
+
}
|
|
139
133
|
};
|
|
140
134
|
switch (part.state) {
|
|
141
|
-
case "
|
|
135
|
+
case "completed": return {
|
|
142
136
|
...toolCall,
|
|
143
137
|
result: part.output
|
|
144
138
|
};
|
|
145
|
-
case "
|
|
139
|
+
case "failed":
|
|
140
|
+
case "rejected":
|
|
141
|
+
case "cancelled": return {
|
|
146
142
|
...toolCall,
|
|
147
|
-
result: { error: part.
|
|
143
|
+
result: { error: part.error?.message ?? part.interaction?.reason ?? (part.state === "rejected" ? "Tool approval denied" : part.state === "cancelled" ? "Tool call cancelled" : "Tool call failed") },
|
|
148
144
|
isError: true
|
|
149
145
|
};
|
|
150
|
-
|
|
146
|
+
default: return part.output === void 0 ? toolCall : {
|
|
151
147
|
...toolCall,
|
|
152
|
-
result:
|
|
153
|
-
|
|
148
|
+
result: part.output,
|
|
149
|
+
isPreliminary: true
|
|
154
150
|
};
|
|
155
|
-
default: return toolCall;
|
|
156
151
|
}
|
|
157
152
|
};
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
...part.authorization?.url !== void 0 && httpUrlPattern.test(part.authorization.url) && { url: part.authorization.url },
|
|
167
|
-
...part.authorization?.userCode !== void 0 && { userCode: part.authorization.userCode },
|
|
168
|
-
...part.authorization?.instructions !== void 0 && { instructions: part.authorization.instructions },
|
|
169
|
-
...part.authorization?.expiresAt !== void 0 && { expiresAt: part.authorization.expiresAt },
|
|
170
|
-
...part.outcome !== void 0 && { outcome: part.outcome },
|
|
171
|
-
...part.reason !== void 0 && { reason: part.reason }
|
|
153
|
+
const toAuthorizationOutcome = (outcome) => {
|
|
154
|
+
switch (outcome) {
|
|
155
|
+
case "accepted": return "authorized";
|
|
156
|
+
case "declined":
|
|
157
|
+
case "invalid": return "declined";
|
|
158
|
+
case "expired": return "timed-out";
|
|
159
|
+
case "failed": return "failed";
|
|
160
|
+
default: return "cancelled";
|
|
172
161
|
}
|
|
173
|
-
}
|
|
162
|
+
};
|
|
163
|
+
const convertAuthorizationPart = (interaction) => {
|
|
164
|
+
const signIn = interaction.request.signIn;
|
|
165
|
+
return {
|
|
166
|
+
type: "data",
|
|
167
|
+
name: "authorization",
|
|
168
|
+
data: {
|
|
169
|
+
state: interaction.status === "open" ? "required" : "completed",
|
|
170
|
+
name: signIn?.name ?? interaction.request.title ?? "sign-in",
|
|
171
|
+
...signIn?.displayName !== void 0 && { displayName: signIn.displayName },
|
|
172
|
+
description: interaction.request.prompt,
|
|
173
|
+
...signIn?.url !== void 0 && httpUrlPattern.test(signIn.url) && { url: signIn.url },
|
|
174
|
+
...signIn?.userCode !== void 0 && { userCode: signIn.userCode },
|
|
175
|
+
...signIn?.instructions !== void 0 && { instructions: signIn.instructions },
|
|
176
|
+
...signIn?.expiresAt !== void 0 && { expiresAt: signIn.expiresAt },
|
|
177
|
+
...interaction.outcome !== void 0 && { outcome: toAuthorizationOutcome(interaction.outcome) },
|
|
178
|
+
...interaction.reason !== void 0 && { reason: interaction.reason }
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
const convertInteractionPart = (interaction) => {
|
|
183
|
+
if (interaction.request.kind === "sign-in") return convertAuthorizationPart(interaction);
|
|
184
|
+
const approval = toApproval(interaction);
|
|
185
|
+
return {
|
|
186
|
+
type: "tool-call",
|
|
187
|
+
toolCallId: interaction.interactionId,
|
|
188
|
+
toolName: interaction.call?.name ?? interaction.request.kind,
|
|
189
|
+
args: toJsonObject(interaction.call?.input),
|
|
190
|
+
argsText: stringifyArgs(interaction.call?.input),
|
|
191
|
+
approval,
|
|
192
|
+
providerMetadata: toToolProviderMetadata(interaction),
|
|
193
|
+
...interaction.status === "settled" && {
|
|
194
|
+
result: interaction.answer ?? {
|
|
195
|
+
outcome: interaction.outcome,
|
|
196
|
+
reason: interaction.reason
|
|
197
|
+
},
|
|
198
|
+
...interaction.outcome !== "accepted" && { isError: true }
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
};
|
|
174
202
|
const convertFilePart = (part) => {
|
|
175
203
|
if (part.url === void 0) return null;
|
|
176
204
|
return {
|
|
@@ -193,9 +221,8 @@ const convertAssistantPart = (part) => {
|
|
|
193
221
|
...status && { status }
|
|
194
222
|
};
|
|
195
223
|
}
|
|
196
|
-
case "
|
|
197
|
-
case "
|
|
198
|
-
case "authorization": return convertAuthorizationPart(part);
|
|
224
|
+
case "tool": return convertToolPart(part);
|
|
225
|
+
case "interaction": return convertInteractionPart(part);
|
|
199
226
|
case "file": return convertFilePart(part);
|
|
200
227
|
default: return null;
|
|
201
228
|
}
|
|
@@ -265,40 +292,43 @@ const convertEveMessageWithInterruptions = (message, index, messages, options, i
|
|
|
265
292
|
};
|
|
266
293
|
return fromThreadMessageLike(like, message.id, toMessageStatus(message, index, messages, options, interruptedTurns));
|
|
267
294
|
};
|
|
268
|
-
const isTurnInterruption = (event) => event.type === "turn.
|
|
269
|
-
/**
|
|
270
|
-
* Collects the `turn.failed` and `turn.cancelled` events of an append-only
|
|
271
|
-
* event log, scanning only the events appended since the cached call. The
|
|
272
|
-
* returned array keeps its identity until a new interruption arrives.
|
|
273
|
-
*/
|
|
295
|
+
const isTurnInterruption = (event) => event.type === "turn.settled" && event.data.outcome !== "completed" || event.type === "session.ended" && event.data.outcome === "failed";
|
|
274
296
|
const collectInterruptedTurnEvents = (events, cache) => {
|
|
275
297
|
if (events === cache.lastEvents) return cache.interruptions;
|
|
276
298
|
const scanned = cache.lastEvents;
|
|
277
299
|
const resumesScan = scanned.length === 0 || events[scanned.length - 1] === scanned[scanned.length - 1];
|
|
278
|
-
|
|
300
|
+
const interruptions = resumesScan ? cache.interruptions : [];
|
|
301
|
+
let nextInterruptions;
|
|
279
302
|
for (let i = resumesScan ? scanned.length : 0; i < events.length; i++) {
|
|
280
303
|
const event = events[i];
|
|
281
|
-
if (isTurnInterruption(event))
|
|
304
|
+
if (isTurnInterruption(event)) {
|
|
305
|
+
nextInterruptions ??= [...interruptions];
|
|
306
|
+
nextInterruptions.push(event);
|
|
307
|
+
}
|
|
282
308
|
}
|
|
283
309
|
cache.lastEvents = events;
|
|
284
|
-
cache.interruptions = interruptions;
|
|
285
|
-
return interruptions;
|
|
310
|
+
cache.interruptions = nextInterruptions ?? interruptions;
|
|
311
|
+
return cache.interruptions;
|
|
286
312
|
};
|
|
287
313
|
const getInterruptedTurns = (events) => {
|
|
288
314
|
const interrupted = /* @__PURE__ */ new Map();
|
|
289
|
-
|
|
315
|
+
const sessionFailures = /* @__PURE__ */ new Map();
|
|
316
|
+
for (const event of events ?? []) if (event.type === "turn.settled" && event.data.outcome === "failed") interrupted.set(event.data.turnId, {
|
|
290
317
|
type: "incomplete",
|
|
291
318
|
reason: "error",
|
|
292
|
-
error:
|
|
293
|
-
code: event.data.code,
|
|
294
|
-
message: event.data.message
|
|
295
|
-
}
|
|
319
|
+
...event.data.error && { error: toAssistantError(event.data.error) }
|
|
296
320
|
});
|
|
297
|
-
else if (event.type === "turn.cancelled") interrupted.set(event.data.turnId, ASSISTANT_CANCELLED_STATUS);
|
|
321
|
+
else if (event.type === "turn.settled" && event.data.outcome === "cancelled") interrupted.set(event.data.turnId, ASSISTANT_CANCELLED_STATUS);
|
|
322
|
+
else if (event.type === "session.ended" && event.data.outcome === "failed" && event.data.cause && "turnId" in event.data.cause) sessionFailures.set(event.data.cause.turnId, {
|
|
323
|
+
type: "incomplete",
|
|
324
|
+
reason: "error",
|
|
325
|
+
...event.data.error && { error: toAssistantError(event.data.error) }
|
|
326
|
+
});
|
|
327
|
+
for (const [turnId, status] of sessionFailures) interrupted.set(turnId, status);
|
|
298
328
|
return interrupted;
|
|
299
329
|
};
|
|
300
330
|
/**
|
|
301
|
-
* Converts
|
|
331
|
+
* Converts Eve's conversation messages into assistant-ui thread messages.
|
|
302
332
|
*/
|
|
303
333
|
const convertEveMessages = (data, options = {}) => {
|
|
304
334
|
const interruptedTurns = getInterruptedTurns(options.events);
|
|
@@ -358,63 +388,62 @@ const getEveMessageContent = (message) => {
|
|
|
358
388
|
return parts;
|
|
359
389
|
};
|
|
360
390
|
/**
|
|
361
|
-
* Finds the
|
|
362
|
-
*
|
|
391
|
+
* Finds the request of the interaction answered by the given approval id, so
|
|
392
|
+
* the answer mapping can honor the request's display mode.
|
|
363
393
|
*/
|
|
364
394
|
const findEveInputRequest = (data, approvalId) => {
|
|
365
|
-
for (const message of data.messages) for (const part of message.parts)
|
|
395
|
+
for (const message of data.messages) for (const part of message.parts) {
|
|
396
|
+
const interaction = part.type === "interaction" ? part : part.type === "tool" ? part.interaction : void 0;
|
|
397
|
+
if (interaction?.interactionId === approvalId) return interaction.request;
|
|
398
|
+
}
|
|
366
399
|
};
|
|
367
400
|
/**
|
|
368
|
-
* Converts an assistant-ui tool approval response into an Eve
|
|
401
|
+
* Converts an assistant-ui tool approval response into an Eve `Answer`.
|
|
369
402
|
*
|
|
370
|
-
* When `inputRequest` is known, every returned
|
|
371
|
-
*
|
|
372
|
-
*
|
|
403
|
+
* When `inputRequest` is known, every returned answer carries an option the
|
|
404
|
+
* request declares, the literal `"approve"` / `"deny"` for an approval that
|
|
405
|
+
* declares no options, or a free-form answer. A literal option match wins,
|
|
406
|
+
* then the `"approve"` / `"deny"` option the response's boolean decision
|
|
373
407
|
* names, then the response's `text` (or its `reason`, for a caller that
|
|
374
408
|
* predates the first-class answer field) when the request takes a free-form
|
|
375
409
|
* answer (`display: "text"`, `allowFreeform`, or no options at all, and never
|
|
376
410
|
* `display: "confirmation"`) and the response is not a refusal.
|
|
377
|
-
* A one-argument call, or a part with no request, still forwards a caller
|
|
378
|
-
* `optionId` as it always has, including ids the request never declared.
|
|
379
411
|
*
|
|
380
412
|
* A response the known request cannot record as the answer it asked for throws
|
|
381
|
-
* instead of being submitted, because
|
|
382
|
-
*
|
|
383
|
-
* content. Not sending leaves the
|
|
413
|
+
* instead of being submitted, because Eve resolves an interaction the moment
|
|
414
|
+
* any answer for it arrives, and an empty one is recorded as an answer with no
|
|
415
|
+
* content. Not sending leaves the interaction open, so the caller can retry or
|
|
384
416
|
* the user can answer it as an ordinary message.
|
|
385
417
|
*
|
|
386
|
-
* With no request to map against
|
|
387
|
-
* `
|
|
388
|
-
*
|
|
389
|
-
* a display mode is what this mapper stopped doing; an unknown request is not
|
|
390
|
-
* a guess about one.
|
|
418
|
+
* With no request to map against, a one-argument call forwards a caller
|
|
419
|
+
* `optionId` as is and otherwise maps the decision to the literal `"approve"` /
|
|
420
|
+
* `"deny"` option every Eve approval declares.
|
|
391
421
|
*/
|
|
392
422
|
const toEveInputResponse = (response, inputRequest) => {
|
|
393
|
-
const
|
|
423
|
+
const interactionId = response.approvalId;
|
|
394
424
|
const options = inputRequest?.options;
|
|
395
425
|
const text = response.text ?? response.reason;
|
|
396
426
|
if (response.optionId !== void 0) {
|
|
397
427
|
if (!inputRequest || options?.some((option) => option.id === response.optionId)) return {
|
|
398
|
-
|
|
428
|
+
interactionId,
|
|
399
429
|
optionId: response.optionId,
|
|
400
430
|
...text && { text }
|
|
401
431
|
};
|
|
402
|
-
throw new Error(`Eve
|
|
432
|
+
throw new Error(`Eve interaction "${interactionId}" has no option with id "${response.optionId}"; respond with one of: ${options?.length ? options.map((option) => option.id).join(", ") : "(the request carries no options)"}`);
|
|
403
433
|
}
|
|
404
|
-
const decisionOptionId = response.approved ?
|
|
405
|
-
if (!inputRequest || inputRequest.kind === "
|
|
406
|
-
|
|
434
|
+
const decisionOptionId = response.approved ? APPROVE_OPTION_ID : DENY_OPTION_ID;
|
|
435
|
+
if (!inputRequest || inputRequest.kind === "approval" && !options?.length || options?.some((option) => option.id === decisionOptionId)) return {
|
|
436
|
+
interactionId,
|
|
407
437
|
optionId: decisionOptionId,
|
|
408
438
|
...text && { text }
|
|
409
439
|
};
|
|
410
440
|
const acceptsText = acceptsFreeformAnswer(inputRequest);
|
|
411
441
|
if (acceptsText && text && response.approved !== false) return {
|
|
412
|
-
|
|
442
|
+
interactionId,
|
|
413
443
|
text
|
|
414
444
|
};
|
|
415
|
-
throw new Error(`Eve
|
|
445
|
+
throw new Error(`Eve interaction "${interactionId}" (${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 text" : "pass the answer as the response text, because the request takes a free-form answer"}`);
|
|
416
446
|
};
|
|
417
|
-
//#endregion
|
|
418
447
|
export { collectInterruptedTurnEvents, convertEveMessage, convertEveMessages, findEveInputRequest, getEveMessageContent, toEveInputResponse };
|
|
419
448
|
|
|
420
449
|
//# sourceMappingURL=convertEveMessages.js.map
|