@ekairos/events 1.22.32-beta.development.0 → 1.22.33-beta.development.0
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/README.md +61 -0
- package/dist/context.engine.js +29 -7
- package/dist/context.events.d.ts +20 -0
- package/dist/context.events.js +352 -7
- package/dist/context.parts.d.ts +241 -0
- package/dist/context.parts.js +360 -0
- package/dist/context.store.d.ts +5 -0
- package/dist/context.toolcalls.js +55 -11
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/schema.js +3 -2
- package/dist/stores/instant.store.d.ts +2 -0
- package/dist/stores/instant.store.js +100 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,6 +23,67 @@ Context-first durable execution runtime for Ekairos.
|
|
|
23
23
|
|
|
24
24
|
The aggregate is `context`. Executions, steps, parts, and items are scoped to a context.
|
|
25
25
|
|
|
26
|
+
## Canonical Parts
|
|
27
|
+
|
|
28
|
+
`event_parts` is the canonical content model for produced output.
|
|
29
|
+
|
|
30
|
+
Rules:
|
|
31
|
+
|
|
32
|
+
- `event_parts.part` is the source of truth for replay and inspection.
|
|
33
|
+
- `event_items.content.parts` on output items is maintained as a compatibility mirror and is deprecated as a replay source.
|
|
34
|
+
- Provider/model-specific values must live under `metadata`, never as first-class semantic fields.
|
|
35
|
+
|
|
36
|
+
Canonical part kinds:
|
|
37
|
+
|
|
38
|
+
- `content`
|
|
39
|
+
- `reasoning`
|
|
40
|
+
- `source`
|
|
41
|
+
- `tool-call`
|
|
42
|
+
- `tool-result`
|
|
43
|
+
|
|
44
|
+
Each canonical part stores a `content` array. The entries inside that array define the payload type:
|
|
45
|
+
|
|
46
|
+
- `text`
|
|
47
|
+
- `file`
|
|
48
|
+
- `json`
|
|
49
|
+
- `source-url`
|
|
50
|
+
- `source-document`
|
|
51
|
+
|
|
52
|
+
Example tool result:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
{
|
|
56
|
+
type: "tool-result",
|
|
57
|
+
toolCallId: "call_123",
|
|
58
|
+
toolName: "inspectCanvasRegion",
|
|
59
|
+
state: "output-available",
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: "Zoomed crop of the requested region.",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: "file",
|
|
67
|
+
mediaType: "image/png",
|
|
68
|
+
filename: "inspect-region.png",
|
|
69
|
+
data: "iVBORw0KGgoAAAANSUhEUgAA...",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
metadata: {
|
|
73
|
+
provider: {
|
|
74
|
+
itemId: "fc_041cb...",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The AI SDK bridge projects canonical parts to:
|
|
81
|
+
|
|
82
|
+
- assistant messages with text/file/reasoning/source/tool-call parts
|
|
83
|
+
- tool messages with `tool-result` or `tool-error`
|
|
84
|
+
|
|
85
|
+
That means multipart tool outputs are replayed from `event_parts` instead of relying on the deprecated output-item mirror.
|
|
86
|
+
|
|
26
87
|
## Install
|
|
27
88
|
|
|
28
89
|
```bash
|
package/dist/context.engine.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerContextEnv } from "./env.js";
|
|
2
2
|
import { OUTPUT_ITEM_TYPE, WEB_CHANNEL } from "./context.events.js";
|
|
3
3
|
import { applyToolExecutionResultToParts } from "./context.toolcalls.js";
|
|
4
|
+
import { isContextPartEnvelope, normalizePartsForPersistence, } from "./context.parts.js";
|
|
4
5
|
import { toolsToModelTools } from "./tools-to-model-tools.js";
|
|
5
6
|
import { createAiSdkReactor, } from "./context.reactor.js";
|
|
6
7
|
import { abortPersistedContextStepStream, closePersistedContextStepStream, createPersistedContextStepStream, closeContextStream, } from "./steps/stream.steps.js";
|
|
@@ -22,6 +23,20 @@ function clipPreview(value, max = 240) {
|
|
|
22
23
|
function summarizePartPreview(part) {
|
|
23
24
|
if (!part || typeof part !== "object")
|
|
24
25
|
return {};
|
|
26
|
+
if (isContextPartEnvelope(part)) {
|
|
27
|
+
const preview = part.content[0]?.type === "text"
|
|
28
|
+
? part.content[0].text
|
|
29
|
+
: JSON.stringify(part.content[0] ?? part);
|
|
30
|
+
const state = "state" in part && typeof part.state === "string" ? part.state : undefined;
|
|
31
|
+
const toolCallId = "toolCallId" in part && typeof part.toolCallId === "string"
|
|
32
|
+
? part.toolCallId
|
|
33
|
+
: undefined;
|
|
34
|
+
return {
|
|
35
|
+
partPreview: preview ? clipPreview(preview) : undefined,
|
|
36
|
+
partState: state,
|
|
37
|
+
partToolCallId: toolCallId,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
25
40
|
const row = part;
|
|
26
41
|
const partType = typeof row.type === "string" ? row.type : "";
|
|
27
42
|
const partState = typeof row.state === "string" ? row.state : undefined;
|
|
@@ -527,7 +542,7 @@ export class ContextEngine {
|
|
|
527
542
|
: [];
|
|
528
543
|
let persistedReactionPartsSignature = "";
|
|
529
544
|
const persistReactionParts = async (nextParts) => {
|
|
530
|
-
const normalizedParts = Array.isArray(nextParts) ? nextParts : [];
|
|
545
|
+
const normalizedParts = normalizePartsForPersistence(Array.isArray(nextParts) ? nextParts : []);
|
|
531
546
|
const nextSignature = JSON.stringify(normalizedParts);
|
|
532
547
|
if (nextSignature === persistedReactionPartsSignature)
|
|
533
548
|
return;
|
|
@@ -591,7 +606,7 @@ export class ContextEngine {
|
|
|
591
606
|
// We intentionally do NOT persist the per-step LLM assistant event as a `context_event`.
|
|
592
607
|
// The story exposes a single visible `context_event` per turn (`reactionEventId`) so the UI
|
|
593
608
|
// doesn't render duplicate assistant messages (LLM-step + aggregated reaction).
|
|
594
|
-
const stepParts = (assistantEvent?.content?.parts ?? []);
|
|
609
|
+
const stepParts = normalizePartsForPersistence((assistantEvent?.content?.parts ?? []));
|
|
595
610
|
const assistantEventEffective = {
|
|
596
611
|
...assistantEvent,
|
|
597
612
|
content: {
|
|
@@ -836,11 +851,9 @@ export class ContextEngine {
|
|
|
836
851
|
}
|
|
837
852
|
})));
|
|
838
853
|
// Merge action results into persisted parts (so next LLM call can see them)
|
|
839
|
-
let
|
|
840
|
-
? [...reactionEvent.content.parts]
|
|
841
|
-
: [];
|
|
854
|
+
let finalizedStepParts = Array.isArray(stepParts) ? [...stepParts] : [];
|
|
842
855
|
for (const r of actionResults) {
|
|
843
|
-
|
|
856
|
+
finalizedStepParts = applyToolExecutionResultToParts(finalizedStepParts, {
|
|
844
857
|
toolCallId: r.actionRequest.actionRef,
|
|
845
858
|
toolName: r.actionRequest.actionName,
|
|
846
859
|
}, {
|
|
@@ -849,11 +862,20 @@ export class ContextEngine {
|
|
|
849
862
|
message: r.errorText,
|
|
850
863
|
});
|
|
851
864
|
}
|
|
865
|
+
await measureBenchmark(params.__benchmark, `${stagePrefix}.saveFinalStepPartsMs`, async () => await ops.saveContextPartsStep({
|
|
866
|
+
stepId: stepCreate.stepId,
|
|
867
|
+
parts: finalizedStepParts,
|
|
868
|
+
executionId,
|
|
869
|
+
contextId: String(currentContext.id),
|
|
870
|
+
iteration: iter,
|
|
871
|
+
}));
|
|
852
872
|
reactionEvent = {
|
|
853
873
|
...reactionEvent,
|
|
854
874
|
content: {
|
|
855
875
|
...reactionEvent.content,
|
|
856
|
-
|
|
876
|
+
// Deprecated mirror for compatibility. `event_parts` are the
|
|
877
|
+
// source of truth for replay and step inspection.
|
|
878
|
+
parts: [...reactionPartsBeforeStep, ...finalizedStepParts],
|
|
857
879
|
},
|
|
858
880
|
status: "pending",
|
|
859
881
|
};
|
package/dist/context.events.d.ts
CHANGED
|
@@ -6,6 +6,26 @@ export declare const INPUT_TEXT_ITEM_TYPE = "input";
|
|
|
6
6
|
export declare const WEB_CHANNEL = "web";
|
|
7
7
|
export declare const AGENT_CHANNEL = "whatsapp";
|
|
8
8
|
export declare const EMAIL_CHANNEL = "email";
|
|
9
|
+
export type ContextOutputContentPart = {
|
|
10
|
+
type: "text";
|
|
11
|
+
text: string;
|
|
12
|
+
} | ({
|
|
13
|
+
type: "image-data";
|
|
14
|
+
data: string;
|
|
15
|
+
mediaType: string;
|
|
16
|
+
filename?: string;
|
|
17
|
+
} & Record<string, unknown>) | ({
|
|
18
|
+
type: string;
|
|
19
|
+
} & Record<string, unknown>);
|
|
20
|
+
export type ContextOutputPart = {
|
|
21
|
+
type: "json";
|
|
22
|
+
value: unknown;
|
|
23
|
+
} | {
|
|
24
|
+
type: "content";
|
|
25
|
+
value: ContextOutputContentPart[];
|
|
26
|
+
};
|
|
27
|
+
export declare function isContextOutputPart(value: unknown): value is ContextOutputPart;
|
|
28
|
+
export declare function normalizeContextOutputPart(value: unknown): ContextOutputPart;
|
|
9
29
|
export declare function createUserItemFromUIMessages(messages: UIMessage[]): ContextItem;
|
|
10
30
|
export declare function createAssistantItemFromUIMessages(itemId: string, messages: UIMessage[]): ContextItem;
|
|
11
31
|
export declare function convertToUIMessage(item: ContextItem): UIMessage;
|
package/dist/context.events.js
CHANGED
|
@@ -1,10 +1,322 @@
|
|
|
1
1
|
import { convertToModelMessages } from "ai";
|
|
2
|
+
import { isContextPartEnvelope, normalizePartsForPersistence, } from "./context.parts.js";
|
|
2
3
|
export const INPUT_ITEM_TYPE = "input";
|
|
3
4
|
export const OUTPUT_ITEM_TYPE = "output";
|
|
4
5
|
export const INPUT_TEXT_ITEM_TYPE = INPUT_ITEM_TYPE;
|
|
5
6
|
export const WEB_CHANNEL = "web";
|
|
6
7
|
export const AGENT_CHANNEL = "whatsapp";
|
|
7
8
|
export const EMAIL_CHANNEL = "email";
|
|
9
|
+
function asRecord(value) {
|
|
10
|
+
if (!value || typeof value !== "object")
|
|
11
|
+
return null;
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function isContextOutputContentPart(value) {
|
|
15
|
+
const record = asRecord(value);
|
|
16
|
+
return Boolean(record && typeof record.type === "string");
|
|
17
|
+
}
|
|
18
|
+
export function isContextOutputPart(value) {
|
|
19
|
+
const record = asRecord(value);
|
|
20
|
+
if (!record || typeof record.type !== "string") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (record.type === "json") {
|
|
24
|
+
return "value" in record;
|
|
25
|
+
}
|
|
26
|
+
if (record.type === "content") {
|
|
27
|
+
return Array.isArray(record.value) && record.value.every(isContextOutputContentPart);
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
export function normalizeContextOutputPart(value) {
|
|
32
|
+
if (isContextOutputPart(value)) {
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
type: "json",
|
|
37
|
+
value,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function isToolUIPart(value) {
|
|
41
|
+
const record = asRecord(value);
|
|
42
|
+
return Boolean(record && typeof record.type === "string" && record.type.startsWith("tool-"));
|
|
43
|
+
}
|
|
44
|
+
function readToolNameFromPart(part) {
|
|
45
|
+
return String(part.type).split("-").slice(1).join("-");
|
|
46
|
+
}
|
|
47
|
+
function stripDataUrlPrefix(value) {
|
|
48
|
+
return value.replace(/^data:[^;]+;base64,/i, "");
|
|
49
|
+
}
|
|
50
|
+
function asCanonicalParts(parts) {
|
|
51
|
+
return normalizePartsForPersistence(parts);
|
|
52
|
+
}
|
|
53
|
+
function contentBlockToPrimaryUiParts(block) {
|
|
54
|
+
if (block.type === "text") {
|
|
55
|
+
return [{ type: "text", text: block.text }];
|
|
56
|
+
}
|
|
57
|
+
if (block.type === "file") {
|
|
58
|
+
const url = typeof block.url === "string" && block.url.length > 0
|
|
59
|
+
? block.url
|
|
60
|
+
: typeof block.data === "string" && block.data.length > 0
|
|
61
|
+
? block.data.startsWith("data:")
|
|
62
|
+
? block.data
|
|
63
|
+
: `data:${block.mediaType};base64,${block.data}`
|
|
64
|
+
: typeof block.fileId === "string" && block.fileId.length > 0
|
|
65
|
+
? block.fileId
|
|
66
|
+
: "";
|
|
67
|
+
if (!url) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
type: "file",
|
|
73
|
+
mediaType: block.mediaType,
|
|
74
|
+
filename: block.filename,
|
|
75
|
+
url,
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
if (block.type === "json") {
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
type: "text",
|
|
83
|
+
text: JSON.stringify(block.value, null, 2),
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
if (block.type === "source-url") {
|
|
88
|
+
return [
|
|
89
|
+
{
|
|
90
|
+
type: "source-url",
|
|
91
|
+
sourceId: block.sourceId,
|
|
92
|
+
url: block.url,
|
|
93
|
+
title: block.title,
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
if (block.type === "source-document") {
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
type: "source-document",
|
|
101
|
+
sourceId: block.sourceId,
|
|
102
|
+
mediaType: block.mediaType,
|
|
103
|
+
title: block.title,
|
|
104
|
+
filename: block.filename,
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
function toolCallContentToInput(content) {
|
|
111
|
+
if (content.length === 0)
|
|
112
|
+
return undefined;
|
|
113
|
+
if (content.length === 1) {
|
|
114
|
+
const first = content[0];
|
|
115
|
+
if (first.type === "json")
|
|
116
|
+
return first.value;
|
|
117
|
+
if (first.type === "text")
|
|
118
|
+
return first.text;
|
|
119
|
+
return first;
|
|
120
|
+
}
|
|
121
|
+
return content;
|
|
122
|
+
}
|
|
123
|
+
function canonicalPartsToPrimaryUiParts(parts) {
|
|
124
|
+
const uiParts = [];
|
|
125
|
+
for (const part of parts) {
|
|
126
|
+
if (part.type === "content") {
|
|
127
|
+
uiParts.push(...part.content.flatMap((block) => contentBlockToPrimaryUiParts(block)));
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (part.type === "reasoning") {
|
|
131
|
+
const text = part.content
|
|
132
|
+
.filter((block) => block.type === "text")
|
|
133
|
+
.map((block) => block.text)
|
|
134
|
+
.join("\n\n");
|
|
135
|
+
if (text.trim()) {
|
|
136
|
+
uiParts.push({
|
|
137
|
+
type: "reasoning",
|
|
138
|
+
text,
|
|
139
|
+
state: part.state,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (part.type === "source") {
|
|
145
|
+
uiParts.push(...part.content.flatMap((block) => contentBlockToPrimaryUiParts(block)));
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (part.type === "tool-call") {
|
|
149
|
+
uiParts.push({
|
|
150
|
+
type: `tool-${part.toolName}`,
|
|
151
|
+
toolCallId: part.toolCallId,
|
|
152
|
+
state: part.state ?? "input-available",
|
|
153
|
+
input: toolCallContentToInput(part.content),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return uiParts;
|
|
158
|
+
}
|
|
159
|
+
function canonicalToolResultContentToOutput(content) {
|
|
160
|
+
if (content.length === 1 && content[0]?.type === "json") {
|
|
161
|
+
return {
|
|
162
|
+
type: "json",
|
|
163
|
+
value: content[0].value,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
type: "content",
|
|
168
|
+
value: content.map((block) => {
|
|
169
|
+
if (block.type === "text") {
|
|
170
|
+
return {
|
|
171
|
+
type: "text",
|
|
172
|
+
text: block.text,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (block.type === "file") {
|
|
176
|
+
if (block.mediaType.startsWith("image/") &&
|
|
177
|
+
typeof block.data === "string" &&
|
|
178
|
+
block.data.length > 0) {
|
|
179
|
+
return {
|
|
180
|
+
type: "image-data",
|
|
181
|
+
data: stripDataUrlPrefix(block.data),
|
|
182
|
+
mediaType: block.mediaType,
|
|
183
|
+
filename: block.filename,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
type: "file",
|
|
188
|
+
mediaType: block.mediaType,
|
|
189
|
+
filename: block.filename,
|
|
190
|
+
data: typeof block.data === "string" && block.data.length > 0
|
|
191
|
+
? block.data
|
|
192
|
+
: typeof block.url === "string" && block.url.length > 0
|
|
193
|
+
? block.url
|
|
194
|
+
: block.fileId,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (block.type === "json") {
|
|
198
|
+
return {
|
|
199
|
+
type: "text",
|
|
200
|
+
text: JSON.stringify(block.value, null, 2),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
type: "text",
|
|
205
|
+
text: JSON.stringify(block),
|
|
206
|
+
};
|
|
207
|
+
}),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function canonicalToolPartsToModelMessages(parts) {
|
|
211
|
+
const toolInputs = new Map();
|
|
212
|
+
const toolResults = [];
|
|
213
|
+
for (const part of parts) {
|
|
214
|
+
if (part.type === "tool-call") {
|
|
215
|
+
toolInputs.set(part.toolCallId, toolCallContentToInput(part.content));
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (part.type !== "tool-result") {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (part.state === "output-error") {
|
|
222
|
+
const text = part.content
|
|
223
|
+
.filter((block) => block.type === "text")
|
|
224
|
+
.map((block) => block.text)
|
|
225
|
+
.join("\n\n");
|
|
226
|
+
toolResults.push({
|
|
227
|
+
type: "tool-error",
|
|
228
|
+
toolCallId: part.toolCallId,
|
|
229
|
+
toolName: part.toolName,
|
|
230
|
+
input: toolInputs.get(part.toolCallId),
|
|
231
|
+
error: text || "Tool execution failed.",
|
|
232
|
+
});
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
toolResults.push({
|
|
236
|
+
type: "tool-result",
|
|
237
|
+
toolCallId: part.toolCallId,
|
|
238
|
+
toolName: part.toolName,
|
|
239
|
+
output: canonicalToolResultContentToOutput(part.content),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (toolResults.length === 0) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
return [
|
|
246
|
+
{
|
|
247
|
+
role: "tool",
|
|
248
|
+
content: toolResults,
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
function canonicalPartsToModelMessages(role, parts) {
|
|
253
|
+
const uiMessage = {
|
|
254
|
+
id: "canonical-item",
|
|
255
|
+
role,
|
|
256
|
+
parts: canonicalPartsToPrimaryUiParts(parts),
|
|
257
|
+
};
|
|
258
|
+
return removeEmptyToolMessages(convertToModelMessages([uiMessage]));
|
|
259
|
+
}
|
|
260
|
+
function normalizeAssistantPartsForModel(parts) {
|
|
261
|
+
return parts
|
|
262
|
+
.map((part) => {
|
|
263
|
+
if (!isToolUIPart(part)) {
|
|
264
|
+
return part;
|
|
265
|
+
}
|
|
266
|
+
const next = {
|
|
267
|
+
...part,
|
|
268
|
+
state: part.state === "output-available" || part.state === "output-error"
|
|
269
|
+
? "input-available"
|
|
270
|
+
: part.state,
|
|
271
|
+
};
|
|
272
|
+
delete next.output;
|
|
273
|
+
delete next.errorText;
|
|
274
|
+
return next;
|
|
275
|
+
})
|
|
276
|
+
.filter(Boolean);
|
|
277
|
+
}
|
|
278
|
+
function buildToolResultContent(parts) {
|
|
279
|
+
const toolContent = [];
|
|
280
|
+
for (const part of parts) {
|
|
281
|
+
if (!isToolUIPart(part)) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const toolCallId = typeof part.toolCallId === "string" ? part.toolCallId : "";
|
|
285
|
+
const toolName = readToolNameFromPart(part);
|
|
286
|
+
if (!toolCallId || !toolName) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (part.state === "output-available") {
|
|
290
|
+
toolContent.push({
|
|
291
|
+
type: "tool-result",
|
|
292
|
+
toolCallId,
|
|
293
|
+
toolName,
|
|
294
|
+
output: normalizeContextOutputPart(part.output),
|
|
295
|
+
});
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (part.state === "output-error") {
|
|
299
|
+
toolContent.push({
|
|
300
|
+
type: "tool-error",
|
|
301
|
+
toolCallId,
|
|
302
|
+
toolName,
|
|
303
|
+
input: part.input,
|
|
304
|
+
error: typeof part.errorText === "string" && part.errorText.trim().length > 0
|
|
305
|
+
? part.errorText
|
|
306
|
+
: "Tool execution failed.",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return toolContent;
|
|
311
|
+
}
|
|
312
|
+
function removeEmptyToolMessages(messages) {
|
|
313
|
+
return messages.filter((message) => {
|
|
314
|
+
if (message.role !== "tool") {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
return Array.isArray(message.content) ? message.content.length > 0 : true;
|
|
318
|
+
});
|
|
319
|
+
}
|
|
8
320
|
export function createUserItemFromUIMessages(messages) {
|
|
9
321
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
10
322
|
throw new Error("Missing messages to create item");
|
|
@@ -15,7 +327,7 @@ export function createUserItemFromUIMessages(messages) {
|
|
|
15
327
|
type: INPUT_ITEM_TYPE,
|
|
16
328
|
channel: WEB_CHANNEL,
|
|
17
329
|
content: {
|
|
18
|
-
parts: lastMessage.parts,
|
|
330
|
+
parts: asCanonicalParts(lastMessage.parts),
|
|
19
331
|
},
|
|
20
332
|
createdAt: new Date().toISOString(),
|
|
21
333
|
};
|
|
@@ -30,16 +342,17 @@ export function createAssistantItemFromUIMessages(itemId, messages) {
|
|
|
30
342
|
type: OUTPUT_ITEM_TYPE,
|
|
31
343
|
channel: WEB_CHANNEL,
|
|
32
344
|
content: {
|
|
33
|
-
parts: lastMessage.parts,
|
|
345
|
+
parts: asCanonicalParts(lastMessage.parts),
|
|
34
346
|
},
|
|
35
347
|
createdAt: new Date().toISOString(),
|
|
36
348
|
};
|
|
37
349
|
}
|
|
38
350
|
export function convertToUIMessage(item) {
|
|
39
351
|
const role = item.type === INPUT_ITEM_TYPE ? "user" : "assistant";
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
352
|
+
const rawParts = Array.isArray(item.content.parts) ? item.content.parts : [];
|
|
353
|
+
const parts = rawParts.every(isContextPartEnvelope)
|
|
354
|
+
? canonicalPartsToPrimaryUiParts(rawParts)
|
|
355
|
+
: rawParts;
|
|
43
356
|
return {
|
|
44
357
|
id: item.id,
|
|
45
358
|
role,
|
|
@@ -67,8 +380,40 @@ export async function convertItemsToModelMessages(items) {
|
|
|
67
380
|
return results.flat();
|
|
68
381
|
}
|
|
69
382
|
export async function convertItemToModelMessages(item) {
|
|
70
|
-
const
|
|
71
|
-
|
|
383
|
+
const role = item.type === INPUT_ITEM_TYPE ? "user" : "assistant";
|
|
384
|
+
const rawParts = Array.isArray(item.content.parts) ? item.content.parts : [];
|
|
385
|
+
const canonicalParts = asCanonicalParts(rawParts);
|
|
386
|
+
if (canonicalParts.length > 0) {
|
|
387
|
+
const primary = await canonicalPartsToModelMessages(role, canonicalParts);
|
|
388
|
+
if (role !== "assistant") {
|
|
389
|
+
return primary;
|
|
390
|
+
}
|
|
391
|
+
return [
|
|
392
|
+
...primary,
|
|
393
|
+
...canonicalToolPartsToModelMessages(canonicalParts),
|
|
394
|
+
];
|
|
395
|
+
}
|
|
396
|
+
const assistantParts = normalizeAssistantPartsForModel(rawParts);
|
|
397
|
+
const message = {
|
|
398
|
+
id: item.id,
|
|
399
|
+
role,
|
|
400
|
+
parts: assistantParts,
|
|
401
|
+
};
|
|
402
|
+
const modelMessages = removeEmptyToolMessages(await convertToModelMessages([message]));
|
|
403
|
+
if (role !== "assistant") {
|
|
404
|
+
return modelMessages;
|
|
405
|
+
}
|
|
406
|
+
const toolContent = buildToolResultContent(rawParts);
|
|
407
|
+
if (toolContent.length === 0) {
|
|
408
|
+
return modelMessages;
|
|
409
|
+
}
|
|
410
|
+
return [
|
|
411
|
+
...modelMessages,
|
|
412
|
+
{
|
|
413
|
+
role: "tool",
|
|
414
|
+
content: toolContent,
|
|
415
|
+
},
|
|
416
|
+
];
|
|
72
417
|
}
|
|
73
418
|
function normalizeModelMessageContentToParts(content) {
|
|
74
419
|
if (Array.isArray(content))
|