@truefoundry/trueforge-assistant-ui-runtime 0.0.0 → 0.2.0-rc.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/CHANGELOG.md +9 -0
- package/LICENSE +201 -0
- package/README.md +146 -4
- package/dist/chunk-2SQK6TIO.js +104 -0
- package/dist/chunk-2SQK6TIO.js.map +1 -0
- package/dist/index.d.ts +378 -0
- package/dist/index.js +4391 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.d.ts +1210 -0
- package/dist/server/index.js +9 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +79 -16
- package/src/askUserQuestion.ts +38 -0
- package/src/attachmentAdapter.ts +63 -0
- package/src/collectPending.ts +167 -0
- package/src/constants.ts +2 -0
- package/src/convertTurnMessages.ts +1679 -0
- package/src/createSubAgent.ts +11 -0
- package/src/draft/agentSpec.ts +34 -0
- package/src/draft/draftSessionBridge.ts +28 -0
- package/src/draft/trueforgeDraftThreadListAdapter.ts +73 -0
- package/src/draft/useDraftAgentSpec.ts +289 -0
- package/src/extractTurnUserText.ts +23 -0
- package/src/foldPeerThreads.ts +553 -0
- package/src/hooks.ts +176 -0
- package/src/index.ts +227 -0
- package/src/lastUserMessageText.ts +19 -0
- package/src/listPages.ts +19 -0
- package/src/loadSessionSnapshot.ts +34 -0
- package/src/mcpAuth.ts +35 -0
- package/src/messageCustomMetadata.ts +50 -0
- package/src/modelMessageContent.ts +149 -0
- package/src/modelMessageImageContent.ts +154 -0
- package/src/requiredActionInputs.ts +38 -0
- package/src/sandboxDownload.ts +33 -0
- package/src/server/eventUtils.ts +125 -0
- package/src/server/events.ts +232 -0
- package/src/server/index.ts +178 -0
- package/src/server/types.ts +1191 -0
- package/src/sessionListStartTimestamp.ts +6 -0
- package/src/sessionSnapshot.ts +146 -0
- package/src/sessionThreadMetadata.ts +36 -0
- package/src/sessions.ts +17 -0
- package/src/streamTurn.ts +118 -0
- package/src/toolApproval.ts +413 -0
- package/src/toolResponse.ts +346 -0
- package/src/trueforgeExtras.ts +223 -0
- package/src/trueforgeOwnedSessionsThreadListAdapter.ts +71 -0
- package/src/trueforgeThreadListAdapter.ts +69 -0
- package/src/turnEventHelpers.ts +71 -0
- package/src/turnStreamUpdate.ts +11 -0
- package/src/types.ts +84 -0
- package/src/useTrueForgeAgentMessages.ts +1138 -0
- package/src/useTrueForgeAgentRuntime.ts +308 -0
- package/index.js +0 -6
package/dist/index.js
ADDED
|
@@ -0,0 +1,4391 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isEventDelta,
|
|
3
|
+
mergeEventDelta
|
|
4
|
+
} from "./chunk-2SQK6TIO.js";
|
|
5
|
+
|
|
6
|
+
// src/attachmentAdapter.ts
|
|
7
|
+
var bytesToBase64 = (bytes) => globalThis.Buffer.from(bytes).toString("base64");
|
|
8
|
+
var getFileDataURL = async (file) => {
|
|
9
|
+
if (typeof FileReader === "undefined") {
|
|
10
|
+
const buffer = await file.arrayBuffer();
|
|
11
|
+
return `data:${file.type};base64,${bytesToBase64(new Uint8Array(buffer))}`;
|
|
12
|
+
}
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const reader = new FileReader();
|
|
15
|
+
reader.onload = () => {
|
|
16
|
+
if (typeof reader.result === "string") {
|
|
17
|
+
resolve(reader.result);
|
|
18
|
+
} else {
|
|
19
|
+
reject(new Error("Attachment reader returned non-text data."));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
reader.onerror = () => {
|
|
23
|
+
reject(reader.error ?? new Error("Failed to read attachment."));
|
|
24
|
+
};
|
|
25
|
+
reader.readAsDataURL(file);
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
var trueForgeAttachmentAdapter = {
|
|
29
|
+
accept: "*",
|
|
30
|
+
add({ file }) {
|
|
31
|
+
return Promise.resolve({
|
|
32
|
+
id: globalThis.crypto.randomUUID(),
|
|
33
|
+
type: "file",
|
|
34
|
+
name: file.name,
|
|
35
|
+
file,
|
|
36
|
+
contentType: file.type,
|
|
37
|
+
content: [],
|
|
38
|
+
status: {
|
|
39
|
+
type: "requires-action",
|
|
40
|
+
reason: "composer-send"
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
async send(attachment) {
|
|
45
|
+
return {
|
|
46
|
+
...attachment,
|
|
47
|
+
status: { type: "complete" },
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: "file",
|
|
51
|
+
mimeType: attachment.contentType ?? "",
|
|
52
|
+
filename: attachment.name,
|
|
53
|
+
data: await getFileDataURL(attachment.file)
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
remove() {
|
|
59
|
+
return Promise.resolve();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/constants.ts
|
|
64
|
+
var ROOT_THREAD_ID = "main";
|
|
65
|
+
|
|
66
|
+
// src/extractTurnUserText.ts
|
|
67
|
+
function extractTurnUserText(input) {
|
|
68
|
+
const parts = [];
|
|
69
|
+
let hasUserMessage = false;
|
|
70
|
+
for (const item of input ?? []) {
|
|
71
|
+
if (item.type !== "user.message") {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
hasUserMessage = true;
|
|
75
|
+
const { content } = item;
|
|
76
|
+
if (typeof content === "string") {
|
|
77
|
+
parts.push(content);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
for (const part of content) {
|
|
81
|
+
if (part.type === "text") {
|
|
82
|
+
parts.push(part.text);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return hasUserMessage ? parts.join("\n").trim() : void 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/askUserQuestion.ts
|
|
90
|
+
function parseAskUserQuestionArgs(argsText) {
|
|
91
|
+
if (!argsText) {
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const parsed = JSON.parse(argsText);
|
|
96
|
+
if (!isUnknownRecord(parsed)) {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
const rawQuestion = parsed["question"];
|
|
100
|
+
const rawOptions = parsed["options"];
|
|
101
|
+
const question = typeof rawQuestion === "string" ? rawQuestion : void 0;
|
|
102
|
+
const options = Array.isArray(rawOptions) ? rawOptions.filter((item) => typeof item === "string") : void 0;
|
|
103
|
+
return {
|
|
104
|
+
...question == null ? {} : { question },
|
|
105
|
+
...options == null ? {} : { options }
|
|
106
|
+
};
|
|
107
|
+
} catch {
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function isUnknownRecord(value) {
|
|
112
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/createSubAgent.ts
|
|
116
|
+
function isCreateSubAgentToolCall(toolCall) {
|
|
117
|
+
if (toolCall.toolInfo?.type === "trueforge-system" && toolCall.toolInfo.name === "create_sub_agent") {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
return toolCall.function.name === "create_sub_agent";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/modelMessageImageContent.ts
|
|
124
|
+
function parseDataUriMime(data) {
|
|
125
|
+
if (!data.startsWith("data:")) {
|
|
126
|
+
return "image/png";
|
|
127
|
+
}
|
|
128
|
+
const match = /^data:([^;,]+)/.exec(data);
|
|
129
|
+
return match?.[1] ?? "image/png";
|
|
130
|
+
}
|
|
131
|
+
function imageFilenameFromUrl(url, index) {
|
|
132
|
+
const mimeType = parseDataUriMime(url);
|
|
133
|
+
const ext = mimeType.split("/")[1] ?? "png";
|
|
134
|
+
return `image-${String(index + 1)}.${ext}`;
|
|
135
|
+
}
|
|
136
|
+
function isImageUrlContentPart(part) {
|
|
137
|
+
if (!isUnknownRecord2(part) || part["type"] !== "image_url" || !isUnknownRecord2(part["image_url"])) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return typeof part["image_url"]["url"] === "string";
|
|
141
|
+
}
|
|
142
|
+
function isUnknownRecord2(value) {
|
|
143
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
144
|
+
}
|
|
145
|
+
function normalizeModelMessageContent(message) {
|
|
146
|
+
const { content } = message;
|
|
147
|
+
if (content == null) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
if (typeof content === "string") {
|
|
151
|
+
return content.length > 0 ? [{ type: "text", text: content }] : [];
|
|
152
|
+
}
|
|
153
|
+
return content;
|
|
154
|
+
}
|
|
155
|
+
function mergeContentBlockDeltas(message, blocks) {
|
|
156
|
+
const content = normalizeModelMessageContent(message);
|
|
157
|
+
message.content = content;
|
|
158
|
+
for (const block of blocks) {
|
|
159
|
+
const index = block.index;
|
|
160
|
+
while (content.length <= index) {
|
|
161
|
+
content.push({ type: "text", text: "" });
|
|
162
|
+
}
|
|
163
|
+
const delta = block.delta;
|
|
164
|
+
if (delta.type === "text") {
|
|
165
|
+
const existing2 = content[index];
|
|
166
|
+
if (existing2?.type === "text") {
|
|
167
|
+
existing2.text += delta.text ?? "";
|
|
168
|
+
} else {
|
|
169
|
+
content[index] = { type: "text", text: delta.text ?? "" };
|
|
170
|
+
}
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const chunk = delta.image_url?.url ?? "";
|
|
174
|
+
const existing = content[index];
|
|
175
|
+
if (isImageUrlContentPart(existing)) {
|
|
176
|
+
existing.image_url.url += chunk;
|
|
177
|
+
} else {
|
|
178
|
+
content[index] = { type: "image_url", image_url: { url: chunk } };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function mergeStreamEventDelta(base, delta) {
|
|
183
|
+
if (!isEventDelta(delta)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
mergeEventDelta(base, delta);
|
|
187
|
+
if (base.type !== "model.message") {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const blocks = delta.contentBlocks ?? delta.content_blocks;
|
|
191
|
+
if (blocks == null || blocks.length === 0) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
mergeContentBlockDeltas(base, blocks);
|
|
195
|
+
}
|
|
196
|
+
function imageUrlToAttachment(url, attachmentId) {
|
|
197
|
+
const mimeType = parseDataUriMime(url);
|
|
198
|
+
return {
|
|
199
|
+
id: attachmentId,
|
|
200
|
+
type: "image",
|
|
201
|
+
name: imageFilenameFromUrl(url, 0),
|
|
202
|
+
contentType: mimeType,
|
|
203
|
+
status: { type: "complete" },
|
|
204
|
+
content: [{ type: "image", image: url, filename: imageFilenameFromUrl(url, 0) }]
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function imagePartToAssistantImage(url, index) {
|
|
208
|
+
return {
|
|
209
|
+
type: "image",
|
|
210
|
+
image: url,
|
|
211
|
+
filename: imageFilenameFromUrl(url, index)
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function extractImagePartsFromModelMessage(message) {
|
|
215
|
+
const parts = [];
|
|
216
|
+
let imageIndex = 0;
|
|
217
|
+
for (const part of normalizeModelMessageContent(message)) {
|
|
218
|
+
if (!isImageUrlContentPart(part)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const url = part.image_url.url.trim();
|
|
222
|
+
if (url.length === 0) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
parts.push(imagePartToAssistantImage(url, imageIndex));
|
|
226
|
+
imageIndex += 1;
|
|
227
|
+
}
|
|
228
|
+
return parts;
|
|
229
|
+
}
|
|
230
|
+
function extractImageUrlFromUserContentItem(part) {
|
|
231
|
+
if (isImageUrlContentPart(part)) {
|
|
232
|
+
const url = part.image_url.url.trim();
|
|
233
|
+
return url.length > 0 ? url : void 0;
|
|
234
|
+
}
|
|
235
|
+
return void 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/modelMessageContent.ts
|
|
239
|
+
function isToolCallArgValue(value) {
|
|
240
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
if (typeof value === "number") {
|
|
244
|
+
return Number.isFinite(value);
|
|
245
|
+
}
|
|
246
|
+
if (Array.isArray(value)) {
|
|
247
|
+
return value.every(isToolCallArgValue);
|
|
248
|
+
}
|
|
249
|
+
if (value != null && typeof value === "object") {
|
|
250
|
+
return Object.keys(value).every((key) => isToolCallArgValue(Reflect.get(value, key)));
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
function parseToolArgs(argsText) {
|
|
255
|
+
if (!argsText) {
|
|
256
|
+
return {};
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const parsed = JSON.parse(argsText);
|
|
260
|
+
if (parsed != null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
261
|
+
const entries = [];
|
|
262
|
+
for (const key of Object.keys(parsed)) {
|
|
263
|
+
const value = Reflect.get(parsed, key);
|
|
264
|
+
if (isToolCallArgValue(value)) {
|
|
265
|
+
entries.push([key, value]);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return Object.fromEntries(entries);
|
|
269
|
+
}
|
|
270
|
+
return {};
|
|
271
|
+
} catch {
|
|
272
|
+
return {};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function toolCallToPart(toolCall, context) {
|
|
276
|
+
const argsText = toolCall.function.arguments;
|
|
277
|
+
const toolResult = context?.toolResults?.get(toolCall.id);
|
|
278
|
+
const pendingResponse = context?.pendingResponses?.get(toolCall.id);
|
|
279
|
+
const pendingApproval = context?.pendingApprovals?.get(toolCall.id);
|
|
280
|
+
const approvalDecision = context?.approvalDecisions?.get(toolCall.id);
|
|
281
|
+
let interrupt;
|
|
282
|
+
if (pendingResponse != null && toolResult === void 0) {
|
|
283
|
+
interrupt = {
|
|
284
|
+
type: "human",
|
|
285
|
+
payload: {
|
|
286
|
+
...pendingResponse.question != null ? { question: pendingResponse.question } : {},
|
|
287
|
+
...pendingResponse.options != null ? { options: pendingResponse.options } : {}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
let approval;
|
|
292
|
+
if (approvalDecision != null) {
|
|
293
|
+
approval = {
|
|
294
|
+
id: approvalDecision.id,
|
|
295
|
+
approved: approvalDecision.approved,
|
|
296
|
+
...approvalDecision.reason != null ? { reason: approvalDecision.reason } : {}
|
|
297
|
+
};
|
|
298
|
+
} else if (pendingApproval != null) {
|
|
299
|
+
approval = { id: pendingApproval.id };
|
|
300
|
+
}
|
|
301
|
+
let result;
|
|
302
|
+
let isError = false;
|
|
303
|
+
if (toolResult !== void 0) {
|
|
304
|
+
result = toolResult;
|
|
305
|
+
} else if (approvalDecision?.approved === false) {
|
|
306
|
+
result = {
|
|
307
|
+
error: approvalDecision.reason == null || approvalDecision.reason.length === 0 ? "Tool approval denied" : approvalDecision.reason
|
|
308
|
+
};
|
|
309
|
+
isError = true;
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
type: "tool-call",
|
|
313
|
+
toolCallId: toolCall.id,
|
|
314
|
+
toolName: toolCall.function.name,
|
|
315
|
+
argsText,
|
|
316
|
+
args: parseToolArgs(argsText),
|
|
317
|
+
...result !== void 0 ? { result } : {},
|
|
318
|
+
...isError ? { isError: true } : {},
|
|
319
|
+
...approval != null ? { approval } : {},
|
|
320
|
+
...interrupt != null ? { interrupt } : {}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function extractText(message) {
|
|
324
|
+
const { content, refusal } = message;
|
|
325
|
+
if (content == null) {
|
|
326
|
+
return refusal ?? "";
|
|
327
|
+
}
|
|
328
|
+
if (typeof content === "string") {
|
|
329
|
+
return content;
|
|
330
|
+
}
|
|
331
|
+
return content.map((part) => {
|
|
332
|
+
if (part.type === "text") {
|
|
333
|
+
return part.text;
|
|
334
|
+
}
|
|
335
|
+
if (part.type === "refusal") {
|
|
336
|
+
return part.refusal;
|
|
337
|
+
}
|
|
338
|
+
return "";
|
|
339
|
+
}).join("");
|
|
340
|
+
}
|
|
341
|
+
function buildAssistantContent(message, context) {
|
|
342
|
+
const parts = [];
|
|
343
|
+
if (message.reasoningContent) {
|
|
344
|
+
parts.push({ type: "reasoning", text: message.reasoningContent });
|
|
345
|
+
}
|
|
346
|
+
const text = extractText(message);
|
|
347
|
+
if (text) {
|
|
348
|
+
parts.push({ type: "text", text });
|
|
349
|
+
}
|
|
350
|
+
parts.push(...extractImagePartsFromModelMessage(message));
|
|
351
|
+
for (const toolCall of message.toolCalls ?? []) {
|
|
352
|
+
parts.push(toolCallToPart(toolCall, context));
|
|
353
|
+
}
|
|
354
|
+
return parts;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/toolApproval.ts
|
|
358
|
+
var TOOL_APPROVAL_THREAD_ID_CUSTOM_KEY = "toolApprovalThreadId";
|
|
359
|
+
function hasPendingToolApproval(approval) {
|
|
360
|
+
return approval != null && approval.approved === void 0 && approval.resolution === void 0;
|
|
361
|
+
}
|
|
362
|
+
function applyApprovalDecisionToToolCall(part, options) {
|
|
363
|
+
const { approved, optionId, reason } = options;
|
|
364
|
+
const targetApproval = part.approval;
|
|
365
|
+
if (targetApproval == null) {
|
|
366
|
+
return part;
|
|
367
|
+
}
|
|
368
|
+
const approval = {
|
|
369
|
+
...targetApproval,
|
|
370
|
+
approved,
|
|
371
|
+
...optionId != null ? { optionId } : {},
|
|
372
|
+
...reason != null ? { reason } : {}
|
|
373
|
+
};
|
|
374
|
+
if (approved) {
|
|
375
|
+
return { ...part, approval };
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
...part,
|
|
379
|
+
approval,
|
|
380
|
+
result: { error: reason ?? "Tool approval denied" },
|
|
381
|
+
isError: true
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function updateToolApprovalInContent(content, options) {
|
|
385
|
+
let found = false;
|
|
386
|
+
const newContent = content.map((part) => {
|
|
387
|
+
if (part.type !== "tool-call") {
|
|
388
|
+
return part;
|
|
389
|
+
}
|
|
390
|
+
if (part.approval?.id === options.approvalId) {
|
|
391
|
+
found = true;
|
|
392
|
+
return applyApprovalDecisionToToolCall(part, options);
|
|
393
|
+
}
|
|
394
|
+
if (part.messages == null) {
|
|
395
|
+
return part;
|
|
396
|
+
}
|
|
397
|
+
const messages = part.messages.map((message) => {
|
|
398
|
+
if (message.role !== "assistant") {
|
|
399
|
+
return message;
|
|
400
|
+
}
|
|
401
|
+
const nested = updateToolApprovalInContent(message.content, options);
|
|
402
|
+
if (!nested.found) {
|
|
403
|
+
return message;
|
|
404
|
+
}
|
|
405
|
+
found = true;
|
|
406
|
+
return { ...message, content: nested.content };
|
|
407
|
+
});
|
|
408
|
+
return { ...part, messages };
|
|
409
|
+
});
|
|
410
|
+
return { content: newContent, found };
|
|
411
|
+
}
|
|
412
|
+
function applyApprovalDecisionsToMessage(message, options) {
|
|
413
|
+
const { content } = updateToolApprovalInContent(message.content, options);
|
|
414
|
+
return { ...message, content: [...content] };
|
|
415
|
+
}
|
|
416
|
+
function toolApprovalStatus() {
|
|
417
|
+
return { type: "requires-action", reason: "tool-calls" };
|
|
418
|
+
}
|
|
419
|
+
function toolApprovalMessageCustom(threadId) {
|
|
420
|
+
return {
|
|
421
|
+
[TOOL_APPROVAL_THREAD_ID_CUSTOM_KEY]: threadId === ROOT_THREAD_ID ? ROOT_THREAD_ID : threadId
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function getToolApprovalThreadId(message) {
|
|
425
|
+
if (message?.role !== "assistant") {
|
|
426
|
+
return void 0;
|
|
427
|
+
}
|
|
428
|
+
const threadId = message.metadata.custom[TOOL_APPROVAL_THREAD_ID_CUSTOM_KEY];
|
|
429
|
+
return typeof threadId === "string" ? threadId : void 0;
|
|
430
|
+
}
|
|
431
|
+
function findApprovalRequiredInTurn(turn) {
|
|
432
|
+
if (turn.state.status !== "done") {
|
|
433
|
+
return void 0;
|
|
434
|
+
}
|
|
435
|
+
const found = turn.state.requiredActions?.find((action) => action.type === "tool.approval_required");
|
|
436
|
+
return found?.type === "tool.approval_required" ? found : void 0;
|
|
437
|
+
}
|
|
438
|
+
function toolCallPartHasPendingApproval(part) {
|
|
439
|
+
return hasPendingToolApproval(part.approval);
|
|
440
|
+
}
|
|
441
|
+
function nestedMessagesHavePendingApprovals(messages) {
|
|
442
|
+
for (const message of messages) {
|
|
443
|
+
if (messageHasPendingApprovals(message)) {
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
function messageHasPendingApprovals(message) {
|
|
450
|
+
if (message?.role !== "assistant") {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
for (const part of message.content) {
|
|
454
|
+
if (part.type !== "tool-call") {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (toolCallPartHasPendingApproval(part)) {
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
if (part.messages != null && nestedMessagesHavePendingApprovals(part.messages)) {
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
function applyApprovalDecisionsToMessages(messages, decisions) {
|
|
467
|
+
return messages.map((message) => {
|
|
468
|
+
if (message.role !== "assistant") {
|
|
469
|
+
return message;
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
...message,
|
|
473
|
+
content: applyApprovalDecisionsToContent(message.content, decisions)
|
|
474
|
+
};
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function applyApprovalDecisionsToContent(content, decisions) {
|
|
478
|
+
return content.map((part) => {
|
|
479
|
+
if (part.type !== "tool-call") {
|
|
480
|
+
return part;
|
|
481
|
+
}
|
|
482
|
+
const decision = part.approval?.id != null ? decisions.get(part.approval.id) : void 0;
|
|
483
|
+
let nextPart = part;
|
|
484
|
+
if (decision != null && part.approval != null && part.approval.approved === void 0) {
|
|
485
|
+
nextPart = applyApprovalDecisionToToolCall(part, {
|
|
486
|
+
approvalId: part.approval.id,
|
|
487
|
+
approved: decision.approved,
|
|
488
|
+
...decision.reason == null ? {} : { reason: decision.reason }
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
if (nextPart.messages == null) {
|
|
492
|
+
return nextPart;
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
...nextPart,
|
|
496
|
+
messages: applyApprovalDecisionsToMessages(nextPart.messages, decisions)
|
|
497
|
+
};
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
function extractToolApprovalsFromTurnInput(input) {
|
|
501
|
+
const events = [];
|
|
502
|
+
for (const item of input ?? []) {
|
|
503
|
+
if (item.type === "user.tool_approval") {
|
|
504
|
+
events.push(item);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return events;
|
|
508
|
+
}
|
|
509
|
+
function collectSubsequentApprovalDecisions(turns, fromIndex) {
|
|
510
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
511
|
+
for (let index = fromIndex + 1; index < turns.length; index++) {
|
|
512
|
+
const input = turns[index]?.input ?? [];
|
|
513
|
+
if (input.some((item) => item.type === "user.message")) {
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
for (const event of extractToolApprovalsFromTurnInput(input)) {
|
|
517
|
+
decisions.set(event.toolCallId, {
|
|
518
|
+
approved: event.approval.status === "allow",
|
|
519
|
+
...event.approval.status === "deny" && event.approval.reason != null ? { reason: event.approval.reason } : {}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return decisions;
|
|
524
|
+
}
|
|
525
|
+
function collectApprovalDecisionsFromTurnInput(input) {
|
|
526
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const event of extractToolApprovalsFromTurnInput(input)) {
|
|
528
|
+
decisions.set(event.toolCallId, {
|
|
529
|
+
approved: event.approval.status === "allow",
|
|
530
|
+
...event.approval.status === "deny" && event.approval.reason != null ? { reason: event.approval.reason } : {}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
return decisions;
|
|
534
|
+
}
|
|
535
|
+
function mapApprovalDecision(approved, reason) {
|
|
536
|
+
if (approved) {
|
|
537
|
+
return { status: "allow" };
|
|
538
|
+
}
|
|
539
|
+
return { status: "deny", ...reason != null ? { reason } : {} };
|
|
540
|
+
}
|
|
541
|
+
function isDecidedApprovalAwaitingSdk(part) {
|
|
542
|
+
const { approval, result, isError } = part;
|
|
543
|
+
if (approval?.id == null || approval.approved === void 0) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
if (approval.approved) {
|
|
547
|
+
return result === void 0;
|
|
548
|
+
}
|
|
549
|
+
return isError === true;
|
|
550
|
+
}
|
|
551
|
+
function collectApprovalInputsFromMessages(messages, defaultThreadId) {
|
|
552
|
+
const events = [];
|
|
553
|
+
for (const message of messages) {
|
|
554
|
+
events.push(...collectApprovalInputs(message, defaultThreadId));
|
|
555
|
+
}
|
|
556
|
+
return events;
|
|
557
|
+
}
|
|
558
|
+
function collectApprovalInputs(message, threadId) {
|
|
559
|
+
if (message.role !== "assistant" || !threadId) {
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
562
|
+
if (messageHasPendingApprovals(message)) {
|
|
563
|
+
return [];
|
|
564
|
+
}
|
|
565
|
+
const scopedThreadId = getToolApprovalThreadId(message) ?? threadId;
|
|
566
|
+
const events = [];
|
|
567
|
+
for (const part of message.content) {
|
|
568
|
+
if (part.type !== "tool-call") {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (isDecidedApprovalAwaitingSdk(part)) {
|
|
572
|
+
const { approval } = part;
|
|
573
|
+
if (approval?.approved === void 0) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
events.push({
|
|
577
|
+
type: "user.tool_approval",
|
|
578
|
+
threadId: scopedThreadId,
|
|
579
|
+
toolCallId: approval.id,
|
|
580
|
+
approval: mapApprovalDecision(approval.approved, approval.reason)
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
if (part.messages != null) {
|
|
584
|
+
events.push(...collectApprovalInputsFromMessages(part.messages, scopedThreadId));
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return events;
|
|
588
|
+
}
|
|
589
|
+
function toTrueForgeApprovalInputs(message, response, defaultThreadId = ROOT_THREAD_ID) {
|
|
590
|
+
const updated = applyApprovalDecisionsToMessage(message, response);
|
|
591
|
+
return collectApprovalInputs(updated, defaultThreadId);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/toolResponse.ts
|
|
595
|
+
var TOOL_RESPONSE_THREAD_ID_CUSTOM_KEY = "toolResponseThreadId";
|
|
596
|
+
function hasPendingToolResponse(part) {
|
|
597
|
+
return part.interrupt != null && part.result === void 0;
|
|
598
|
+
}
|
|
599
|
+
function isStagedResponseAwaitingSdk(part) {
|
|
600
|
+
return part.interrupt != null && part.result !== void 0;
|
|
601
|
+
}
|
|
602
|
+
function toolResponseStatus() {
|
|
603
|
+
return { type: "requires-action", reason: "tool-calls" };
|
|
604
|
+
}
|
|
605
|
+
function toolResponseMessageCustom(threadId) {
|
|
606
|
+
return {
|
|
607
|
+
[TOOL_RESPONSE_THREAD_ID_CUSTOM_KEY]: threadId === ROOT_THREAD_ID ? ROOT_THREAD_ID : threadId
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
function getToolResponseThreadId(message) {
|
|
611
|
+
if (message?.role !== "assistant") {
|
|
612
|
+
return void 0;
|
|
613
|
+
}
|
|
614
|
+
const threadId = message.metadata.custom[TOOL_RESPONSE_THREAD_ID_CUSTOM_KEY];
|
|
615
|
+
return typeof threadId === "string" ? threadId : void 0;
|
|
616
|
+
}
|
|
617
|
+
function findResponseRequiredInTurn(turn) {
|
|
618
|
+
if (turn.state.status !== "done") {
|
|
619
|
+
return void 0;
|
|
620
|
+
}
|
|
621
|
+
const found = turn.state.requiredActions?.find((action) => action.type === "tool.response_required");
|
|
622
|
+
return found?.type === "tool.response_required" ? found : void 0;
|
|
623
|
+
}
|
|
624
|
+
function applyToolResponseToToolCall(part, content) {
|
|
625
|
+
return { ...part, result: content };
|
|
626
|
+
}
|
|
627
|
+
function nestedMessagesHavePendingResponses(messages) {
|
|
628
|
+
for (const message of messages) {
|
|
629
|
+
if (messageHasPendingResponses(message)) {
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
function messageHasPendingResponses(message) {
|
|
636
|
+
if (message?.role !== "assistant") {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
for (const part of message.content) {
|
|
640
|
+
if (part.type !== "tool-call") {
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (hasPendingToolResponse(part)) {
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
if (part.messages != null && nestedMessagesHavePendingResponses(part.messages)) {
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
function collectResponseInputsFromMessages(messages, defaultThreadId) {
|
|
653
|
+
const events = [];
|
|
654
|
+
for (const message of messages) {
|
|
655
|
+
events.push(...collectResponseInputs(message, defaultThreadId));
|
|
656
|
+
}
|
|
657
|
+
return events;
|
|
658
|
+
}
|
|
659
|
+
function collectResponseInputs(message, threadId) {
|
|
660
|
+
if (message.role !== "assistant" || !threadId) {
|
|
661
|
+
return [];
|
|
662
|
+
}
|
|
663
|
+
if (messageHasPendingResponses(message)) {
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
const scopedThreadId = getToolResponseThreadId(message) ?? threadId;
|
|
667
|
+
const events = [];
|
|
668
|
+
for (const part of message.content) {
|
|
669
|
+
if (part.type !== "tool-call") {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (isStagedResponseAwaitingSdk(part)) {
|
|
673
|
+
events.push({
|
|
674
|
+
type: "user.tool_response",
|
|
675
|
+
threadId: scopedThreadId,
|
|
676
|
+
toolCallId: part.toolCallId,
|
|
677
|
+
content: String(part.result)
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
if (part.messages != null) {
|
|
681
|
+
events.push(...collectResponseInputsFromMessages(part.messages, scopedThreadId));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return events;
|
|
685
|
+
}
|
|
686
|
+
function applyStagedResponsesToContentMap(content, staged) {
|
|
687
|
+
return content.map((part) => {
|
|
688
|
+
if (part.type !== "tool-call") {
|
|
689
|
+
return part;
|
|
690
|
+
}
|
|
691
|
+
const contentValue = staged.get(part.toolCallId);
|
|
692
|
+
let nextPart = part;
|
|
693
|
+
if (contentValue != null && hasPendingToolResponse(part)) {
|
|
694
|
+
nextPart = applyToolResponseToToolCall(part, contentValue);
|
|
695
|
+
}
|
|
696
|
+
if (nextPart.messages == null) {
|
|
697
|
+
return nextPart;
|
|
698
|
+
}
|
|
699
|
+
return {
|
|
700
|
+
...nextPart,
|
|
701
|
+
messages: nextPart.messages.map((message) => {
|
|
702
|
+
if (message.role !== "assistant") {
|
|
703
|
+
return message;
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
...message,
|
|
707
|
+
content: applyStagedResponsesToContentMap(message.content, staged)
|
|
708
|
+
};
|
|
709
|
+
})
|
|
710
|
+
};
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
function extractToolResponsesFromTurnInput(input) {
|
|
714
|
+
const events = [];
|
|
715
|
+
for (const item of input ?? []) {
|
|
716
|
+
if (item.type === "user.tool_response") {
|
|
717
|
+
events.push(item);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return events;
|
|
721
|
+
}
|
|
722
|
+
function applyUserToolResponsesToFold(fold, inputs) {
|
|
723
|
+
for (const item of inputs) {
|
|
724
|
+
if (item.type === "user.tool_response") {
|
|
725
|
+
recordToolResponseInFold(fold, {
|
|
726
|
+
toolCallId: item.toolCallId,
|
|
727
|
+
content: item.content
|
|
728
|
+
});
|
|
729
|
+
} else if (item.type === "user.tool_approval") {
|
|
730
|
+
recordToolApprovalInFold(fold, {
|
|
731
|
+
toolCallId: item.toolCallId,
|
|
732
|
+
approved: item.approval.status === "allow",
|
|
733
|
+
...item.approval.status === "deny" && item.approval.reason != null ? { reason: item.approval.reason } : {}
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function collectSubsequentToolResponses(turns, fromIndex) {
|
|
739
|
+
const responses = /* @__PURE__ */ new Map();
|
|
740
|
+
for (let index = fromIndex + 1; index < turns.length; index++) {
|
|
741
|
+
const input = turns[index]?.input ?? [];
|
|
742
|
+
if (input.some((item) => item.type === "user.message")) {
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
for (const event of extractToolResponsesFromTurnInput(input)) {
|
|
746
|
+
responses.set(event.toolCallId, { content: event.content });
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return responses;
|
|
750
|
+
}
|
|
751
|
+
function collectToolResponsesFromTurnInput(input) {
|
|
752
|
+
const responses = /* @__PURE__ */ new Map();
|
|
753
|
+
for (const event of extractToolResponsesFromTurnInput(input)) {
|
|
754
|
+
responses.set(event.toolCallId, { content: event.content });
|
|
755
|
+
}
|
|
756
|
+
return responses;
|
|
757
|
+
}
|
|
758
|
+
function applyStagedResponsesToContent(content, responses) {
|
|
759
|
+
const staged = new Map([...responses.entries()].map(([toolCallId, value]) => [toolCallId, value.content]));
|
|
760
|
+
return applyStagedResponsesToContentMap(content, staged);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/foldPeerThreads.ts
|
|
764
|
+
var isDev = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env["NODE_ENV"] !== "production";
|
|
765
|
+
function warnUnexpectedRootThread(threadId, eventType) {
|
|
766
|
+
if (!isDev) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
console.warn(
|
|
770
|
+
`[@truefoundry/trueforge-assistant-ui-runtime] Expected root thread "${ROOT_THREAD_ID}" but received "${threadId}" on ${eventType}.`
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
function assertRootThreadEvent(threadId, eventType) {
|
|
774
|
+
if (threadId === ROOT_THREAD_ID) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
if (isDev) {
|
|
778
|
+
throw new Error(
|
|
779
|
+
`[@truefoundry/trueforge-assistant-ui-runtime] Root-looking event ${eventType} arrived on thread "${threadId}" instead of "${ROOT_THREAD_ID}".`
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
warnUnexpectedRootThread(threadId, eventType);
|
|
783
|
+
}
|
|
784
|
+
var PeerThreadFoldState = class {
|
|
785
|
+
threads = /* @__PURE__ */ new Map();
|
|
786
|
+
threadParents = /* @__PURE__ */ new Map();
|
|
787
|
+
getOrCreateBucket(threadId) {
|
|
788
|
+
let bucket = this.threads.get(threadId);
|
|
789
|
+
if (bucket == null) {
|
|
790
|
+
bucket = {
|
|
791
|
+
events: /* @__PURE__ */ new Map(),
|
|
792
|
+
modelMessageIds: [],
|
|
793
|
+
toolResults: /* @__PURE__ */ new Map(),
|
|
794
|
+
pendingApprovals: /* @__PURE__ */ new Map(),
|
|
795
|
+
approvalDecisions: /* @__PURE__ */ new Map(),
|
|
796
|
+
pendingResponses: /* @__PURE__ */ new Map(),
|
|
797
|
+
done: false
|
|
798
|
+
};
|
|
799
|
+
this.threads.set(threadId, bucket);
|
|
800
|
+
}
|
|
801
|
+
return bucket;
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
function isTurnScopedEvent(message) {
|
|
805
|
+
return message.type === "turn.created" || message.type === "turn.done";
|
|
806
|
+
}
|
|
807
|
+
function ingestEventIntoBucket(bucket, message) {
|
|
808
|
+
if (isTurnScopedEvent(message)) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (isEventDelta(message)) {
|
|
812
|
+
const base = bucket.events.get(message.id);
|
|
813
|
+
if (base != null) {
|
|
814
|
+
mergeStreamEventDelta(base, message);
|
|
815
|
+
}
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
bucket.events.set(message.id, message);
|
|
819
|
+
if (message.type === "model.message") {
|
|
820
|
+
if (!bucket.modelMessageIds.includes(message.id)) {
|
|
821
|
+
bucket.modelMessageIds.push(message.id);
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (message.type === "tool.response") {
|
|
826
|
+
bucket.toolResults.set(message.toolCallId, message.content);
|
|
827
|
+
bucket.pendingResponses.delete(message.toolCallId);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (message.type === "tool.approval_required") {
|
|
831
|
+
for (const ref of message.toolCalls) {
|
|
832
|
+
bucket.pendingApprovals.set(ref.id, { id: ref.id });
|
|
833
|
+
}
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
if (message.type === "tool.response_required") {
|
|
837
|
+
for (const ref of message.toolCalls) {
|
|
838
|
+
const resolved = resolveAskUserQuestionFromBucket(bucket, ref);
|
|
839
|
+
bucket.pendingResponses.set(ref.id, {
|
|
840
|
+
id: ref.id,
|
|
841
|
+
sourceEventId: ref.sourceEventId,
|
|
842
|
+
...resolved?.question != null ? { question: resolved.question } : {},
|
|
843
|
+
...resolved?.options != null ? { options: resolved.options } : {}
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
if (message.type === "thread.done") {
|
|
849
|
+
bucket.done = true;
|
|
850
|
+
if (message.title) {
|
|
851
|
+
bucket.title = message.title;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
function isContentAffectingEvent(message) {
|
|
856
|
+
return message.type === "thread.created" || message.type === "thread.done" || message.type === "model.message" || message.type === "model.message.delta" || message.type === "tool.response" || message.type === "tool.approval_required" || message.type === "tool.response_required";
|
|
857
|
+
}
|
|
858
|
+
function ingestStreamEvent(state, message) {
|
|
859
|
+
if (message.type === "mcp.auth_required" || isTurnScopedEvent(message)) {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
if (message.threadId == null) {
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
if (message.type === "thread.created") {
|
|
866
|
+
state.threadParents.set(message.threadId, {
|
|
867
|
+
parentThreadId: message.parent.threadId,
|
|
868
|
+
toolCallId: message.parent.toolCallId
|
|
869
|
+
});
|
|
870
|
+
const bucket2 = state.getOrCreateBucket(message.threadId);
|
|
871
|
+
bucket2.title = message.title;
|
|
872
|
+
bucket2.agentInfo = message.agentInfo;
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
if (message.type === "model.message" && message.threadId === ROOT_THREAD_ID) {
|
|
876
|
+
assertRootThreadEvent(message.threadId, message.type);
|
|
877
|
+
}
|
|
878
|
+
const bucket = state.getOrCreateBucket(message.threadId);
|
|
879
|
+
ingestEventIntoBucket(bucket, message);
|
|
880
|
+
return isContentAffectingEvent(message);
|
|
881
|
+
}
|
|
882
|
+
function ingestTurnEvent(state, event) {
|
|
883
|
+
if (event.threadId == null) {
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
if (event.type === "thread.created") {
|
|
887
|
+
state.threadParents.set(event.threadId, {
|
|
888
|
+
parentThreadId: event.parent.threadId,
|
|
889
|
+
toolCallId: event.parent.toolCallId
|
|
890
|
+
});
|
|
891
|
+
const bucket = state.getOrCreateBucket(event.threadId);
|
|
892
|
+
bucket.title = event.title;
|
|
893
|
+
bucket.agentInfo = event.agentInfo;
|
|
894
|
+
} else if (event.type === "model.message" && event.threadId === ROOT_THREAD_ID) {
|
|
895
|
+
assertRootThreadEvent(event.threadId, event.type);
|
|
896
|
+
}
|
|
897
|
+
ingestEventIntoBucket(state.getOrCreateBucket(event.threadId), event);
|
|
898
|
+
}
|
|
899
|
+
function resolveAskUserQuestionFromBucket(bucket, ref) {
|
|
900
|
+
const modelMessage = bucket.events.get(ref.sourceEventId);
|
|
901
|
+
if (modelMessage?.type !== "model.message") {
|
|
902
|
+
return void 0;
|
|
903
|
+
}
|
|
904
|
+
const toolCall = modelMessage.toolCalls?.find((call) => call.id === ref.id);
|
|
905
|
+
if (toolCall == null) {
|
|
906
|
+
return void 0;
|
|
907
|
+
}
|
|
908
|
+
return parseAskUserQuestionArgs(toolCall.function.arguments);
|
|
909
|
+
}
|
|
910
|
+
function findToolCallInBucket(bucket, toolCallId) {
|
|
911
|
+
for (const id of bucket.modelMessageIds) {
|
|
912
|
+
const event = bucket.events.get(id);
|
|
913
|
+
if (event?.type !== "model.message") {
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
const match = event.toolCalls?.find((toolCall) => toolCall.id === toolCallId);
|
|
917
|
+
if (match != null) {
|
|
918
|
+
return match;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return void 0;
|
|
922
|
+
}
|
|
923
|
+
function isLinkedCreateSubAgentThread(state, subThreadId) {
|
|
924
|
+
const link = state.threadParents.get(subThreadId);
|
|
925
|
+
if (link == null) {
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
const parentBucket = state.threads.get(link.parentThreadId);
|
|
929
|
+
if (parentBucket == null) {
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
const toolCall = findToolCallInBucket(parentBucket, link.toolCallId);
|
|
933
|
+
return toolCall != null && isCreateSubAgentToolCall(toolCall);
|
|
934
|
+
}
|
|
935
|
+
function childSubThreadIds(state, parentThreadId, toolCallId) {
|
|
936
|
+
const ids = [];
|
|
937
|
+
for (const [subThreadId, link] of state.threadParents) {
|
|
938
|
+
if (link.parentThreadId === parentThreadId && link.toolCallId === toolCallId && isLinkedCreateSubAgentThread(state, subThreadId)) {
|
|
939
|
+
ids.push(subThreadId);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return ids;
|
|
943
|
+
}
|
|
944
|
+
function bucketHasUnresolvedPendingResponses(bucket) {
|
|
945
|
+
for (const id of bucket.pendingResponses.keys()) {
|
|
946
|
+
if (!bucket.toolResults.has(id)) {
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
function bucketAssistantStatus(bucket) {
|
|
953
|
+
if (bucket.pendingApprovals.size > 0) {
|
|
954
|
+
return toolApprovalStatus();
|
|
955
|
+
}
|
|
956
|
+
if (bucketHasUnresolvedPendingResponses(bucket)) {
|
|
957
|
+
return toolResponseStatus();
|
|
958
|
+
}
|
|
959
|
+
if (!bucket.done && bucket.modelMessageIds.length > 0) {
|
|
960
|
+
return { type: "running" };
|
|
961
|
+
}
|
|
962
|
+
return { type: "complete", reason: "stop" };
|
|
963
|
+
}
|
|
964
|
+
function buildSubAgentCustomMetadata(threadId, bucket) {
|
|
965
|
+
const metadata = {
|
|
966
|
+
threadId,
|
|
967
|
+
...bucket.title != null ? { title: bucket.title } : {},
|
|
968
|
+
...bucket.agentInfo?.name != null ? { name: bucket.agentInfo.name } : {},
|
|
969
|
+
...bucket.agentInfo?.model != null ? { model: bucket.agentInfo.model } : {},
|
|
970
|
+
...bucket.agentInfo?.input != null ? { input: bucket.agentInfo.input } : {}
|
|
971
|
+
};
|
|
972
|
+
return { subAgent: metadata };
|
|
973
|
+
}
|
|
974
|
+
function attachSubAgentMessages(state, parentThreadId, parts) {
|
|
975
|
+
const parentBucket = state.threads.get(parentThreadId);
|
|
976
|
+
return parts.map((part) => {
|
|
977
|
+
if (part.type !== "tool-call" || parentBucket == null) {
|
|
978
|
+
return part;
|
|
979
|
+
}
|
|
980
|
+
const sdkToolCall = findToolCallInBucket(parentBucket, part.toolCallId);
|
|
981
|
+
if (sdkToolCall == null || !isCreateSubAgentToolCall(sdkToolCall)) {
|
|
982
|
+
return part;
|
|
983
|
+
}
|
|
984
|
+
const childIds = childSubThreadIds(state, parentThreadId, part.toolCallId);
|
|
985
|
+
if (childIds.length === 0) {
|
|
986
|
+
return part;
|
|
987
|
+
}
|
|
988
|
+
const messages = [];
|
|
989
|
+
const subAgents = [];
|
|
990
|
+
for (const childId of childIds) {
|
|
991
|
+
const childMessages = buildSubThreadMessages(state, childId);
|
|
992
|
+
if (childMessages.length > 0) {
|
|
993
|
+
messages.push(...childMessages);
|
|
994
|
+
}
|
|
995
|
+
const childBucket = state.threads.get(childId);
|
|
996
|
+
if (childBucket != null) {
|
|
997
|
+
subAgents.push({
|
|
998
|
+
threadId: childId,
|
|
999
|
+
...childBucket.title != null ? { title: childBucket.title } : {},
|
|
1000
|
+
...childBucket.agentInfo != null ? { agentInfo: childBucket.agentInfo } : {}
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
if (messages.length === 0 && subAgents.length === 0) {
|
|
1005
|
+
return part;
|
|
1006
|
+
}
|
|
1007
|
+
const artifact = { subAgents };
|
|
1008
|
+
if (messages.length === 0) {
|
|
1009
|
+
return { ...part, artifact };
|
|
1010
|
+
}
|
|
1011
|
+
return { ...part, messages, artifact };
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
function buildThreadAssistantParts(state, threadId, modelMessageIds) {
|
|
1015
|
+
const bucket = state.threads.get(threadId);
|
|
1016
|
+
if (bucket == null) {
|
|
1017
|
+
return [];
|
|
1018
|
+
}
|
|
1019
|
+
const ids = threadId === ROOT_THREAD_ID && modelMessageIds != null ? modelMessageIds : bucket.modelMessageIds;
|
|
1020
|
+
const parts = [];
|
|
1021
|
+
const toolCallIndexById = /* @__PURE__ */ new Map();
|
|
1022
|
+
for (const id of ids) {
|
|
1023
|
+
const event = bucket.events.get(id);
|
|
1024
|
+
if (event?.type !== "model.message") {
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
for (const part of buildAssistantContent(event, {
|
|
1028
|
+
toolResults: bucket.toolResults,
|
|
1029
|
+
pendingApprovals: bucket.pendingApprovals,
|
|
1030
|
+
approvalDecisions: bucket.approvalDecisions,
|
|
1031
|
+
pendingResponses: bucket.pendingResponses
|
|
1032
|
+
})) {
|
|
1033
|
+
if (part.type === "tool-call") {
|
|
1034
|
+
const existingIndex = toolCallIndexById.get(part.toolCallId);
|
|
1035
|
+
if (existingIndex != null) {
|
|
1036
|
+
parts[existingIndex] = part;
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
toolCallIndexById.set(part.toolCallId, parts.length);
|
|
1040
|
+
}
|
|
1041
|
+
parts.push(part);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return attachSubAgentMessages(state, threadId, parts);
|
|
1045
|
+
}
|
|
1046
|
+
function buildSubThreadMessages(state, threadId) {
|
|
1047
|
+
const bucket = state.threads.get(threadId);
|
|
1048
|
+
if (bucket == null) {
|
|
1049
|
+
return [];
|
|
1050
|
+
}
|
|
1051
|
+
const content = buildThreadAssistantParts(state, threadId);
|
|
1052
|
+
if (content.length === 0) {
|
|
1053
|
+
return [];
|
|
1054
|
+
}
|
|
1055
|
+
const custom = {
|
|
1056
|
+
...buildSubAgentCustomMetadata(threadId, bucket),
|
|
1057
|
+
...bucket.pendingApprovals.size > 0 ? toolApprovalMessageCustom(threadId) : {},
|
|
1058
|
+
...bucketHasUnresolvedPendingResponses(bucket) ? toolResponseMessageCustom(threadId) : {}
|
|
1059
|
+
};
|
|
1060
|
+
return [
|
|
1061
|
+
{
|
|
1062
|
+
id: `${threadId}-assistant`,
|
|
1063
|
+
role: "assistant",
|
|
1064
|
+
content,
|
|
1065
|
+
status: bucketAssistantStatus(bucket),
|
|
1066
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1067
|
+
metadata: {
|
|
1068
|
+
unstable_state: null,
|
|
1069
|
+
unstable_annotations: [],
|
|
1070
|
+
unstable_data: [],
|
|
1071
|
+
steps: [],
|
|
1072
|
+
custom
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
];
|
|
1076
|
+
}
|
|
1077
|
+
function buildRootAssistantContent(state) {
|
|
1078
|
+
return buildThreadAssistantParts(state, ROOT_THREAD_ID);
|
|
1079
|
+
}
|
|
1080
|
+
function buildRootAssistantContentForIds(state, modelMessageIds) {
|
|
1081
|
+
return buildThreadAssistantParts(state, ROOT_THREAD_ID, modelMessageIds);
|
|
1082
|
+
}
|
|
1083
|
+
function findFirstPendingApprovalThreadId(state) {
|
|
1084
|
+
for (const [threadId, bucket] of state.threads) {
|
|
1085
|
+
if (bucket.pendingApprovals.size > 0) {
|
|
1086
|
+
return threadId;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return void 0;
|
|
1090
|
+
}
|
|
1091
|
+
function findFirstPendingResponseThreadId(state) {
|
|
1092
|
+
for (const [threadId, bucket] of state.threads) {
|
|
1093
|
+
if (bucketHasUnresolvedPendingResponses(bucket)) {
|
|
1094
|
+
return threadId;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return void 0;
|
|
1098
|
+
}
|
|
1099
|
+
function recordToolApprovalInFold(fold, decision) {
|
|
1100
|
+
const record = {
|
|
1101
|
+
id: decision.toolCallId,
|
|
1102
|
+
approved: decision.approved,
|
|
1103
|
+
...decision.reason != null ? { reason: decision.reason } : {}
|
|
1104
|
+
};
|
|
1105
|
+
let applied = false;
|
|
1106
|
+
for (const bucket of fold.threads.values()) {
|
|
1107
|
+
if (!bucket.pendingApprovals.has(decision.toolCallId)) {
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
bucket.pendingApprovals.delete(decision.toolCallId);
|
|
1111
|
+
bucket.approvalDecisions.set(decision.toolCallId, record);
|
|
1112
|
+
applied = true;
|
|
1113
|
+
}
|
|
1114
|
+
if (applied) {
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
const rootBucket = fold.getOrCreateBucket(ROOT_THREAD_ID);
|
|
1118
|
+
rootBucket.approvalDecisions.set(decision.toolCallId, record);
|
|
1119
|
+
}
|
|
1120
|
+
function recordToolResponseInFold(fold, response) {
|
|
1121
|
+
let applied = false;
|
|
1122
|
+
for (const bucket of fold.threads.values()) {
|
|
1123
|
+
if (!bucket.pendingResponses.has(response.toolCallId)) {
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
bucket.toolResults.set(response.toolCallId, response.content);
|
|
1127
|
+
bucket.pendingResponses.delete(response.toolCallId);
|
|
1128
|
+
applied = true;
|
|
1129
|
+
}
|
|
1130
|
+
if (applied) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const rootBucket = fold.getOrCreateBucket(ROOT_THREAD_ID);
|
|
1134
|
+
rootBucket.toolResults.set(response.toolCallId, response.content);
|
|
1135
|
+
rootBucket.pendingResponses.delete(response.toolCallId);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// src/listPages.ts
|
|
1139
|
+
async function drainListPages(fetchPage) {
|
|
1140
|
+
const items = [];
|
|
1141
|
+
let pageToken;
|
|
1142
|
+
for (; ; ) {
|
|
1143
|
+
const page = await fetchPage(pageToken);
|
|
1144
|
+
items.push(...page.data);
|
|
1145
|
+
if (page.nextPageToken == null || page.nextPageToken === "") {
|
|
1146
|
+
break;
|
|
1147
|
+
}
|
|
1148
|
+
pageToken = page.nextPageToken;
|
|
1149
|
+
}
|
|
1150
|
+
return items;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/mcpAuth.ts
|
|
1154
|
+
var MCP_AUTH_RESUME_RUN_CUSTOM_KEY = "resumeMcpAuth";
|
|
1155
|
+
function buildMcpAuthTextParts(servers) {
|
|
1156
|
+
void servers;
|
|
1157
|
+
const text = [
|
|
1158
|
+
"This agent needs access to external services before it can continue.",
|
|
1159
|
+
"",
|
|
1160
|
+
"Click the **Connect** button(s) to authorize the Connectors, then press **Continue**."
|
|
1161
|
+
].join("\n");
|
|
1162
|
+
return [{ type: "text", text }];
|
|
1163
|
+
}
|
|
1164
|
+
function findMcpAuthRequired(requiredActions) {
|
|
1165
|
+
const found = requiredActions?.find((action) => action.type === "mcp.auth_required");
|
|
1166
|
+
return found?.type === "mcp.auth_required" ? found : void 0;
|
|
1167
|
+
}
|
|
1168
|
+
function mcpAuthAssistantStatus() {
|
|
1169
|
+
return { type: "requires-action", reason: "interrupt" };
|
|
1170
|
+
}
|
|
1171
|
+
function mcpAuthMessageCustom(servers) {
|
|
1172
|
+
return { pendingMcpAuth: true, mcpServers: [...servers] };
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/sessionSnapshot.ts
|
|
1176
|
+
function emptyRequiredActionsOverlay() {
|
|
1177
|
+
return {
|
|
1178
|
+
approvals: /* @__PURE__ */ new Map(),
|
|
1179
|
+
toolResponses: /* @__PURE__ */ new Map()
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function createEmptySessionSnapshot() {
|
|
1183
|
+
return {
|
|
1184
|
+
fold: new PeerThreadFoldState(),
|
|
1185
|
+
turns: [],
|
|
1186
|
+
requiredActions: emptyRequiredActionsOverlay()
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
function turnToSessionRecord(turn) {
|
|
1190
|
+
const userText = extractTurnUserText(turn.input);
|
|
1191
|
+
return {
|
|
1192
|
+
id: turn.id,
|
|
1193
|
+
...userText !== void 0 ? { userText } : {},
|
|
1194
|
+
createdAt: turn.createdAt,
|
|
1195
|
+
state: turn.state,
|
|
1196
|
+
input: turn.input ?? []
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
function sessionEventsToSessionRecord(turnId, createdEvent, doneEvent, rootModelMessageIds, sandboxId) {
|
|
1200
|
+
const userText = extractTurnUserText(createdEvent.input);
|
|
1201
|
+
return {
|
|
1202
|
+
id: turnId,
|
|
1203
|
+
...userText !== void 0 ? { userText } : {},
|
|
1204
|
+
createdAt: createdEvent.createdAt,
|
|
1205
|
+
state: doneEvent.state,
|
|
1206
|
+
input: createdEvent.input ?? [],
|
|
1207
|
+
rootModelMessageIds,
|
|
1208
|
+
...sandboxId != null ? { sandboxId } : {}
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
function replaceSessionSnapshot(snapshot, patch) {
|
|
1212
|
+
return {
|
|
1213
|
+
...snapshot,
|
|
1214
|
+
...patch,
|
|
1215
|
+
...patch.requiredActions != null ? { requiredActions: patch.requiredActions } : {}
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// src/turnEventHelpers.ts
|
|
1220
|
+
function buildToolApprovalUpdate(content, turn) {
|
|
1221
|
+
const pendingApproval = findApprovalRequiredInTurn(turn);
|
|
1222
|
+
if (pendingApproval == null) {
|
|
1223
|
+
return { content };
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
content,
|
|
1227
|
+
status: toolApprovalStatus(),
|
|
1228
|
+
metadata: { custom: toolApprovalMessageCustom(pendingApproval.threadId) }
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
function buildToolResponseUpdate(update, turn) {
|
|
1232
|
+
const pendingResponse = findResponseRequiredInTurn(turn);
|
|
1233
|
+
if (pendingResponse == null) {
|
|
1234
|
+
return update;
|
|
1235
|
+
}
|
|
1236
|
+
return {
|
|
1237
|
+
...update,
|
|
1238
|
+
content: update.content,
|
|
1239
|
+
status: toolResponseStatus(),
|
|
1240
|
+
metadata: {
|
|
1241
|
+
custom: {
|
|
1242
|
+
...update.metadata?.custom,
|
|
1243
|
+
...toolResponseMessageCustom(pendingResponse.threadId)
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
function appendToolResponseToTurnContent(update, turn) {
|
|
1249
|
+
if (update.status?.type === "requires-action") {
|
|
1250
|
+
return update;
|
|
1251
|
+
}
|
|
1252
|
+
return buildToolResponseUpdate(update, turn);
|
|
1253
|
+
}
|
|
1254
|
+
function appendToolApprovalToTurnContent(update, turn) {
|
|
1255
|
+
if (update.status?.type === "requires-action") {
|
|
1256
|
+
return update;
|
|
1257
|
+
}
|
|
1258
|
+
const approvalUpdate = buildToolApprovalUpdate(update.content, turn);
|
|
1259
|
+
return appendToolResponseToTurnContent(approvalUpdate, turn);
|
|
1260
|
+
}
|
|
1261
|
+
function appendMcpAuthToTurnContent(content, turn) {
|
|
1262
|
+
const pendingMcpAuth = findMcpAuthRequired(turn.state.status === "done" ? turn.state.requiredActions : void 0);
|
|
1263
|
+
if (pendingMcpAuth == null) {
|
|
1264
|
+
return appendToolApprovalToTurnContent({ content }, turn);
|
|
1265
|
+
}
|
|
1266
|
+
return appendToolApprovalToTurnContent(
|
|
1267
|
+
{
|
|
1268
|
+
content: [...content, ...buildMcpAuthTextParts(pendingMcpAuth.mcpServers)],
|
|
1269
|
+
status: mcpAuthAssistantStatus(),
|
|
1270
|
+
metadata: { custom: mcpAuthMessageCustom(pendingMcpAuth.mcpServers) }
|
|
1271
|
+
},
|
|
1272
|
+
turn
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// src/convertTurnMessages.ts
|
|
1277
|
+
var TURN_EVENTS_PAGE_SIZE = 25;
|
|
1278
|
+
var SESSION_EVENTS_PAGE_SIZE = 100;
|
|
1279
|
+
var MAX_HISTORY_BOUNDARY_PAGES = 10;
|
|
1280
|
+
function trimIncompleteLeadingEvents(itemsAsc) {
|
|
1281
|
+
const start = itemsAsc.findIndex((item) => item.event.type === "turn.created");
|
|
1282
|
+
if (start === -1) {
|
|
1283
|
+
return [];
|
|
1284
|
+
}
|
|
1285
|
+
return itemsAsc.slice(start);
|
|
1286
|
+
}
|
|
1287
|
+
function oldestCompleteTurnGroupState(itemsAsc) {
|
|
1288
|
+
const trimmed = trimIncompleteLeadingEvents(itemsAsc);
|
|
1289
|
+
if (trimmed.length === 0) {
|
|
1290
|
+
return "incomplete";
|
|
1291
|
+
}
|
|
1292
|
+
const created = trimmed[0];
|
|
1293
|
+
if (created?.event.type !== "turn.created") {
|
|
1294
|
+
return "incomplete";
|
|
1295
|
+
}
|
|
1296
|
+
const hasDone = trimmed.some((item) => item.turnId === created.turnId && item.event.type === "turn.done");
|
|
1297
|
+
if (!hasDone) {
|
|
1298
|
+
return "incomplete";
|
|
1299
|
+
}
|
|
1300
|
+
return extractTurnUserText(created.event.input) != null ? "user-group" : "continuation";
|
|
1301
|
+
}
|
|
1302
|
+
async function fetchSessionEventsPage(server, sessionId, options) {
|
|
1303
|
+
const page = await server.listEvents({
|
|
1304
|
+
sessionId,
|
|
1305
|
+
limit: SESSION_EVENTS_PAGE_SIZE,
|
|
1306
|
+
...options?.lastTurnId != null ? { lastTurnId: options.lastTurnId } : {},
|
|
1307
|
+
...options?.pageToken != null ? { pageToken: options.pageToken } : {}
|
|
1308
|
+
});
|
|
1309
|
+
const olderPageToken = page.nextPageToken;
|
|
1310
|
+
const hasOlder = olderPageToken != null && olderPageToken !== "";
|
|
1311
|
+
return {
|
|
1312
|
+
itemsNewestFirst: page.data,
|
|
1313
|
+
...olderPageToken != null && olderPageToken !== "" ? { olderPageToken } : {},
|
|
1314
|
+
hasOlder
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
async function fetchSessionEventsWindow(server, sessionId, options) {
|
|
1318
|
+
let itemsNewestFirst = [];
|
|
1319
|
+
let pageToken = options?.pageToken;
|
|
1320
|
+
let olderPageToken;
|
|
1321
|
+
let hasOlder = false;
|
|
1322
|
+
for (let pageCount = 0; pageCount < MAX_HISTORY_BOUNDARY_PAGES; pageCount++) {
|
|
1323
|
+
const page = await fetchSessionEventsPage(server, sessionId, {
|
|
1324
|
+
...options,
|
|
1325
|
+
...pageToken != null ? { pageToken } : {}
|
|
1326
|
+
});
|
|
1327
|
+
itemsNewestFirst = [...itemsNewestFirst, ...page.itemsNewestFirst];
|
|
1328
|
+
olderPageToken = page.olderPageToken;
|
|
1329
|
+
hasOlder = page.hasOlder;
|
|
1330
|
+
const itemsAsc2 = trimIncompleteLeadingEvents([...itemsNewestFirst].reverse());
|
|
1331
|
+
const groupState = oldestCompleteTurnGroupState(itemsAsc2);
|
|
1332
|
+
if (groupState === "user-group" || !hasOlder) {
|
|
1333
|
+
return {
|
|
1334
|
+
itemsAsc: itemsAsc2,
|
|
1335
|
+
...olderPageToken != null ? { olderPageToken } : {},
|
|
1336
|
+
hasOlder
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
if (olderPageToken == null) {
|
|
1340
|
+
return { itemsAsc: itemsAsc2, hasOlder: false };
|
|
1341
|
+
}
|
|
1342
|
+
pageToken = olderPageToken;
|
|
1343
|
+
}
|
|
1344
|
+
const itemsAsc = trimIncompleteLeadingEvents([...itemsNewestFirst].reverse());
|
|
1345
|
+
return {
|
|
1346
|
+
itemsAsc,
|
|
1347
|
+
...olderPageToken != null ? { olderPageToken } : {},
|
|
1348
|
+
hasOlder
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
async function fetchAllSessionEvents(server, sessionId, options) {
|
|
1352
|
+
const items = await drainListPages(
|
|
1353
|
+
(pageToken) => server.listEvents({
|
|
1354
|
+
sessionId,
|
|
1355
|
+
limit: SESSION_EVENTS_PAGE_SIZE,
|
|
1356
|
+
...options?.lastTurnId != null ? { lastTurnId: options.lastTurnId } : {},
|
|
1357
|
+
...pageToken != null ? { pageToken } : {}
|
|
1358
|
+
})
|
|
1359
|
+
);
|
|
1360
|
+
items.reverse();
|
|
1361
|
+
return items;
|
|
1362
|
+
}
|
|
1363
|
+
function cloneThreadBucket(bucket) {
|
|
1364
|
+
return {
|
|
1365
|
+
events: new Map(bucket.events),
|
|
1366
|
+
modelMessageIds: [...bucket.modelMessageIds],
|
|
1367
|
+
toolResults: new Map(bucket.toolResults),
|
|
1368
|
+
pendingApprovals: new Map(bucket.pendingApprovals),
|
|
1369
|
+
approvalDecisions: new Map(bucket.approvalDecisions),
|
|
1370
|
+
pendingResponses: new Map(bucket.pendingResponses),
|
|
1371
|
+
done: bucket.done,
|
|
1372
|
+
...bucket.title != null ? { title: bucket.title } : {},
|
|
1373
|
+
...bucket.agentInfo != null ? { agentInfo: bucket.agentInfo } : {}
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
function prependFoldState(older, newer) {
|
|
1377
|
+
const result = new PeerThreadFoldState();
|
|
1378
|
+
const threadIds = /* @__PURE__ */ new Set([...older.threads.keys(), ...newer.threads.keys()]);
|
|
1379
|
+
for (const threadId of threadIds) {
|
|
1380
|
+
const olderBucket = older.threads.get(threadId);
|
|
1381
|
+
const newerBucket = newer.threads.get(threadId);
|
|
1382
|
+
if (olderBucket == null && newerBucket != null) {
|
|
1383
|
+
result.threads.set(threadId, cloneThreadBucket(newerBucket));
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
if (newerBucket == null && olderBucket != null) {
|
|
1387
|
+
result.threads.set(threadId, cloneThreadBucket(olderBucket));
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
if (olderBucket == null || newerBucket == null) {
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
result.threads.set(threadId, {
|
|
1394
|
+
events: new Map([...olderBucket.events, ...newerBucket.events]),
|
|
1395
|
+
modelMessageIds: [...olderBucket.modelMessageIds, ...newerBucket.modelMessageIds],
|
|
1396
|
+
toolResults: new Map([...olderBucket.toolResults, ...newerBucket.toolResults]),
|
|
1397
|
+
pendingApprovals: new Map([...olderBucket.pendingApprovals, ...newerBucket.pendingApprovals]),
|
|
1398
|
+
approvalDecisions: new Map([...olderBucket.approvalDecisions, ...newerBucket.approvalDecisions]),
|
|
1399
|
+
pendingResponses: new Map([...olderBucket.pendingResponses, ...newerBucket.pendingResponses]),
|
|
1400
|
+
done: newerBucket.done || olderBucket.done,
|
|
1401
|
+
...newerBucket.title != null || olderBucket.title != null ? { title: newerBucket.title ?? olderBucket.title } : {},
|
|
1402
|
+
...newerBucket.agentInfo != null || olderBucket.agentInfo != null ? { agentInfo: newerBucket.agentInfo ?? olderBucket.agentInfo } : {}
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
for (const [threadId, link] of older.threadParents) {
|
|
1406
|
+
result.threadParents.set(threadId, link);
|
|
1407
|
+
}
|
|
1408
|
+
for (const [threadId, link] of newer.threadParents) {
|
|
1409
|
+
result.threadParents.set(threadId, link);
|
|
1410
|
+
}
|
|
1411
|
+
return result;
|
|
1412
|
+
}
|
|
1413
|
+
function ingestSessionEventsIntoSnapshot(snapshot, items, onTurnComplete) {
|
|
1414
|
+
let currentTurnId = null;
|
|
1415
|
+
let currentCreatedEvent = null;
|
|
1416
|
+
let currentContentEvents = [];
|
|
1417
|
+
let beforeCount = 0;
|
|
1418
|
+
let sessionSandboxId;
|
|
1419
|
+
for (const item of items) {
|
|
1420
|
+
const { turnId, event } = item;
|
|
1421
|
+
if (event.type === "turn.created") {
|
|
1422
|
+
currentTurnId = turnId;
|
|
1423
|
+
currentCreatedEvent = event;
|
|
1424
|
+
currentContentEvents = [];
|
|
1425
|
+
beforeCount = snapshot.fold.threads.get(ROOT_THREAD_ID)?.modelMessageIds.length ?? 0;
|
|
1426
|
+
} else if (event.type === "turn.done") {
|
|
1427
|
+
if (currentTurnId == null || currentCreatedEvent == null) {
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
const afterBucket = snapshot.fold.threads.get(ROOT_THREAD_ID);
|
|
1431
|
+
const rootModelMessageIds = (afterBucket?.modelMessageIds ?? []).slice(beforeCount);
|
|
1432
|
+
const sandboxEvent = currentContentEvents.find(
|
|
1433
|
+
(ev) => ev.type === "sandbox.created"
|
|
1434
|
+
);
|
|
1435
|
+
sessionSandboxId = sandboxEvent?.sandboxId ?? sessionSandboxId;
|
|
1436
|
+
applyUserToolResponsesToFold(snapshot.fold, currentCreatedEvent.input ?? []);
|
|
1437
|
+
snapshot.turns.push(
|
|
1438
|
+
sessionEventsToSessionRecord(currentTurnId, currentCreatedEvent, event, rootModelMessageIds, sessionSandboxId)
|
|
1439
|
+
);
|
|
1440
|
+
onTurnComplete?.(replaceSessionSnapshot(snapshot, {}));
|
|
1441
|
+
currentTurnId = null;
|
|
1442
|
+
currentCreatedEvent = null;
|
|
1443
|
+
currentContentEvents = [];
|
|
1444
|
+
} else {
|
|
1445
|
+
if (currentTurnId != null) {
|
|
1446
|
+
ingestTurnEvent(snapshot.fold, event);
|
|
1447
|
+
currentContentEvents.push(event);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
function attachRunningTurn(snapshot, runningTurn) {
|
|
1453
|
+
if (runningTurn == null) {
|
|
1454
|
+
return snapshot;
|
|
1455
|
+
}
|
|
1456
|
+
const pendingUserText = extractTurnUserText(runningTurn.input);
|
|
1457
|
+
return replaceSessionSnapshot(snapshot, {
|
|
1458
|
+
runningTurn,
|
|
1459
|
+
unstable_resume: true,
|
|
1460
|
+
groupRootBaseline: computeGroupRootBaseline(snapshot.turns),
|
|
1461
|
+
...pendingUserText !== void 0 ? {
|
|
1462
|
+
pendingUser: {
|
|
1463
|
+
turnId: runningTurn.id,
|
|
1464
|
+
content: extractTurnUserMessageContent(runningTurn.input),
|
|
1465
|
+
createdAt: new Date(runningTurn.createdAt)
|
|
1466
|
+
}
|
|
1467
|
+
} : {}
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
function findOpenTurnCreated(itemsAsc) {
|
|
1471
|
+
let open;
|
|
1472
|
+
for (const item of itemsAsc) {
|
|
1473
|
+
if (item.event.type === "turn.created") {
|
|
1474
|
+
open = { turnId: item.turnId, event: item.event };
|
|
1475
|
+
} else if (item.event.type === "turn.done") {
|
|
1476
|
+
open = void 0;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
return open;
|
|
1480
|
+
}
|
|
1481
|
+
function turnFromCreatedEvent(options) {
|
|
1482
|
+
const { sessionId, turnId, event } = options;
|
|
1483
|
+
return {
|
|
1484
|
+
id: turnId,
|
|
1485
|
+
sessionId,
|
|
1486
|
+
state: { status: "running" },
|
|
1487
|
+
createdAt: event.createdAt,
|
|
1488
|
+
...event.input != null ? { input: event.input } : {},
|
|
1489
|
+
...event.previousTurnId === void 0 ? {} : { previousTurnId: event.previousTurnId }
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
async function resolveSessionTip(options) {
|
|
1493
|
+
const { server, sessionId, itemsAsc } = options;
|
|
1494
|
+
const open = findOpenTurnCreated(itemsAsc);
|
|
1495
|
+
if (open != null) {
|
|
1496
|
+
const continuationInput = open.event.input ?? [];
|
|
1497
|
+
if (typeof server.getTurn === "function") {
|
|
1498
|
+
try {
|
|
1499
|
+
const turn = await server.getTurn({
|
|
1500
|
+
sessionId,
|
|
1501
|
+
turnId: open.turnId
|
|
1502
|
+
});
|
|
1503
|
+
if (turn.state.status === "running") {
|
|
1504
|
+
return {
|
|
1505
|
+
runningTurn: turn,
|
|
1506
|
+
continuationInput: turn.input ?? continuationInput
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
return { continuationInput: turn.input ?? continuationInput };
|
|
1510
|
+
} catch {
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
return {
|
|
1514
|
+
runningTurn: turnFromCreatedEvent({
|
|
1515
|
+
sessionId,
|
|
1516
|
+
turnId: open.turnId,
|
|
1517
|
+
event: open.event
|
|
1518
|
+
}),
|
|
1519
|
+
continuationInput
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
const turnsPage = await server.listTurns({ sessionId, limit: 1 });
|
|
1523
|
+
const tip = turnsPage.data[0];
|
|
1524
|
+
return tip?.state.status === "running" ? { runningTurn: tip, continuationInput: tip.input ?? [] } : { continuationInput: [] };
|
|
1525
|
+
}
|
|
1526
|
+
async function buildSnapshotFromSessionEvents(server, sessionId, onProgress) {
|
|
1527
|
+
const window = await fetchSessionEventsWindow(server, sessionId);
|
|
1528
|
+
const historyPagination = {
|
|
1529
|
+
hasOlder: window.hasOlder,
|
|
1530
|
+
...window.olderPageToken != null ? { olderPageToken: window.olderPageToken } : {}
|
|
1531
|
+
};
|
|
1532
|
+
const snapshot = createEmptySessionSnapshot();
|
|
1533
|
+
ingestSessionEventsIntoSnapshot(snapshot, window.itemsAsc, onProgress);
|
|
1534
|
+
const withHistory = replaceSessionSnapshot(snapshot, {
|
|
1535
|
+
historyEvents: window.itemsAsc,
|
|
1536
|
+
historyPagination
|
|
1537
|
+
});
|
|
1538
|
+
const tip = await resolveSessionTip({
|
|
1539
|
+
server,
|
|
1540
|
+
sessionId,
|
|
1541
|
+
itemsAsc: window.itemsAsc
|
|
1542
|
+
});
|
|
1543
|
+
applyUserToolResponsesToFold(withHistory.fold, tip.continuationInput);
|
|
1544
|
+
return attachRunningTurn(withHistory, tip.runningTurn);
|
|
1545
|
+
}
|
|
1546
|
+
async function prependOlderSessionHistory(server, sessionId, snapshot) {
|
|
1547
|
+
const pagination = snapshot.historyPagination;
|
|
1548
|
+
if (pagination?.hasOlder !== true || pagination.olderPageToken == null) {
|
|
1549
|
+
return snapshot;
|
|
1550
|
+
}
|
|
1551
|
+
const window = await fetchSessionEventsWindow(server, sessionId, {
|
|
1552
|
+
pageToken: pagination.olderPageToken
|
|
1553
|
+
});
|
|
1554
|
+
if (window.itemsAsc.length === 0) {
|
|
1555
|
+
return replaceSessionSnapshot(snapshot, {
|
|
1556
|
+
historyPagination: { hasOlder: false }
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
const olderSnap = createEmptySessionSnapshot();
|
|
1560
|
+
ingestSessionEventsIntoSnapshot(olderSnap, window.itemsAsc);
|
|
1561
|
+
const existingIds = new Set(snapshot.turns.map((turn) => turn.id));
|
|
1562
|
+
const olderTurns = olderSnap.turns.filter((turn) => !existingIds.has(turn.id));
|
|
1563
|
+
const mergedFold = prependFoldState(olderSnap.fold, snapshot.fold);
|
|
1564
|
+
const historyEvents = [...window.itemsAsc, ...snapshot.historyEvents ?? []];
|
|
1565
|
+
const olderRootIds = olderTurns.flatMap((turn) => turn.rootModelMessageIds ?? []);
|
|
1566
|
+
let knownSandboxId;
|
|
1567
|
+
const mergedTurns = [...olderTurns, ...snapshot.turns].map((turn) => {
|
|
1568
|
+
if (turn.sandboxId != null) {
|
|
1569
|
+
knownSandboxId = turn.sandboxId;
|
|
1570
|
+
return turn;
|
|
1571
|
+
}
|
|
1572
|
+
if (knownSandboxId != null) {
|
|
1573
|
+
return { ...turn, sandboxId: knownSandboxId };
|
|
1574
|
+
}
|
|
1575
|
+
return turn;
|
|
1576
|
+
});
|
|
1577
|
+
return replaceSessionSnapshot(snapshot, {
|
|
1578
|
+
fold: mergedFold,
|
|
1579
|
+
turns: mergedTurns,
|
|
1580
|
+
historyEvents,
|
|
1581
|
+
historyPagination: {
|
|
1582
|
+
hasOlder: window.hasOlder,
|
|
1583
|
+
...window.olderPageToken != null ? { olderPageToken: window.olderPageToken } : {}
|
|
1584
|
+
},
|
|
1585
|
+
...snapshot.groupRootBaseline != null ? {
|
|
1586
|
+
groupRootBaseline: [...olderRootIds, ...snapshot.groupRootBaseline]
|
|
1587
|
+
} : {}
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
function assistantStatusFromTurnState(state) {
|
|
1591
|
+
switch (state.status) {
|
|
1592
|
+
case "done":
|
|
1593
|
+
return { type: "complete", reason: "stop" };
|
|
1594
|
+
case "error":
|
|
1595
|
+
return { type: "incomplete", reason: "error", error: state.message };
|
|
1596
|
+
case "cancelled":
|
|
1597
|
+
return { type: "incomplete", reason: "cancelled" };
|
|
1598
|
+
case "running":
|
|
1599
|
+
return { type: "running" };
|
|
1600
|
+
default:
|
|
1601
|
+
return { type: "complete", reason: "unknown" };
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
function resolveCreatedAt(messageId, fallback, options, replace = false) {
|
|
1605
|
+
return options?.getCreatedAt?.(messageId, fallback, replace) ?? fallback;
|
|
1606
|
+
}
|
|
1607
|
+
function parseDataUriMime2(data) {
|
|
1608
|
+
if (!data.startsWith("data:")) {
|
|
1609
|
+
return "application/octet-stream";
|
|
1610
|
+
}
|
|
1611
|
+
const match = /^data:([^;,]+)/.exec(data);
|
|
1612
|
+
return match?.[1] ?? "application/octet-stream";
|
|
1613
|
+
}
|
|
1614
|
+
function fileContentToAttachment(file, attachmentId) {
|
|
1615
|
+
const mimeType = parseDataUriMime2(file.data);
|
|
1616
|
+
if (mimeType.startsWith("image/")) {
|
|
1617
|
+
return {
|
|
1618
|
+
id: attachmentId,
|
|
1619
|
+
type: "image",
|
|
1620
|
+
name: file.name,
|
|
1621
|
+
contentType: mimeType,
|
|
1622
|
+
status: { type: "complete" },
|
|
1623
|
+
content: [{ type: "image", image: file.data, filename: file.name }]
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
return {
|
|
1627
|
+
id: attachmentId,
|
|
1628
|
+
type: "file",
|
|
1629
|
+
name: file.name,
|
|
1630
|
+
contentType: mimeType,
|
|
1631
|
+
status: { type: "complete" },
|
|
1632
|
+
content: [
|
|
1633
|
+
{
|
|
1634
|
+
type: "file",
|
|
1635
|
+
mimeType,
|
|
1636
|
+
filename: file.name,
|
|
1637
|
+
data: file.data
|
|
1638
|
+
}
|
|
1639
|
+
]
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function buildUserMessageFromTurnInput(turnId, input, createdAt, options) {
|
|
1643
|
+
const fallback = createdAt instanceof Date ? createdAt : new Date(createdAt);
|
|
1644
|
+
const id = `${turnId}-user`;
|
|
1645
|
+
const content = [];
|
|
1646
|
+
const attachments = [];
|
|
1647
|
+
for (const item of input ?? []) {
|
|
1648
|
+
if (item.type !== "user.message") {
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
const messageContent = item.content;
|
|
1652
|
+
if (typeof messageContent === "string") {
|
|
1653
|
+
content.push({ type: "text", text: messageContent });
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
for (const part of messageContent) {
|
|
1657
|
+
const imageUrl = extractImageUrlFromUserContentItem(part);
|
|
1658
|
+
if (imageUrl != null) {
|
|
1659
|
+
attachments.push(imageUrlToAttachment(imageUrl, `${turnId}-file-${String(attachments.length)}`));
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
if (part.type === "text") {
|
|
1663
|
+
content.push({ type: "text", text: part.text });
|
|
1664
|
+
} else {
|
|
1665
|
+
attachments.push(fileContentToAttachment(part, `${turnId}-file-${String(attachments.length)}`));
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
return {
|
|
1670
|
+
id,
|
|
1671
|
+
role: "user",
|
|
1672
|
+
content: content.length > 0 ? content : [{ type: "text", text: "" }],
|
|
1673
|
+
attachments,
|
|
1674
|
+
createdAt: resolveCreatedAt(id, fallback, options),
|
|
1675
|
+
metadata: { custom: {} }
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function buildAssistantMessage(turnId, content, createdAt, status, custom = {}, options, replaceCreatedAt = false) {
|
|
1679
|
+
const fallback = createdAt instanceof Date ? createdAt : new Date(createdAt);
|
|
1680
|
+
const id = `${turnId}-assistant`;
|
|
1681
|
+
return {
|
|
1682
|
+
id,
|
|
1683
|
+
role: "assistant",
|
|
1684
|
+
content,
|
|
1685
|
+
status,
|
|
1686
|
+
createdAt: resolveCreatedAt(id, fallback, options, replaceCreatedAt),
|
|
1687
|
+
metadata: {
|
|
1688
|
+
unstable_state: null,
|
|
1689
|
+
unstable_annotations: [],
|
|
1690
|
+
unstable_data: [],
|
|
1691
|
+
steps: [],
|
|
1692
|
+
custom
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
async function ingestTurnEventsIntoFold(server, sessionId, turnId, foldState) {
|
|
1697
|
+
if (server.listTurnEvents == null) {
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
const listTurnEvents = server.listTurnEvents.bind(server);
|
|
1701
|
+
const events = await drainListPages(
|
|
1702
|
+
(pageToken) => listTurnEvents({
|
|
1703
|
+
sessionId,
|
|
1704
|
+
turnId,
|
|
1705
|
+
order: "asc",
|
|
1706
|
+
limit: TURN_EVENTS_PAGE_SIZE,
|
|
1707
|
+
...pageToken != null ? { pageToken } : {}
|
|
1708
|
+
})
|
|
1709
|
+
);
|
|
1710
|
+
for (const event of events) {
|
|
1711
|
+
ingestTurnEvent(foldState, event);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
async function fetchTurnEvents(server, sessionId, turnId) {
|
|
1715
|
+
if (server.listTurnEvents == null) {
|
|
1716
|
+
return [];
|
|
1717
|
+
}
|
|
1718
|
+
const listTurnEvents = server.listTurnEvents.bind(server);
|
|
1719
|
+
const events = await drainListPages(
|
|
1720
|
+
(pageToken) => listTurnEvents({
|
|
1721
|
+
sessionId,
|
|
1722
|
+
turnId,
|
|
1723
|
+
order: "asc",
|
|
1724
|
+
limit: TURN_EVENTS_PAGE_SIZE,
|
|
1725
|
+
...pageToken != null ? { pageToken } : {}
|
|
1726
|
+
})
|
|
1727
|
+
);
|
|
1728
|
+
return events;
|
|
1729
|
+
}
|
|
1730
|
+
function ingestCollectedEventsIntoFold(foldState, events) {
|
|
1731
|
+
for (const event of events) {
|
|
1732
|
+
ingestTurnEvent(foldState, event);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
async function fetchAllTurnEventsWithConcurrency(server, sessionId, turns, concurrency) {
|
|
1736
|
+
const results = Array.from({ length: turns.length }, () => []);
|
|
1737
|
+
const pool = /* @__PURE__ */ new Set();
|
|
1738
|
+
for (let i = 0; i < turns.length; i++) {
|
|
1739
|
+
const idx = i;
|
|
1740
|
+
const turn = turns[idx];
|
|
1741
|
+
if (turn == null) {
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
const p = fetchTurnEvents(server, sessionId, turn.id).then((events) => {
|
|
1745
|
+
results[idx] = events;
|
|
1746
|
+
pool.delete(p);
|
|
1747
|
+
});
|
|
1748
|
+
pool.add(p);
|
|
1749
|
+
if (pool.size >= concurrency) {
|
|
1750
|
+
await Promise.race(pool);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
await Promise.all(pool);
|
|
1754
|
+
return results;
|
|
1755
|
+
}
|
|
1756
|
+
function rootModelMessageIdsSinceBaseline(foldState, baseline) {
|
|
1757
|
+
const bucket = foldState.threads.get(ROOT_THREAD_ID);
|
|
1758
|
+
if (bucket == null) {
|
|
1759
|
+
return [];
|
|
1760
|
+
}
|
|
1761
|
+
if (baseline.length === 0) {
|
|
1762
|
+
return [...bucket.modelMessageIds];
|
|
1763
|
+
}
|
|
1764
|
+
const baselineSet = new Set(baseline);
|
|
1765
|
+
return bucket.modelMessageIds.filter((id) => !baselineSet.has(id));
|
|
1766
|
+
}
|
|
1767
|
+
function computeGroupRootBaseline(turns) {
|
|
1768
|
+
let groupStartIndex = turns.length - 1;
|
|
1769
|
+
while (groupStartIndex >= 0 && turns[groupStartIndex]?.userText == null) {
|
|
1770
|
+
groupStartIndex--;
|
|
1771
|
+
}
|
|
1772
|
+
const baseline = [];
|
|
1773
|
+
for (let i = 0; i < groupStartIndex; i++) {
|
|
1774
|
+
baseline.push(...turns[i]?.rootModelMessageIds ?? []);
|
|
1775
|
+
}
|
|
1776
|
+
return baseline;
|
|
1777
|
+
}
|
|
1778
|
+
function buildTurnUpdateFromFold(foldState, turn, rootModelMessageIds) {
|
|
1779
|
+
const content = buildRootAssistantContentForIds(foldState, rootModelMessageIds);
|
|
1780
|
+
let update = { content };
|
|
1781
|
+
update = appendMcpAuthToTurnContent(update.content, turn);
|
|
1782
|
+
update = appendToolApprovalToTurnContent(update, turn);
|
|
1783
|
+
return update;
|
|
1784
|
+
}
|
|
1785
|
+
function contentHasPendingRequiredActions(content) {
|
|
1786
|
+
const message = {
|
|
1787
|
+
id: "pending-check",
|
|
1788
|
+
role: "assistant",
|
|
1789
|
+
content,
|
|
1790
|
+
status: { type: "complete", reason: "stop" },
|
|
1791
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1792
|
+
metadata: {
|
|
1793
|
+
unstable_state: null,
|
|
1794
|
+
unstable_annotations: [],
|
|
1795
|
+
unstable_data: [],
|
|
1796
|
+
steps: [],
|
|
1797
|
+
custom: {}
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
return messageHasPendingApprovals(message) || messageHasPendingResponses(message);
|
|
1801
|
+
}
|
|
1802
|
+
function resolveProjectedRequiredActionState(turnUpdate, content, record) {
|
|
1803
|
+
let status = turnUpdate.status ?? assistantStatusFromTurnState(record.state);
|
|
1804
|
+
let custom = { ...turnUpdate.metadata?.custom ?? {} };
|
|
1805
|
+
if (status.type === "requires-action" && status.reason === "tool-calls" && !contentHasPendingRequiredActions(content)) {
|
|
1806
|
+
status = assistantStatusFromTurnState(record.state);
|
|
1807
|
+
custom = Object.fromEntries(
|
|
1808
|
+
Object.entries(custom).filter(
|
|
1809
|
+
([key]) => key !== TOOL_APPROVAL_THREAD_ID_CUSTOM_KEY && key !== TOOL_RESPONSE_THREAD_ID_CUSTOM_KEY
|
|
1810
|
+
)
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
return { status, custom };
|
|
1814
|
+
}
|
|
1815
|
+
function projectActiveStreamUpdate(snapshot) {
|
|
1816
|
+
const activeStream = snapshot.activeStream;
|
|
1817
|
+
if (activeStream == null) {
|
|
1818
|
+
throw new Error("projectActiveStreamUpdate requires an active stream");
|
|
1819
|
+
}
|
|
1820
|
+
const hasStagedOverlay = snapshot.requiredActions.approvals.size > 0 || snapshot.requiredActions.toolResponses.size > 0;
|
|
1821
|
+
if (hasStagedOverlay) {
|
|
1822
|
+
return activeStream.update;
|
|
1823
|
+
}
|
|
1824
|
+
const baseline = snapshot.groupRootBaseline ?? computeGroupRootBaseline(snapshot.turns);
|
|
1825
|
+
const rootModelMessageIds = rootModelMessageIdsSinceBaseline(snapshot.fold, baseline);
|
|
1826
|
+
const foldContent = buildRootAssistantContentForIds(snapshot.fold, rootModelMessageIds);
|
|
1827
|
+
const content = foldContent.length > 0 ? foldContent : activeStream.update.content;
|
|
1828
|
+
const turnRecord = snapshot.turns.find((turn) => turn.id === activeStream.turnId);
|
|
1829
|
+
const turnLike = turnRecord ?? snapshot.runningTurn;
|
|
1830
|
+
const rebuilt = turnLike != null ? buildTurnUpdateFromFold(snapshot.fold, turnLike, rootModelMessageIds) : { content };
|
|
1831
|
+
const metadata = rebuilt.metadata ?? activeStream.update.metadata;
|
|
1832
|
+
const status = rebuilt.status ?? activeStream.update.status;
|
|
1833
|
+
return {
|
|
1834
|
+
...activeStream.update,
|
|
1835
|
+
content,
|
|
1836
|
+
...metadata == null ? {} : { metadata },
|
|
1837
|
+
...status == null ? {} : { status }
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
function applyRequiredActionsOverlayToMessages(messages, overlay) {
|
|
1841
|
+
if (overlay.approvals.size === 0 && overlay.toolResponses.size === 0) {
|
|
1842
|
+
return messages;
|
|
1843
|
+
}
|
|
1844
|
+
return messages.map((message) => {
|
|
1845
|
+
if (message.role !== "assistant") {
|
|
1846
|
+
return message;
|
|
1847
|
+
}
|
|
1848
|
+
let { content } = message;
|
|
1849
|
+
if (overlay.approvals.size > 0) {
|
|
1850
|
+
content = applyApprovalDecisionsToContent(content, overlay.approvals);
|
|
1851
|
+
}
|
|
1852
|
+
if (overlay.toolResponses.size > 0) {
|
|
1853
|
+
content = applyStagedResponsesToContent(content, overlay.toolResponses);
|
|
1854
|
+
}
|
|
1855
|
+
if (content === message.content) {
|
|
1856
|
+
return message;
|
|
1857
|
+
}
|
|
1858
|
+
return { ...message, content: [...content] };
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
function projectHistoryTurns(snapshot, options) {
|
|
1862
|
+
const messages = [];
|
|
1863
|
+
let lastAssistantIndex;
|
|
1864
|
+
let groupRootIds = [];
|
|
1865
|
+
let sandboxId;
|
|
1866
|
+
for (let turnIndex = 0; turnIndex < snapshot.turns.length; turnIndex++) {
|
|
1867
|
+
const record = snapshot.turns[turnIndex];
|
|
1868
|
+
if (record == null) {
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
const turnRootIds = record.rootModelMessageIds ?? [];
|
|
1872
|
+
sandboxId = record.sandboxId ?? sandboxId;
|
|
1873
|
+
if (record.userText !== void 0) {
|
|
1874
|
+
groupRootIds = [...turnRootIds];
|
|
1875
|
+
} else {
|
|
1876
|
+
groupRootIds = [...groupRootIds, ...turnRootIds];
|
|
1877
|
+
}
|
|
1878
|
+
const turnUpdate = buildTurnUpdateFromFold(snapshot.fold, record, groupRootIds);
|
|
1879
|
+
let content = turnUpdate.content;
|
|
1880
|
+
const subsequentDecisions = collectSubsequentApprovalDecisions(snapshot.turns, turnIndex);
|
|
1881
|
+
if (subsequentDecisions.size > 0) {
|
|
1882
|
+
content = applyApprovalDecisionsToContent(content, subsequentDecisions);
|
|
1883
|
+
}
|
|
1884
|
+
const subsequentResponses = collectSubsequentToolResponses(snapshot.turns, turnIndex);
|
|
1885
|
+
if (subsequentResponses.size > 0) {
|
|
1886
|
+
content = applyStagedResponsesToContent(content, subsequentResponses);
|
|
1887
|
+
}
|
|
1888
|
+
const currentResponses = collectToolResponsesFromTurnInput(record.input);
|
|
1889
|
+
if (currentResponses.size > 0) {
|
|
1890
|
+
content = applyStagedResponsesToContent(content, currentResponses);
|
|
1891
|
+
}
|
|
1892
|
+
const currentDecisions = collectApprovalDecisionsFromTurnInput(record.input);
|
|
1893
|
+
if (currentDecisions.size > 0) {
|
|
1894
|
+
content = applyApprovalDecisionsToContent(content, currentDecisions);
|
|
1895
|
+
}
|
|
1896
|
+
const { status, custom: baseCustom } = resolveProjectedRequiredActionState(turnUpdate, content, record);
|
|
1897
|
+
const custom = {
|
|
1898
|
+
...baseCustom,
|
|
1899
|
+
turnId: record.id,
|
|
1900
|
+
...sandboxId != null ? { sandboxId } : {}
|
|
1901
|
+
};
|
|
1902
|
+
const assistantCreatedAt = record.state.status === "running" ? record.createdAt : record.state.completedAt;
|
|
1903
|
+
const replaceAssistantCreatedAt = record.state.status !== "running";
|
|
1904
|
+
if (record.userText !== void 0) {
|
|
1905
|
+
messages.push(buildUserMessageFromTurnInput(record.id, record.input, record.createdAt, options));
|
|
1906
|
+
if (record.state.status === "running") {
|
|
1907
|
+
if (content.length > 0) {
|
|
1908
|
+
messages.push(
|
|
1909
|
+
buildAssistantMessage(
|
|
1910
|
+
record.id,
|
|
1911
|
+
content,
|
|
1912
|
+
assistantCreatedAt,
|
|
1913
|
+
status,
|
|
1914
|
+
custom,
|
|
1915
|
+
options,
|
|
1916
|
+
replaceAssistantCreatedAt
|
|
1917
|
+
)
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
break;
|
|
1921
|
+
}
|
|
1922
|
+
if (content.length > 0) {
|
|
1923
|
+
messages.push(
|
|
1924
|
+
buildAssistantMessage(
|
|
1925
|
+
record.id,
|
|
1926
|
+
content,
|
|
1927
|
+
assistantCreatedAt,
|
|
1928
|
+
status,
|
|
1929
|
+
custom,
|
|
1930
|
+
options,
|
|
1931
|
+
replaceAssistantCreatedAt
|
|
1932
|
+
)
|
|
1933
|
+
);
|
|
1934
|
+
lastAssistantIndex = messages.length - 1;
|
|
1935
|
+
}
|
|
1936
|
+
} else if (content.length > 0 && lastAssistantIndex != null) {
|
|
1937
|
+
if (record.state.status === "running") {
|
|
1938
|
+
break;
|
|
1939
|
+
}
|
|
1940
|
+
const existing = messages[lastAssistantIndex];
|
|
1941
|
+
if (existing?.role !== "assistant") {
|
|
1942
|
+
continue;
|
|
1943
|
+
}
|
|
1944
|
+
messages[lastAssistantIndex] = {
|
|
1945
|
+
...existing,
|
|
1946
|
+
content,
|
|
1947
|
+
status,
|
|
1948
|
+
createdAt: resolveCreatedAt(existing.id, new Date(assistantCreatedAt), options, true),
|
|
1949
|
+
metadata: {
|
|
1950
|
+
...existing.metadata,
|
|
1951
|
+
custom
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
} else if (record.state.status === "running") {
|
|
1955
|
+
break;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
return messages;
|
|
1959
|
+
}
|
|
1960
|
+
function projectSessionMessages(snapshot, options) {
|
|
1961
|
+
let messages = projectHistoryTurns(snapshot, options);
|
|
1962
|
+
if (snapshot.pendingUser != null) {
|
|
1963
|
+
messages.push(
|
|
1964
|
+
buildUserMessageFromTurnInput(
|
|
1965
|
+
snapshot.pendingUser.turnId,
|
|
1966
|
+
[{ type: "user.message", content: snapshot.pendingUser.content }],
|
|
1967
|
+
snapshot.pendingUser.createdAt,
|
|
1968
|
+
options
|
|
1969
|
+
)
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
if (snapshot.activeStream != null) {
|
|
1973
|
+
const { turnId, update, isContinuation, streamComplete } = snapshot.activeStream;
|
|
1974
|
+
const resolvedUpdate = streamComplete === true ? projectActiveStreamUpdate(snapshot) : update;
|
|
1975
|
+
const last = messages.at(-1);
|
|
1976
|
+
const existingAssistant = isContinuation && last?.role === "assistant" ? last : void 0;
|
|
1977
|
+
let assistantMessage = turnStreamUpdateToAssistantMessage(turnId, resolvedUpdate, existingAssistant, options);
|
|
1978
|
+
if (streamComplete === true && resolvedUpdate.status == null) {
|
|
1979
|
+
assistantMessage = {
|
|
1980
|
+
...assistantMessage,
|
|
1981
|
+
status: { type: "complete", reason: "stop" }
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
if (isContinuation && last?.role === "assistant") {
|
|
1985
|
+
messages = [...messages.slice(0, -1), assistantMessage];
|
|
1986
|
+
} else {
|
|
1987
|
+
messages = [...messages, assistantMessage];
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
return applyRequiredActionsOverlayToMessages(messages, snapshot.requiredActions);
|
|
1991
|
+
}
|
|
1992
|
+
var DEFAULT_LIST_EVENTS_CONCURRENCY = 5;
|
|
1993
|
+
function ingestTurnsIntoSnapshot(snapshot, turns, eventArrays) {
|
|
1994
|
+
let runningTurn;
|
|
1995
|
+
let sessionSandboxId;
|
|
1996
|
+
for (let i = 0; i < turns.length; i++) {
|
|
1997
|
+
const turn = turns[i];
|
|
1998
|
+
if (turn == null) {
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
const rootBucket = snapshot.fold.threads.get(ROOT_THREAD_ID);
|
|
2002
|
+
const beforeCount = rootBucket?.modelMessageIds.length ?? 0;
|
|
2003
|
+
ingestCollectedEventsIntoFold(snapshot.fold, eventArrays[i] ?? []);
|
|
2004
|
+
applyUserToolResponsesToFold(snapshot.fold, turn.input ?? []);
|
|
2005
|
+
const afterBucket = snapshot.fold.threads.get(ROOT_THREAD_ID);
|
|
2006
|
+
const rootModelMessageIds = (afterBucket?.modelMessageIds ?? []).slice(beforeCount);
|
|
2007
|
+
const sandboxEvent = (eventArrays[i] ?? []).find(
|
|
2008
|
+
(event) => event.type === "sandbox.created"
|
|
2009
|
+
);
|
|
2010
|
+
sessionSandboxId = sandboxEvent?.sandboxId ?? sessionSandboxId;
|
|
2011
|
+
snapshot.turns.push({
|
|
2012
|
+
...turnToSessionRecord(turn),
|
|
2013
|
+
rootModelMessageIds,
|
|
2014
|
+
...sessionSandboxId != null ? { sandboxId: sessionSandboxId } : {}
|
|
2015
|
+
});
|
|
2016
|
+
if (turn.state.status === "running") {
|
|
2017
|
+
runningTurn = turn;
|
|
2018
|
+
break;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
return runningTurn;
|
|
2022
|
+
}
|
|
2023
|
+
async function buildSnapshotFromSession(server, sessionId, concurrency = DEFAULT_LIST_EVENTS_CONCURRENCY) {
|
|
2024
|
+
const snapshot = await buildSnapshotFromSessionEvents(server, sessionId);
|
|
2025
|
+
if (snapshot.runningTurn == null) {
|
|
2026
|
+
return snapshot;
|
|
2027
|
+
}
|
|
2028
|
+
const turn = snapshot.runningTurn;
|
|
2029
|
+
const eventArrays = await fetchAllTurnEventsWithConcurrency(server, sessionId, [turn], concurrency);
|
|
2030
|
+
ingestTurnsIntoSnapshot(snapshot, [turn], eventArrays);
|
|
2031
|
+
const snapshotWithoutPendingUser = { ...snapshot };
|
|
2032
|
+
delete snapshotWithoutPendingUser.pendingUser;
|
|
2033
|
+
return replaceSessionSnapshot(snapshotWithoutPendingUser, {
|
|
2034
|
+
runningTurn: turn,
|
|
2035
|
+
unstable_resume: true,
|
|
2036
|
+
groupRootBaseline: computeGroupRootBaseline(snapshot.turns)
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
async function buildSnapshotThroughTurn(server, sessionId, anchorTurnId) {
|
|
2040
|
+
if (anchorTurnId == null) {
|
|
2041
|
+
return createEmptySessionSnapshot();
|
|
2042
|
+
}
|
|
2043
|
+
const items = await fetchAllSessionEvents(server, sessionId, {
|
|
2044
|
+
lastTurnId: anchorTurnId
|
|
2045
|
+
});
|
|
2046
|
+
const snapshot = createEmptySessionSnapshot();
|
|
2047
|
+
ingestSessionEventsIntoSnapshot(snapshot, items);
|
|
2048
|
+
return snapshot;
|
|
2049
|
+
}
|
|
2050
|
+
async function resolveGatewayBranchPreviousTurnIdForTurn(server, sessionId, turnId) {
|
|
2051
|
+
const turn = await server.getTurn({ sessionId, turnId });
|
|
2052
|
+
return turn.previousTurnId ?? "none";
|
|
2053
|
+
}
|
|
2054
|
+
async function buildTurnAssistantContent(server, sessionId, turn, foldState) {
|
|
2055
|
+
const state = foldState ?? new PeerThreadFoldState();
|
|
2056
|
+
const beforeCount = state.threads.get(ROOT_THREAD_ID)?.modelMessageIds.length ?? 0;
|
|
2057
|
+
await ingestTurnEventsIntoFold(server, sessionId, turn.id, state);
|
|
2058
|
+
const afterIds = state.threads.get(ROOT_THREAD_ID)?.modelMessageIds ?? [];
|
|
2059
|
+
const rootModelMessageIds = afterIds.slice(beforeCount);
|
|
2060
|
+
return buildTurnUpdateFromFold(state, turn, rootModelMessageIds).content;
|
|
2061
|
+
}
|
|
2062
|
+
async function convertTurnsToThreadMessages(server, sessionId) {
|
|
2063
|
+
const snapshot = await buildSnapshotFromSession(server, sessionId);
|
|
2064
|
+
const messages = projectSessionMessages(snapshot);
|
|
2065
|
+
return {
|
|
2066
|
+
messages,
|
|
2067
|
+
foldState: snapshot.fold,
|
|
2068
|
+
...snapshot.runningTurn != null ? {
|
|
2069
|
+
runningTurn: snapshot.runningTurn,
|
|
2070
|
+
unstable_resume: true
|
|
2071
|
+
} : {}
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
function getTurnMessageContent(message) {
|
|
2075
|
+
const parts = [];
|
|
2076
|
+
for (const part of message.content) {
|
|
2077
|
+
if (part.type === "text") {
|
|
2078
|
+
parts.push(part.text);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
const text = parts.join("\n").trim();
|
|
2082
|
+
if (!text) {
|
|
2083
|
+
throw new Error("User message must contain text content.");
|
|
2084
|
+
}
|
|
2085
|
+
return text;
|
|
2086
|
+
}
|
|
2087
|
+
function toFileDataUri(data, mimeType) {
|
|
2088
|
+
if (data.startsWith("data:")) {
|
|
2089
|
+
return data;
|
|
2090
|
+
}
|
|
2091
|
+
return `data:${mimeType};base64,${data}`;
|
|
2092
|
+
}
|
|
2093
|
+
function toFileContent(name, data, mimeType) {
|
|
2094
|
+
return {
|
|
2095
|
+
type: "file",
|
|
2096
|
+
name,
|
|
2097
|
+
data: toFileDataUri(data, mimeType)
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
function buildUserMessageContent(message) {
|
|
2101
|
+
const inputParts = [
|
|
2102
|
+
...message.content,
|
|
2103
|
+
...message.attachments?.flatMap(
|
|
2104
|
+
(attachment) => attachment.content.map((part) => ({
|
|
2105
|
+
...part,
|
|
2106
|
+
filename: attachment.name
|
|
2107
|
+
}))
|
|
2108
|
+
) ?? []
|
|
2109
|
+
];
|
|
2110
|
+
const items = [];
|
|
2111
|
+
for (const part of inputParts) {
|
|
2112
|
+
switch (part.type) {
|
|
2113
|
+
case "text":
|
|
2114
|
+
if (part.text.trim().length > 0) {
|
|
2115
|
+
const textPart = { type: "text", text: part.text };
|
|
2116
|
+
items.push(textPart);
|
|
2117
|
+
}
|
|
2118
|
+
break;
|
|
2119
|
+
case "image":
|
|
2120
|
+
items.push(toFileContent(part.filename ?? "image", part.image, "image/png"));
|
|
2121
|
+
break;
|
|
2122
|
+
case "file":
|
|
2123
|
+
items.push(toFileContent(part.filename ?? "file", part.data, part.mimeType));
|
|
2124
|
+
break;
|
|
2125
|
+
default:
|
|
2126
|
+
break;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
const hasFile = items.some((item) => item.type === "file");
|
|
2130
|
+
if (!hasFile) {
|
|
2131
|
+
return getTurnMessageContent(message);
|
|
2132
|
+
}
|
|
2133
|
+
return items;
|
|
2134
|
+
}
|
|
2135
|
+
function userMessageContentToText(content) {
|
|
2136
|
+
if (typeof content === "string") {
|
|
2137
|
+
return content;
|
|
2138
|
+
}
|
|
2139
|
+
return content.filter((item) => item.type === "text").map((item) => item.text).join("\n").trim();
|
|
2140
|
+
}
|
|
2141
|
+
function parseTurnIdFromMessageId(messageId) {
|
|
2142
|
+
return messageId.replace(/-user$/, "");
|
|
2143
|
+
}
|
|
2144
|
+
function extractEditedText(message) {
|
|
2145
|
+
return getTurnMessageContent(message);
|
|
2146
|
+
}
|
|
2147
|
+
function extractTurnUserMessageContent(input) {
|
|
2148
|
+
for (const item of input ?? []) {
|
|
2149
|
+
if (item.type === "user.message") {
|
|
2150
|
+
return item.content;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
return "";
|
|
2154
|
+
}
|
|
2155
|
+
function buildEditedUserMessageContent(editedText, originalInput) {
|
|
2156
|
+
const fileParts = [];
|
|
2157
|
+
for (const item of originalInput ?? []) {
|
|
2158
|
+
if (item.type !== "user.message") {
|
|
2159
|
+
continue;
|
|
2160
|
+
}
|
|
2161
|
+
const content = item.content;
|
|
2162
|
+
if (typeof content === "string") {
|
|
2163
|
+
continue;
|
|
2164
|
+
}
|
|
2165
|
+
for (const part of content) {
|
|
2166
|
+
if (part.type === "file") {
|
|
2167
|
+
fileParts.push(part);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
if (fileParts.length === 0) {
|
|
2172
|
+
return editedText;
|
|
2173
|
+
}
|
|
2174
|
+
const items = [];
|
|
2175
|
+
if (editedText.trim().length > 0) {
|
|
2176
|
+
items.push({ type: "text", text: editedText });
|
|
2177
|
+
}
|
|
2178
|
+
items.push(...fileParts);
|
|
2179
|
+
return items;
|
|
2180
|
+
}
|
|
2181
|
+
function buildMcpAuthUpdate(pendingMcpAuth, foldState, groupRootBaseline) {
|
|
2182
|
+
const base = groupRootBaseline != null ? buildRootAssistantContentForIds(foldState, rootModelMessageIdsSinceBaseline(foldState, groupRootBaseline)) : buildRootAssistantContent(foldState);
|
|
2183
|
+
return {
|
|
2184
|
+
content: [...base, ...buildMcpAuthTextParts()],
|
|
2185
|
+
status: mcpAuthAssistantStatus(),
|
|
2186
|
+
metadata: { custom: mcpAuthMessageCustom(pendingMcpAuth.mcpServers) }
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
async function* streamTurnEvents(stream, foldState, groupRootBaseline, onTurnIdAvailable) {
|
|
2190
|
+
let pendingMcpAuth;
|
|
2191
|
+
let sandboxId;
|
|
2192
|
+
let sandboxIdYielded = false;
|
|
2193
|
+
const withSandbox = (update) => {
|
|
2194
|
+
if (sandboxId == null) {
|
|
2195
|
+
return update;
|
|
2196
|
+
}
|
|
2197
|
+
return {
|
|
2198
|
+
...update,
|
|
2199
|
+
metadata: { ...update.metadata, custom: { ...update.metadata?.custom, sandboxId } }
|
|
2200
|
+
};
|
|
2201
|
+
};
|
|
2202
|
+
const yieldContent = () => {
|
|
2203
|
+
const ids = groupRootBaseline != null ? rootModelMessageIdsSinceBaseline(foldState, groupRootBaseline) : foldState.threads.get(ROOT_THREAD_ID)?.modelMessageIds ?? [];
|
|
2204
|
+
const content = buildRootAssistantContentForIds(foldState, ids);
|
|
2205
|
+
return content.length > 0 ? content : void 0;
|
|
2206
|
+
};
|
|
2207
|
+
for await (const data of stream) {
|
|
2208
|
+
const event = data.event;
|
|
2209
|
+
if (event.type === "turn.created") {
|
|
2210
|
+
onTurnIdAvailable?.(event.turnId);
|
|
2211
|
+
continue;
|
|
2212
|
+
}
|
|
2213
|
+
if (event.type === "sandbox.created") {
|
|
2214
|
+
sandboxId = event.sandboxId;
|
|
2215
|
+
continue;
|
|
2216
|
+
}
|
|
2217
|
+
if (event.type === "mcp.auth_required") {
|
|
2218
|
+
pendingMcpAuth = event;
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
if (event.type === "turn.done") {
|
|
2222
|
+
if (event.state.status === "error") {
|
|
2223
|
+
throw new Error(event.state.message);
|
|
2224
|
+
}
|
|
2225
|
+
break;
|
|
2226
|
+
}
|
|
2227
|
+
if (!ingestStreamEvent(foldState, event)) {
|
|
2228
|
+
continue;
|
|
2229
|
+
}
|
|
2230
|
+
const content = yieldContent();
|
|
2231
|
+
if (content != null) {
|
|
2232
|
+
if (sandboxId != null) {
|
|
2233
|
+
sandboxIdYielded = true;
|
|
2234
|
+
}
|
|
2235
|
+
yield withSandbox({ content });
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
if (pendingMcpAuth != null) {
|
|
2239
|
+
yield withSandbox(buildMcpAuthUpdate(pendingMcpAuth, foldState, groupRootBaseline));
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
const approvalThreadId = findFirstPendingApprovalThreadId(foldState);
|
|
2243
|
+
const responseThreadId = findFirstPendingResponseThreadId(foldState);
|
|
2244
|
+
if (approvalThreadId != null || responseThreadId != null) {
|
|
2245
|
+
const custom = {};
|
|
2246
|
+
if (approvalThreadId != null) {
|
|
2247
|
+
Object.assign(
|
|
2248
|
+
custom,
|
|
2249
|
+
toolApprovalMessageCustom(approvalThreadId === ROOT_THREAD_ID ? ROOT_THREAD_ID : approvalThreadId)
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
if (responseThreadId != null) {
|
|
2253
|
+
Object.assign(
|
|
2254
|
+
custom,
|
|
2255
|
+
toolResponseMessageCustom(responseThreadId === ROOT_THREAD_ID ? ROOT_THREAD_ID : responseThreadId)
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
const ids = groupRootBaseline != null ? rootModelMessageIdsSinceBaseline(foldState, groupRootBaseline) : foldState.threads.get(ROOT_THREAD_ID)?.modelMessageIds ?? [];
|
|
2259
|
+
yield withSandbox({
|
|
2260
|
+
content: buildRootAssistantContentForIds(foldState, ids),
|
|
2261
|
+
status: approvalThreadId != null ? toolApprovalStatus() : toolResponseStatus(),
|
|
2262
|
+
metadata: { custom }
|
|
2263
|
+
});
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
if (sandboxId != null && !sandboxIdYielded) {
|
|
2267
|
+
const ids = groupRootBaseline != null ? rootModelMessageIdsSinceBaseline(foldState, groupRootBaseline) : foldState.threads.get(ROOT_THREAD_ID)?.modelMessageIds ?? [];
|
|
2268
|
+
yield withSandbox({ content: buildRootAssistantContentForIds(foldState, ids) });
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
function turnStreamUpdateToAssistantMessage(turnId, update, existing, options) {
|
|
2272
|
+
const id = existing?.role === "assistant" ? existing.id : `${turnId}-assistant`;
|
|
2273
|
+
const fallbackCreatedAt = existing?.createdAt ?? /* @__PURE__ */ new Date();
|
|
2274
|
+
return {
|
|
2275
|
+
id,
|
|
2276
|
+
role: "assistant",
|
|
2277
|
+
content: update.content,
|
|
2278
|
+
status: update.status ?? { type: "running" },
|
|
2279
|
+
createdAt: resolveCreatedAt(id, fallbackCreatedAt, options),
|
|
2280
|
+
metadata: {
|
|
2281
|
+
unstable_state: null,
|
|
2282
|
+
unstable_annotations: [],
|
|
2283
|
+
unstable_data: [],
|
|
2284
|
+
steps: [],
|
|
2285
|
+
custom: {
|
|
2286
|
+
...existing?.role === "assistant" ? existing.metadata.custom : {},
|
|
2287
|
+
...update.metadata?.custom,
|
|
2288
|
+
turnId
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
function repositoryItemsFromMessages(messages) {
|
|
2294
|
+
const items = [];
|
|
2295
|
+
let parentId = null;
|
|
2296
|
+
for (const message of messages) {
|
|
2297
|
+
items.push({ parentId, message });
|
|
2298
|
+
parentId = message.id;
|
|
2299
|
+
}
|
|
2300
|
+
return items;
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
// src/draft/agentSpec.ts
|
|
2304
|
+
function mergeAgentSpec(base, update) {
|
|
2305
|
+
const { model: modelUpdate, ...rest } = update;
|
|
2306
|
+
const next = Object.assign({}, base, rest);
|
|
2307
|
+
if (modelUpdate != null) {
|
|
2308
|
+
next.model = Object.assign({}, base.model, modelUpdate, {
|
|
2309
|
+
name: modelUpdate.name ?? base.model.name,
|
|
2310
|
+
params: modelUpdate.params != null ? { ...base.model.params, ...modelUpdate.params } : base.model.params
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
return next;
|
|
2314
|
+
}
|
|
2315
|
+
function draftSessionTitle(draft) {
|
|
2316
|
+
return draft.title ?? draft.agentSpec.model.name;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
// src/draft/draftSessionBridge.ts
|
|
2320
|
+
var DRAFT_SESSION_LAST_UPDATED_AT_HEADER = "x-tfy-session-last-updated-at";
|
|
2321
|
+
function createDraftSessionBridge(server) {
|
|
2322
|
+
return {
|
|
2323
|
+
async getDraftAgentSpec(draftSessionId) {
|
|
2324
|
+
const session = await server.getSession({ sessionId: draftSessionId });
|
|
2325
|
+
if (session.agentSpec == null) {
|
|
2326
|
+
throw new Error(`Session ${draftSessionId} has no agentSpec (isMutable=${String(session.isMutable)}).`);
|
|
2327
|
+
}
|
|
2328
|
+
return session.agentSpec;
|
|
2329
|
+
},
|
|
2330
|
+
async syncAgentSpec(draftSessionId, agentSpec) {
|
|
2331
|
+
const updated = await server.updateSession({
|
|
2332
|
+
sessionId: draftSessionId,
|
|
2333
|
+
agentSpec
|
|
2334
|
+
});
|
|
2335
|
+
return updated.updatedAt;
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
// src/sessionListStartTimestamp.ts
|
|
2341
|
+
function sessionListStartTimestamp() {
|
|
2342
|
+
const start = /* @__PURE__ */ new Date();
|
|
2343
|
+
start.setFullYear(start.getFullYear() - 1);
|
|
2344
|
+
return start.toISOString();
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// src/sessionThreadMetadata.ts
|
|
2348
|
+
function sessionDisplayTitle(session, defaultAgentSpec) {
|
|
2349
|
+
if (session.isMutable) {
|
|
2350
|
+
const agentSpec = session.agentSpec ?? defaultAgentSpec;
|
|
2351
|
+
if (agentSpec != null) {
|
|
2352
|
+
return draftSessionTitle({
|
|
2353
|
+
agentSpec,
|
|
2354
|
+
...session.title === void 0 ? {} : { title: session.title }
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
return session.title ?? session.agentName ?? session.id;
|
|
2359
|
+
}
|
|
2360
|
+
function sessionToThreadMetadata(session, title) {
|
|
2361
|
+
return {
|
|
2362
|
+
status: "regular",
|
|
2363
|
+
remoteId: session.id,
|
|
2364
|
+
title,
|
|
2365
|
+
lastMessageAt: new Date(session.updatedAt),
|
|
2366
|
+
custom: {
|
|
2367
|
+
isMutable: session.isMutable,
|
|
2368
|
+
...session.agentName != null ? { agentName: session.agentName } : {}
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// src/draft/trueforgeDraftThreadListAdapter.ts
|
|
2374
|
+
var THREAD_LIST_PAGE_SIZE = 20;
|
|
2375
|
+
function createTrueForgeDraftThreadListAdapter(options) {
|
|
2376
|
+
const { server, defaultAgentSpec, getAgentSpec, listSessionsAgentId, listSessionsCreatedByMe = false } = options;
|
|
2377
|
+
return {
|
|
2378
|
+
async list({ after } = {}) {
|
|
2379
|
+
const page = await server.listSessions({
|
|
2380
|
+
...listSessionsAgentId != null ? { agentId: listSessionsAgentId } : {},
|
|
2381
|
+
createdByMe: listSessionsCreatedByMe,
|
|
2382
|
+
limit: THREAD_LIST_PAGE_SIZE,
|
|
2383
|
+
...after == null ? {} : { pageToken: after },
|
|
2384
|
+
startTimestamp: sessionListStartTimestamp()
|
|
2385
|
+
});
|
|
2386
|
+
const threads = page.data.map(
|
|
2387
|
+
(session) => sessionToThreadMetadata(session, sessionDisplayTitle(session, defaultAgentSpec))
|
|
2388
|
+
);
|
|
2389
|
+
return {
|
|
2390
|
+
threads,
|
|
2391
|
+
nextCursor: page.nextPageToken ?? void 0
|
|
2392
|
+
};
|
|
2393
|
+
},
|
|
2394
|
+
async initialize() {
|
|
2395
|
+
const draft = await server.createSession({
|
|
2396
|
+
agentSpec: getAgentSpec?.() ?? defaultAgentSpec
|
|
2397
|
+
});
|
|
2398
|
+
return { remoteId: draft.id, externalId: void 0 };
|
|
2399
|
+
},
|
|
2400
|
+
async fetch(remoteId) {
|
|
2401
|
+
const draft = await server.getSession({ sessionId: remoteId });
|
|
2402
|
+
return sessionToThreadMetadata(draft, sessionDisplayTitle(draft, defaultAgentSpec));
|
|
2403
|
+
},
|
|
2404
|
+
async rename(remoteId, newTitle) {
|
|
2405
|
+
if (typeof server.renameSession !== "function") {
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
await server.renameSession({ sessionId: remoteId, title: newTitle });
|
|
2409
|
+
},
|
|
2410
|
+
archive() {
|
|
2411
|
+
return Promise.resolve();
|
|
2412
|
+
},
|
|
2413
|
+
unarchive() {
|
|
2414
|
+
return Promise.resolve();
|
|
2415
|
+
},
|
|
2416
|
+
async delete(remoteId) {
|
|
2417
|
+
if (typeof server.deleteSession !== "function") {
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
await server.deleteSession({ sessionId: remoteId });
|
|
2421
|
+
},
|
|
2422
|
+
generateTitle() {
|
|
2423
|
+
return Promise.resolve(new ReadableStream());
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
// src/hooks.ts
|
|
2429
|
+
import { useAui as useAui2, useAuiState } from "@assistant-ui/store";
|
|
2430
|
+
import { useMemo } from "react";
|
|
2431
|
+
|
|
2432
|
+
// src/trueforgeExtras.ts
|
|
2433
|
+
import { useAui } from "@assistant-ui/store";
|
|
2434
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
2435
|
+
var extrasBrandSymbol = /* @__PURE__ */ Symbol("useTrueForgeAgentRuntime extras");
|
|
2436
|
+
var isTrueForgeExtras = (extras) => typeof extras === "object" && extras !== null && extrasBrandSymbol in extras;
|
|
2437
|
+
var extrasBrand = {
|
|
2438
|
+
provide: (value) => {
|
|
2439
|
+
Object.defineProperty(value, extrasBrandSymbol, {
|
|
2440
|
+
value: true,
|
|
2441
|
+
enumerable: false,
|
|
2442
|
+
configurable: true
|
|
2443
|
+
});
|
|
2444
|
+
return value;
|
|
2445
|
+
},
|
|
2446
|
+
is: isTrueForgeExtras,
|
|
2447
|
+
tryGet: (extras) => isTrueForgeExtras(extras) ? extras : void 0
|
|
2448
|
+
};
|
|
2449
|
+
var EMPTY_DRAFT_EXTRAS = {
|
|
2450
|
+
agentSpec: null,
|
|
2451
|
+
draftSessionId: void 0,
|
|
2452
|
+
isSpecLoading: false,
|
|
2453
|
+
isSpecSyncing: false,
|
|
2454
|
+
specError: null,
|
|
2455
|
+
updateAgentSpec: () => {
|
|
2456
|
+
throw new Error("Draft agent extras are only available in draft mode.");
|
|
2457
|
+
},
|
|
2458
|
+
flushAgentSpec: () => Promise.reject(new Error("Draft agent extras are only available in draft mode.")),
|
|
2459
|
+
adoptAgentSpec: () => {
|
|
2460
|
+
throw new Error("Draft agent extras are only available in draft mode.");
|
|
2461
|
+
}
|
|
2462
|
+
};
|
|
2463
|
+
function isThreadAccessor(value) {
|
|
2464
|
+
return typeof value === "function";
|
|
2465
|
+
}
|
|
2466
|
+
function isGetState(value) {
|
|
2467
|
+
return typeof value === "function";
|
|
2468
|
+
}
|
|
2469
|
+
function isSubscribe(value) {
|
|
2470
|
+
return typeof value === "function";
|
|
2471
|
+
}
|
|
2472
|
+
function walkAssistantClientAncestors(client, visit) {
|
|
2473
|
+
let current = client;
|
|
2474
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2475
|
+
while (!seen.has(current)) {
|
|
2476
|
+
seen.add(current);
|
|
2477
|
+
if (visit(current) === "stop") {
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
const parent = Reflect.getPrototypeOf(current);
|
|
2481
|
+
if (parent == null || parent === Object.prototype) {
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
current = parent;
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
function tryGetTrueForgeExtras(client) {
|
|
2488
|
+
let found;
|
|
2489
|
+
walkAssistantClientAncestors(client, (current) => {
|
|
2490
|
+
try {
|
|
2491
|
+
const thread = Reflect.get(current, "thread");
|
|
2492
|
+
if (isThreadAccessor(thread)) {
|
|
2493
|
+
const threadClient = Reflect.apply(thread, current, []);
|
|
2494
|
+
if (threadClient == null || typeof threadClient !== "object") {
|
|
2495
|
+
return "continue";
|
|
2496
|
+
}
|
|
2497
|
+
const getState = Reflect.get(threadClient, "getState");
|
|
2498
|
+
if (!isGetState(getState)) {
|
|
2499
|
+
return "continue";
|
|
2500
|
+
}
|
|
2501
|
+
const state = Reflect.apply(getState, threadClient, []);
|
|
2502
|
+
const extras = extrasBrand.tryGet(
|
|
2503
|
+
state != null && typeof state === "object" ? Reflect.get(state, "extras") : void 0
|
|
2504
|
+
);
|
|
2505
|
+
if (extras != null) {
|
|
2506
|
+
found = extras;
|
|
2507
|
+
return "stop";
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
} catch {
|
|
2511
|
+
}
|
|
2512
|
+
return "continue";
|
|
2513
|
+
});
|
|
2514
|
+
return found;
|
|
2515
|
+
}
|
|
2516
|
+
function getTrueForgeExtras(client) {
|
|
2517
|
+
const extras = tryGetTrueForgeExtras(client);
|
|
2518
|
+
if (extras == null) {
|
|
2519
|
+
throw new Error("The current thread is not backed by the useTrueForgeAgentRuntime runtime.");
|
|
2520
|
+
}
|
|
2521
|
+
return extras;
|
|
2522
|
+
}
|
|
2523
|
+
function subscribeClientChain(client) {
|
|
2524
|
+
return (onStoreChange) => {
|
|
2525
|
+
const unsubs = [];
|
|
2526
|
+
walkAssistantClientAncestors(client, (current) => {
|
|
2527
|
+
try {
|
|
2528
|
+
const subscribe = Reflect.get(current, "subscribe");
|
|
2529
|
+
if (isSubscribe(subscribe)) {
|
|
2530
|
+
const unsubscribe = Reflect.apply(subscribe, current, [onStoreChange]);
|
|
2531
|
+
if (typeof unsubscribe === "function") {
|
|
2532
|
+
unsubs.push(() => {
|
|
2533
|
+
Reflect.apply(unsubscribe, void 0, []);
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
return "continue";
|
|
2538
|
+
} catch {
|
|
2539
|
+
return "stop";
|
|
2540
|
+
}
|
|
2541
|
+
});
|
|
2542
|
+
return () => {
|
|
2543
|
+
for (const unsub of unsubs) {
|
|
2544
|
+
unsub();
|
|
2545
|
+
}
|
|
2546
|
+
};
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
function useTrueForgeRuntimeExtras() {
|
|
2550
|
+
const aui = useAui();
|
|
2551
|
+
const subscribe = useCallback(subscribeClientChain(aui), [aui]);
|
|
2552
|
+
const getSnapshot = useCallback(() => tryGetTrueForgeExtras(aui), [aui]);
|
|
2553
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
2554
|
+
}
|
|
2555
|
+
function useTrueForgeExtrasApi(select, fallback) {
|
|
2556
|
+
const extras = useTrueForgeRuntimeExtras();
|
|
2557
|
+
const hasFallback = arguments.length >= 2;
|
|
2558
|
+
if (extras == null) {
|
|
2559
|
+
if (hasFallback) {
|
|
2560
|
+
return fallback;
|
|
2561
|
+
}
|
|
2562
|
+
throw new Error("The current thread is not backed by the useTrueForgeAgentRuntime runtime.");
|
|
2563
|
+
}
|
|
2564
|
+
return select != null ? select(extras) : extras;
|
|
2565
|
+
}
|
|
2566
|
+
var trueForgeExtras = {
|
|
2567
|
+
provide: extrasBrand.provide,
|
|
2568
|
+
is: extrasBrand.is,
|
|
2569
|
+
tryGet: extrasBrand.tryGet,
|
|
2570
|
+
get: getTrueForgeExtras,
|
|
2571
|
+
use: useTrueForgeExtrasApi
|
|
2572
|
+
};
|
|
2573
|
+
|
|
2574
|
+
// src/hooks.ts
|
|
2575
|
+
var useTrueForgeApprovals = () => {
|
|
2576
|
+
const extras = useTrueForgeRuntimeExtras();
|
|
2577
|
+
return useMemo(
|
|
2578
|
+
() => ({
|
|
2579
|
+
pending: extras?.pendingApprovals ?? [],
|
|
2580
|
+
respond: extras?.respondToToolApproval ?? (() => {
|
|
2581
|
+
throw new Error("TrueForge runtime is not ready yet");
|
|
2582
|
+
})
|
|
2583
|
+
}),
|
|
2584
|
+
[extras]
|
|
2585
|
+
);
|
|
2586
|
+
};
|
|
2587
|
+
var useTrueForgeToolResponses = () => {
|
|
2588
|
+
const extras = useTrueForgeRuntimeExtras();
|
|
2589
|
+
return useMemo(
|
|
2590
|
+
() => ({
|
|
2591
|
+
pending: extras?.pendingToolResponses ?? [],
|
|
2592
|
+
respond: extras?.respondToToolResponse ?? (() => {
|
|
2593
|
+
throw new Error("TrueForge runtime is not ready yet");
|
|
2594
|
+
})
|
|
2595
|
+
}),
|
|
2596
|
+
[extras]
|
|
2597
|
+
);
|
|
2598
|
+
};
|
|
2599
|
+
var useTrueForgeMcpAuth = () => {
|
|
2600
|
+
const extras = useTrueForgeRuntimeExtras();
|
|
2601
|
+
return useMemo(
|
|
2602
|
+
() => ({
|
|
2603
|
+
pending: extras?.pendingMcpAuth ?? null,
|
|
2604
|
+
resume: extras?.resumeMcpAuth ?? (() => Promise.reject(new Error("TrueForge runtime is not ready yet")))
|
|
2605
|
+
}),
|
|
2606
|
+
[extras]
|
|
2607
|
+
);
|
|
2608
|
+
};
|
|
2609
|
+
var useTrueForgeRespondToToolApproval = () => {
|
|
2610
|
+
const aui = useAui2();
|
|
2611
|
+
return (response) => {
|
|
2612
|
+
getTrueForgeExtras(aui).respondToToolApproval(response);
|
|
2613
|
+
};
|
|
2614
|
+
};
|
|
2615
|
+
var useTrueForgeRespondToToolResponse = () => {
|
|
2616
|
+
const aui = useAui2();
|
|
2617
|
+
return (response) => {
|
|
2618
|
+
getTrueForgeExtras(aui).respondToToolResponse(response);
|
|
2619
|
+
};
|
|
2620
|
+
};
|
|
2621
|
+
var useTrueForgeResumeMcpAuth = () => {
|
|
2622
|
+
const aui = useAui2();
|
|
2623
|
+
return () => getTrueForgeExtras(aui).resumeMcpAuth();
|
|
2624
|
+
};
|
|
2625
|
+
var useTrueForgeSandboxId = () => useTrueForgeRuntimeExtras()?.sandboxId;
|
|
2626
|
+
var useTrueForgeTurnId = () => useAuiState((state) => {
|
|
2627
|
+
const turnId = state.message.metadata.custom["turnId"];
|
|
2628
|
+
return typeof turnId === "string" ? turnId : void 0;
|
|
2629
|
+
});
|
|
2630
|
+
var useTrueForgeDownloadSandboxFile = () => {
|
|
2631
|
+
const aui = useAui2();
|
|
2632
|
+
const turnId = useTrueForgeTurnId();
|
|
2633
|
+
return (path) => {
|
|
2634
|
+
if (turnId == null) {
|
|
2635
|
+
throw new Error("Downloading a sandbox file requires a message scope to resolve its turn.");
|
|
2636
|
+
}
|
|
2637
|
+
return getTrueForgeExtras(aui).downloadSandboxFile({ turnId, path });
|
|
2638
|
+
};
|
|
2639
|
+
};
|
|
2640
|
+
var useTrueForgeCancel = () => {
|
|
2641
|
+
const aui = useAui2();
|
|
2642
|
+
return () => getTrueForgeExtras(aui).cancel();
|
|
2643
|
+
};
|
|
2644
|
+
var useTrueForgeReload = () => {
|
|
2645
|
+
const aui = useAui2();
|
|
2646
|
+
return () => {
|
|
2647
|
+
getTrueForgeExtras(aui).reload();
|
|
2648
|
+
};
|
|
2649
|
+
};
|
|
2650
|
+
var useTrueForgeHistoryPagination = () => {
|
|
2651
|
+
const extras = useTrueForgeRuntimeExtras();
|
|
2652
|
+
return useMemo(
|
|
2653
|
+
() => ({
|
|
2654
|
+
hasOlderHistory: extras?.hasOlderHistory ?? false,
|
|
2655
|
+
isLoadingOlderHistory: extras?.isLoadingOlderHistory ?? false,
|
|
2656
|
+
loadOlderHistory: extras?.loadOlderHistory ?? (() => Promise.reject(new Error("TrueForge runtime is not ready yet")))
|
|
2657
|
+
}),
|
|
2658
|
+
[extras]
|
|
2659
|
+
);
|
|
2660
|
+
};
|
|
2661
|
+
var useTrueForgeResumeUnavailable = () => useTrueForgeRuntimeExtras()?.resumeUnavailable ?? false;
|
|
2662
|
+
var useTrueForgeResetFromTurn = () => {
|
|
2663
|
+
const aui = useAui2();
|
|
2664
|
+
return (turnId) => getTrueForgeExtras(aui).resetFromTurn(turnId);
|
|
2665
|
+
};
|
|
2666
|
+
var useTrueForgeAgentSpec = () => {
|
|
2667
|
+
const extras = useTrueForgeRuntimeExtras()?.draft ?? null;
|
|
2668
|
+
return useMemo(() => ({ ...EMPTY_DRAFT_EXTRAS, ...extras }), [extras]);
|
|
2669
|
+
};
|
|
2670
|
+
var useTrueForgeUpdateAgentSpec = () => {
|
|
2671
|
+
const aui = useAui2();
|
|
2672
|
+
return (update) => getTrueForgeExtras(aui).draft?.updateAgentSpec(update);
|
|
2673
|
+
};
|
|
2674
|
+
var useTrueForgeFlushAgentSpec = () => {
|
|
2675
|
+
const aui = useAui2();
|
|
2676
|
+
return () => getTrueForgeExtras(aui).draft?.flushAgentSpec() ?? Promise.resolve();
|
|
2677
|
+
};
|
|
2678
|
+
var useTrueForgeAdoptAgentSpec = () => {
|
|
2679
|
+
const aui = useAui2();
|
|
2680
|
+
return (request) => getTrueForgeExtras(aui).draft?.adoptAgentSpec(request);
|
|
2681
|
+
};
|
|
2682
|
+
|
|
2683
|
+
// src/requiredActionInputs.ts
|
|
2684
|
+
function messageHasPendingRequiredActions(message) {
|
|
2685
|
+
return messageHasPendingApprovals(message) || messageHasPendingResponses(message);
|
|
2686
|
+
}
|
|
2687
|
+
function collectRequiredActionInputs(message, defaultThreadId = ROOT_THREAD_ID) {
|
|
2688
|
+
if (messageHasPendingRequiredActions(message)) {
|
|
2689
|
+
return [];
|
|
2690
|
+
}
|
|
2691
|
+
return [...collectApprovalInputs(message, defaultThreadId), ...collectResponseInputs(message, defaultThreadId)];
|
|
2692
|
+
}
|
|
2693
|
+
function findPausedAssistantMessage(messages) {
|
|
2694
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2695
|
+
const candidate = messages[i];
|
|
2696
|
+
if (candidate?.role === "assistant" && candidate.status.type === "requires-action") {
|
|
2697
|
+
return candidate;
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
return void 0;
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// src/sessions.ts
|
|
2704
|
+
var inflightBySessionId = /* @__PURE__ */ new Map();
|
|
2705
|
+
function getSession(server, sessionId) {
|
|
2706
|
+
let inflight = inflightBySessionId.get(sessionId);
|
|
2707
|
+
if (inflight == null) {
|
|
2708
|
+
inflight = server.getSession({ sessionId }).finally(() => {
|
|
2709
|
+
if (inflightBySessionId.get(sessionId) === inflight) {
|
|
2710
|
+
inflightBySessionId.delete(sessionId);
|
|
2711
|
+
}
|
|
2712
|
+
});
|
|
2713
|
+
inflightBySessionId.set(sessionId, inflight);
|
|
2714
|
+
}
|
|
2715
|
+
return inflight;
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
// src/trueforgeOwnedSessionsThreadListAdapter.ts
|
|
2719
|
+
var THREAD_LIST_PAGE_SIZE2 = 20;
|
|
2720
|
+
function createTrueForgeOwnedSessionsThreadListAdapter(options) {
|
|
2721
|
+
const { server, listSessionsAgentId, listSessionsCreatedByMe = false } = options;
|
|
2722
|
+
return {
|
|
2723
|
+
async list({ after } = {}) {
|
|
2724
|
+
const page = await server.listSessions({
|
|
2725
|
+
...listSessionsAgentId != null ? { agentId: listSessionsAgentId } : {},
|
|
2726
|
+
createdByMe: listSessionsCreatedByMe,
|
|
2727
|
+
limit: THREAD_LIST_PAGE_SIZE2,
|
|
2728
|
+
...after == null ? {} : { pageToken: after },
|
|
2729
|
+
startTimestamp: sessionListStartTimestamp()
|
|
2730
|
+
});
|
|
2731
|
+
const threads = page.data.map((session) => sessionToThreadMetadata(session, sessionDisplayTitle(session)));
|
|
2732
|
+
return {
|
|
2733
|
+
threads,
|
|
2734
|
+
nextCursor: page.nextPageToken ?? void 0
|
|
2735
|
+
};
|
|
2736
|
+
},
|
|
2737
|
+
initialize() {
|
|
2738
|
+
return Promise.reject(
|
|
2739
|
+
new Error("Owned sessions history adapter is read-only; create sessions via a named or draft runtime.")
|
|
2740
|
+
);
|
|
2741
|
+
},
|
|
2742
|
+
async fetch(remoteId) {
|
|
2743
|
+
const session = await server.getSession({ sessionId: remoteId });
|
|
2744
|
+
return sessionToThreadMetadata(session, sessionDisplayTitle(session));
|
|
2745
|
+
},
|
|
2746
|
+
async rename(remoteId, newTitle) {
|
|
2747
|
+
if (typeof server.renameSession !== "function") {
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
await server.renameSession({ sessionId: remoteId, title: newTitle });
|
|
2751
|
+
},
|
|
2752
|
+
archive() {
|
|
2753
|
+
return Promise.resolve();
|
|
2754
|
+
},
|
|
2755
|
+
unarchive() {
|
|
2756
|
+
return Promise.resolve();
|
|
2757
|
+
},
|
|
2758
|
+
async delete(remoteId) {
|
|
2759
|
+
if (typeof server.deleteSession !== "function") {
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2762
|
+
await server.deleteSession({ sessionId: remoteId });
|
|
2763
|
+
},
|
|
2764
|
+
generateTitle() {
|
|
2765
|
+
return Promise.resolve(new ReadableStream());
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
// src/trueforgeThreadListAdapter.ts
|
|
2771
|
+
var THREAD_LIST_PAGE_SIZE3 = 20;
|
|
2772
|
+
function createTrueForgeThreadListAdapter(options) {
|
|
2773
|
+
const { server, agentName, listSessionsAgentId, listSessionsCreatedByMe = false } = options;
|
|
2774
|
+
return {
|
|
2775
|
+
async list({ after } = {}) {
|
|
2776
|
+
const page = await server.listSessions({
|
|
2777
|
+
...listSessionsAgentId != null ? { agentId: listSessionsAgentId } : {},
|
|
2778
|
+
createdByMe: listSessionsCreatedByMe,
|
|
2779
|
+
limit: THREAD_LIST_PAGE_SIZE3,
|
|
2780
|
+
...after == null ? {} : { pageToken: after },
|
|
2781
|
+
startTimestamp: sessionListStartTimestamp()
|
|
2782
|
+
});
|
|
2783
|
+
const threads = page.data.map((session) => sessionToThreadMetadata(session, session.title ?? void 0));
|
|
2784
|
+
return {
|
|
2785
|
+
threads,
|
|
2786
|
+
nextCursor: page.nextPageToken ?? void 0
|
|
2787
|
+
};
|
|
2788
|
+
},
|
|
2789
|
+
async initialize() {
|
|
2790
|
+
const session = await server.createSession({ agentName });
|
|
2791
|
+
return { remoteId: session.id, externalId: void 0 };
|
|
2792
|
+
},
|
|
2793
|
+
async fetch(remoteId) {
|
|
2794
|
+
const session = await getSession(server, remoteId);
|
|
2795
|
+
return sessionToThreadMetadata(session, session.title ?? void 0);
|
|
2796
|
+
},
|
|
2797
|
+
async rename(remoteId, newTitle) {
|
|
2798
|
+
if (typeof server.renameSession !== "function") {
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
await server.renameSession({ sessionId: remoteId, title: newTitle });
|
|
2802
|
+
},
|
|
2803
|
+
archive() {
|
|
2804
|
+
return Promise.resolve();
|
|
2805
|
+
},
|
|
2806
|
+
unarchive() {
|
|
2807
|
+
return Promise.resolve();
|
|
2808
|
+
},
|
|
2809
|
+
async delete(remoteId) {
|
|
2810
|
+
if (typeof server.deleteSession !== "function") {
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
await server.deleteSession({ sessionId: remoteId });
|
|
2814
|
+
},
|
|
2815
|
+
generateTitle() {
|
|
2816
|
+
return Promise.resolve(new ReadableStream());
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
// src/useTrueForgeAgentRuntime.ts
|
|
2822
|
+
import {
|
|
2823
|
+
pickExternalStoreSharedOptions
|
|
2824
|
+
} from "@assistant-ui/core";
|
|
2825
|
+
import { useExternalStoreRuntime, useRemoteThreadListRuntime, useRuntimeAdapters } from "@assistant-ui/core/react";
|
|
2826
|
+
import { useAui as useAui3, useAuiState as useAuiState2 } from "@assistant-ui/store";
|
|
2827
|
+
import { useCallback as useCallback4, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
|
|
2828
|
+
|
|
2829
|
+
// src/messageCustomMetadata.ts
|
|
2830
|
+
function isUnknownRecord3(value) {
|
|
2831
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
2832
|
+
}
|
|
2833
|
+
function isMcpServerAuthInfoList(value) {
|
|
2834
|
+
return Array.isArray(value) && value.every(
|
|
2835
|
+
(server) => isUnknownRecord3(server) && typeof server["id"] === "string" && typeof server["name"] === "string" && typeof server["authUrl"] === "string"
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
// src/collectPending.ts
|
|
2840
|
+
function walkToolCallParts(content, visit, threadId) {
|
|
2841
|
+
for (const part of content) {
|
|
2842
|
+
if (part.type !== "tool-call") {
|
|
2843
|
+
continue;
|
|
2844
|
+
}
|
|
2845
|
+
visit(part, threadId);
|
|
2846
|
+
if (part.messages == null) {
|
|
2847
|
+
continue;
|
|
2848
|
+
}
|
|
2849
|
+
for (const message of part.messages) {
|
|
2850
|
+
if (message.role !== "assistant") {
|
|
2851
|
+
continue;
|
|
2852
|
+
}
|
|
2853
|
+
const nestedThreadId = getToolApprovalThreadId(message) ?? getToolResponseThreadId(message) ?? threadId;
|
|
2854
|
+
walkToolCallParts(message.content, visit, nestedThreadId);
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
function collectPendingApprovals(messages) {
|
|
2859
|
+
const pending = [];
|
|
2860
|
+
for (const message of messages) {
|
|
2861
|
+
if (message.role !== "assistant") {
|
|
2862
|
+
continue;
|
|
2863
|
+
}
|
|
2864
|
+
const rootThreadId = getToolApprovalThreadId(message) ?? ROOT_THREAD_ID;
|
|
2865
|
+
walkToolCallParts(
|
|
2866
|
+
message.content,
|
|
2867
|
+
(part, threadId) => {
|
|
2868
|
+
const approval = part.approval;
|
|
2869
|
+
if (approval == null || !hasPendingToolApproval(approval)) {
|
|
2870
|
+
return;
|
|
2871
|
+
}
|
|
2872
|
+
pending.push({
|
|
2873
|
+
approvalId: approval.id,
|
|
2874
|
+
threadId,
|
|
2875
|
+
toolName: part.toolName,
|
|
2876
|
+
args: { ...part.args },
|
|
2877
|
+
argsText: part.argsText
|
|
2878
|
+
});
|
|
2879
|
+
},
|
|
2880
|
+
rootThreadId
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
return pending;
|
|
2884
|
+
}
|
|
2885
|
+
function collectPendingToolResponses(messages) {
|
|
2886
|
+
const pending = [];
|
|
2887
|
+
for (const message of messages) {
|
|
2888
|
+
if (message.role !== "assistant") {
|
|
2889
|
+
continue;
|
|
2890
|
+
}
|
|
2891
|
+
const rootThreadId = getToolResponseThreadId(message) ?? ROOT_THREAD_ID;
|
|
2892
|
+
walkToolCallParts(
|
|
2893
|
+
message.content,
|
|
2894
|
+
(part, threadId) => {
|
|
2895
|
+
if (!hasPendingToolResponse(part)) {
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
const payload = isUnknownRecord3(part.interrupt?.payload) ? {
|
|
2899
|
+
...typeof part.interrupt.payload["question"] === "string" ? { question: part.interrupt.payload["question"] } : {},
|
|
2900
|
+
...Array.isArray(part.interrupt.payload["options"]) && part.interrupt.payload["options"].every((option) => typeof option === "string") ? { options: part.interrupt.payload["options"] } : {}
|
|
2901
|
+
} : void 0;
|
|
2902
|
+
pending.push({
|
|
2903
|
+
toolCallId: part.toolCallId,
|
|
2904
|
+
threadId,
|
|
2905
|
+
toolName: part.toolName,
|
|
2906
|
+
args: { ...part.args },
|
|
2907
|
+
argsText: part.argsText,
|
|
2908
|
+
...payload?.question != null ? { question: payload.question } : {},
|
|
2909
|
+
...payload?.options != null ? { options: payload.options } : {}
|
|
2910
|
+
});
|
|
2911
|
+
},
|
|
2912
|
+
rootThreadId
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
return pending;
|
|
2916
|
+
}
|
|
2917
|
+
function derivePendingMcpAuth(messages) {
|
|
2918
|
+
for (const message of messages.toReversed()) {
|
|
2919
|
+
if (message.role !== "assistant") {
|
|
2920
|
+
continue;
|
|
2921
|
+
}
|
|
2922
|
+
if (message.status.type !== "requires-action") {
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2925
|
+
const custom = message.metadata.custom;
|
|
2926
|
+
if (custom["pendingMcpAuth"] !== true) {
|
|
2927
|
+
continue;
|
|
2928
|
+
}
|
|
2929
|
+
const servers = custom["mcpServers"];
|
|
2930
|
+
if (!isMcpServerAuthInfoList(servers)) {
|
|
2931
|
+
return { mcpServers: [] };
|
|
2932
|
+
}
|
|
2933
|
+
return { mcpServers: servers };
|
|
2934
|
+
}
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
function deriveSandboxId(messages) {
|
|
2938
|
+
for (const message of messages.toReversed()) {
|
|
2939
|
+
if (message.role !== "assistant") {
|
|
2940
|
+
continue;
|
|
2941
|
+
}
|
|
2942
|
+
const sandboxId = message.metadata.custom["sandboxId"];
|
|
2943
|
+
if (typeof sandboxId === "string") {
|
|
2944
|
+
return sandboxId;
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
return void 0;
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
// src/draft/useDraftAgentSpec.ts
|
|
2951
|
+
import { useCallback as useCallback2, useEffect, useMemo as useMemo2, useRef, useState } from "react";
|
|
2952
|
+
var SPEC_SYNC_DEBOUNCE_MS = 400;
|
|
2953
|
+
function useDraftAgentSpec({
|
|
2954
|
+
draftSessionId,
|
|
2955
|
+
draftBridge,
|
|
2956
|
+
defaultAgentSpec,
|
|
2957
|
+
onAgentSpecChange,
|
|
2958
|
+
onError
|
|
2959
|
+
}) {
|
|
2960
|
+
const enabled = draftBridge != null;
|
|
2961
|
+
const [agentSpec, setAgentSpec] = useState(defaultAgentSpec);
|
|
2962
|
+
const [isSpecLoading, setIsSpecLoading] = useState(false);
|
|
2963
|
+
const [isSpecSyncing, setIsSpecSyncing] = useState(false);
|
|
2964
|
+
const [specError, setSpecError] = useState(null);
|
|
2965
|
+
const agentSpecRef = useRef(agentSpec);
|
|
2966
|
+
agentSpecRef.current = agentSpec;
|
|
2967
|
+
const syncTimeoutRef = useRef(void 0);
|
|
2968
|
+
const syncGenerationRef = useRef(0);
|
|
2969
|
+
const loadedDraftIdRef = useRef(void 0);
|
|
2970
|
+
const localDirtyRef = useRef(false);
|
|
2971
|
+
const lastUpdatedAtRef = useRef(void 0);
|
|
2972
|
+
const pendingFlushRef = useRef(void 0);
|
|
2973
|
+
const inFlightFlushRef = useRef(void 0);
|
|
2974
|
+
const activeDraftIdRef = useRef(draftSessionId);
|
|
2975
|
+
useEffect(() => {
|
|
2976
|
+
const previousDraftId = activeDraftIdRef.current;
|
|
2977
|
+
if (previousDraftId === draftSessionId) {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
activeDraftIdRef.current = draftSessionId;
|
|
2981
|
+
syncGenerationRef.current++;
|
|
2982
|
+
if (syncTimeoutRef.current != null) {
|
|
2983
|
+
clearTimeout(syncTimeoutRef.current);
|
|
2984
|
+
syncTimeoutRef.current = void 0;
|
|
2985
|
+
}
|
|
2986
|
+
pendingFlushRef.current = void 0;
|
|
2987
|
+
inFlightFlushRef.current = void 0;
|
|
2988
|
+
lastUpdatedAtRef.current = void 0;
|
|
2989
|
+
setIsSpecSyncing(false);
|
|
2990
|
+
if (previousDraftId != null) {
|
|
2991
|
+
localDirtyRef.current = false;
|
|
2992
|
+
}
|
|
2993
|
+
}, [draftSessionId]);
|
|
2994
|
+
useEffect(() => {
|
|
2995
|
+
if (draftBridge == null) {
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
if (draftSessionId == null) {
|
|
2999
|
+
loadedDraftIdRef.current = void 0;
|
|
3000
|
+
setAgentSpec(defaultAgentSpec);
|
|
3001
|
+
localDirtyRef.current = false;
|
|
3002
|
+
setSpecError(null);
|
|
3003
|
+
setIsSpecLoading(false);
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
if (loadedDraftIdRef.current === draftSessionId) {
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
const abortController = new AbortController();
|
|
3010
|
+
setIsSpecLoading(true);
|
|
3011
|
+
void (async () => {
|
|
3012
|
+
try {
|
|
3013
|
+
const loaded = await draftBridge.getDraftAgentSpec(draftSessionId);
|
|
3014
|
+
if (abortController.signal.aborted) {
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
loadedDraftIdRef.current = draftSessionId;
|
|
3018
|
+
if (localDirtyRef.current) {
|
|
3019
|
+
scheduleSpecSyncRef.current(draftSessionId, agentSpecRef.current);
|
|
3020
|
+
localDirtyRef.current = false;
|
|
3021
|
+
setSpecError(null);
|
|
3022
|
+
setIsSpecLoading(false);
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
setAgentSpec(loaded);
|
|
3026
|
+
setSpecError(null);
|
|
3027
|
+
setIsSpecLoading(false);
|
|
3028
|
+
} catch (error) {
|
|
3029
|
+
if (!abortController.signal.aborted) {
|
|
3030
|
+
onError?.(error);
|
|
3031
|
+
setSpecError(error);
|
|
3032
|
+
setIsSpecLoading(false);
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
})();
|
|
3036
|
+
return () => {
|
|
3037
|
+
abortController.abort();
|
|
3038
|
+
setIsSpecLoading(false);
|
|
3039
|
+
};
|
|
3040
|
+
}, [defaultAgentSpec, draftBridge, draftSessionId, enabled, onError]);
|
|
3041
|
+
const flushSpecSync = useCallback2(
|
|
3042
|
+
async (draftId, spec, generation) => {
|
|
3043
|
+
if (draftBridge == null) {
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
setIsSpecSyncing(true);
|
|
3047
|
+
try {
|
|
3048
|
+
const updatedAt = await draftBridge.syncAgentSpec(draftId, spec);
|
|
3049
|
+
if (generation !== syncGenerationRef.current) {
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
lastUpdatedAtRef.current = updatedAt || (/* @__PURE__ */ new Date()).toISOString();
|
|
3053
|
+
setSpecError(null);
|
|
3054
|
+
onAgentSpecChange?.(spec);
|
|
3055
|
+
} catch (error) {
|
|
3056
|
+
if (generation === syncGenerationRef.current) {
|
|
3057
|
+
setSpecError(error);
|
|
3058
|
+
onError?.(error);
|
|
3059
|
+
}
|
|
3060
|
+
} finally {
|
|
3061
|
+
if (generation === syncGenerationRef.current) {
|
|
3062
|
+
setIsSpecSyncing(false);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
},
|
|
3066
|
+
[draftBridge, onAgentSpecChange, onError]
|
|
3067
|
+
);
|
|
3068
|
+
const scheduleSpecSync = useCallback2(
|
|
3069
|
+
(draftId, spec) => {
|
|
3070
|
+
if (syncTimeoutRef.current != null) {
|
|
3071
|
+
clearTimeout(syncTimeoutRef.current);
|
|
3072
|
+
}
|
|
3073
|
+
const generation = ++syncGenerationRef.current;
|
|
3074
|
+
const flush = () => {
|
|
3075
|
+
pendingFlushRef.current = void 0;
|
|
3076
|
+
syncTimeoutRef.current = void 0;
|
|
3077
|
+
const promise = flushSpecSync(draftId, spec, generation).finally(() => {
|
|
3078
|
+
if (inFlightFlushRef.current === promise) {
|
|
3079
|
+
inFlightFlushRef.current = void 0;
|
|
3080
|
+
}
|
|
3081
|
+
});
|
|
3082
|
+
inFlightFlushRef.current = promise;
|
|
3083
|
+
return promise;
|
|
3084
|
+
};
|
|
3085
|
+
pendingFlushRef.current = flush;
|
|
3086
|
+
syncTimeoutRef.current = setTimeout(() => {
|
|
3087
|
+
void flush();
|
|
3088
|
+
}, SPEC_SYNC_DEBOUNCE_MS);
|
|
3089
|
+
},
|
|
3090
|
+
[flushSpecSync]
|
|
3091
|
+
);
|
|
3092
|
+
const scheduleSpecSyncRef = useRef(scheduleSpecSync);
|
|
3093
|
+
scheduleSpecSyncRef.current = scheduleSpecSync;
|
|
3094
|
+
const flushPendingSpecSyncNow = useCallback2(async () => {
|
|
3095
|
+
if (syncTimeoutRef.current != null) {
|
|
3096
|
+
clearTimeout(syncTimeoutRef.current);
|
|
3097
|
+
syncTimeoutRef.current = void 0;
|
|
3098
|
+
}
|
|
3099
|
+
const pending = pendingFlushRef.current;
|
|
3100
|
+
if (pending != null) {
|
|
3101
|
+
pendingFlushRef.current = void 0;
|
|
3102
|
+
await pending();
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
if (inFlightFlushRef.current != null) {
|
|
3106
|
+
await inFlightFlushRef.current;
|
|
3107
|
+
}
|
|
3108
|
+
}, []);
|
|
3109
|
+
const adoptAgentSpec = useCallback2(
|
|
3110
|
+
({ agentSpec: persistedSpec, updatedAt }) => {
|
|
3111
|
+
if (syncTimeoutRef.current != null) {
|
|
3112
|
+
clearTimeout(syncTimeoutRef.current);
|
|
3113
|
+
syncTimeoutRef.current = void 0;
|
|
3114
|
+
}
|
|
3115
|
+
syncGenerationRef.current++;
|
|
3116
|
+
pendingFlushRef.current = void 0;
|
|
3117
|
+
inFlightFlushRef.current = void 0;
|
|
3118
|
+
localDirtyRef.current = false;
|
|
3119
|
+
if (updatedAt !== void 0) {
|
|
3120
|
+
lastUpdatedAtRef.current = updatedAt;
|
|
3121
|
+
}
|
|
3122
|
+
agentSpecRef.current = persistedSpec;
|
|
3123
|
+
setAgentSpec(persistedSpec);
|
|
3124
|
+
setSpecError(null);
|
|
3125
|
+
setIsSpecSyncing(false);
|
|
3126
|
+
},
|
|
3127
|
+
[]
|
|
3128
|
+
);
|
|
3129
|
+
const takeTurnHeaderTimestamp = useCallback2(async () => {
|
|
3130
|
+
await flushPendingSpecSyncNow();
|
|
3131
|
+
const updatedAt = lastUpdatedAtRef.current;
|
|
3132
|
+
lastUpdatedAtRef.current = void 0;
|
|
3133
|
+
return updatedAt;
|
|
3134
|
+
}, [flushPendingSpecSyncNow]);
|
|
3135
|
+
useEffect(
|
|
3136
|
+
() => () => {
|
|
3137
|
+
if (syncTimeoutRef.current != null) {
|
|
3138
|
+
clearTimeout(syncTimeoutRef.current);
|
|
3139
|
+
}
|
|
3140
|
+
},
|
|
3141
|
+
[]
|
|
3142
|
+
);
|
|
3143
|
+
const updateAgentSpec = useCallback2(
|
|
3144
|
+
(update) => {
|
|
3145
|
+
if (draftBridge == null) {
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
const next = mergeAgentSpec(agentSpecRef.current, update);
|
|
3149
|
+
setAgentSpec(next);
|
|
3150
|
+
localDirtyRef.current = true;
|
|
3151
|
+
if (draftSessionId != null) {
|
|
3152
|
+
scheduleSpecSync(draftSessionId, next);
|
|
3153
|
+
}
|
|
3154
|
+
},
|
|
3155
|
+
[draftBridge, draftSessionId, enabled, scheduleSpecSync]
|
|
3156
|
+
);
|
|
3157
|
+
return useMemo2(
|
|
3158
|
+
() => ({
|
|
3159
|
+
agentSpec: enabled ? agentSpec : null,
|
|
3160
|
+
draftSessionId: enabled ? draftSessionId : void 0,
|
|
3161
|
+
isSpecLoading: enabled ? isSpecLoading : false,
|
|
3162
|
+
isSpecSyncing: enabled ? isSpecSyncing : false,
|
|
3163
|
+
specError: enabled ? specError : null,
|
|
3164
|
+
updateAgentSpec,
|
|
3165
|
+
flushAgentSpec: flushPendingSpecSyncNow,
|
|
3166
|
+
adoptAgentSpec,
|
|
3167
|
+
takeTurnHeaderTimestamp
|
|
3168
|
+
}),
|
|
3169
|
+
[
|
|
3170
|
+
agentSpec,
|
|
3171
|
+
draftSessionId,
|
|
3172
|
+
enabled,
|
|
3173
|
+
isSpecLoading,
|
|
3174
|
+
isSpecSyncing,
|
|
3175
|
+
specError,
|
|
3176
|
+
flushPendingSpecSyncNow,
|
|
3177
|
+
adoptAgentSpec,
|
|
3178
|
+
takeTurnHeaderTimestamp,
|
|
3179
|
+
updateAgentSpec
|
|
3180
|
+
]
|
|
3181
|
+
);
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
// src/sandboxDownload.ts
|
|
3185
|
+
function buildSandboxDownloadRequest(args) {
|
|
3186
|
+
if (args.sessionId == null) {
|
|
3187
|
+
throw new Error("This session has not been saved yet, so its files cannot be downloaded.");
|
|
3188
|
+
}
|
|
3189
|
+
return {
|
|
3190
|
+
sessionId: args.sessionId,
|
|
3191
|
+
turnId: args.turnId,
|
|
3192
|
+
path: args.path,
|
|
3193
|
+
...args.sandboxId != null ? { sandboxId: args.sandboxId } : {}
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
// src/types.ts
|
|
3198
|
+
function resolveTrueForgeAgentConfig(options) {
|
|
3199
|
+
if (options.agent != null) {
|
|
3200
|
+
if (options.agent.mode === "named" && options.agentName != null) {
|
|
3201
|
+
return { mode: "named", agentName: options.agentName };
|
|
3202
|
+
}
|
|
3203
|
+
return options.agent;
|
|
3204
|
+
}
|
|
3205
|
+
if (options.agentName != null) {
|
|
3206
|
+
return { mode: "named", agentName: options.agentName };
|
|
3207
|
+
}
|
|
3208
|
+
throw new Error("useTrueForgeAgentRuntime requires `agent` or legacy `agentName`.");
|
|
3209
|
+
}
|
|
3210
|
+
function resolveTrueForgeAgentRuntimeOptions(options) {
|
|
3211
|
+
const agent = resolveTrueForgeAgentConfig(options);
|
|
3212
|
+
return {
|
|
3213
|
+
...options,
|
|
3214
|
+
agent
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
// src/useTrueForgeAgentMessages.ts
|
|
3219
|
+
import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
|
|
3220
|
+
|
|
3221
|
+
// src/loadSessionSnapshot.ts
|
|
3222
|
+
var inflightBySessionId2 = /* @__PURE__ */ new Map();
|
|
3223
|
+
function loadSessionSnapshot(server, sessionId, onProgress) {
|
|
3224
|
+
let inflight = inflightBySessionId2.get(sessionId);
|
|
3225
|
+
if (inflight == null) {
|
|
3226
|
+
inflight = getSession(server, sessionId).then(() => buildSnapshotFromSessionEvents(server, sessionId, onProgress)).finally(() => {
|
|
3227
|
+
if (inflightBySessionId2.get(sessionId) === inflight) {
|
|
3228
|
+
inflightBySessionId2.delete(sessionId);
|
|
3229
|
+
}
|
|
3230
|
+
});
|
|
3231
|
+
inflightBySessionId2.set(sessionId, inflight);
|
|
3232
|
+
}
|
|
3233
|
+
return inflight;
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
// src/streamTurn.ts
|
|
3237
|
+
function buildTurnInput(options) {
|
|
3238
|
+
if (options.inputs != null) {
|
|
3239
|
+
return options.inputs;
|
|
3240
|
+
}
|
|
3241
|
+
if (options.resumeMcpAuth === true) {
|
|
3242
|
+
return [];
|
|
3243
|
+
}
|
|
3244
|
+
return [{ type: "user.message", content: options.userMessage ?? "" }];
|
|
3245
|
+
}
|
|
3246
|
+
async function* streamTurnContent(server, sessionId, foldState, options, abortSignal, groupRootBaseline, onTurnIdAvailable) {
|
|
3247
|
+
if (abortSignal.aborted) {
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
let turnIdNotified = false;
|
|
3251
|
+
const notifyTurnId = (turnId) => {
|
|
3252
|
+
if (!turnIdNotified) {
|
|
3253
|
+
onTurnIdAvailable?.(turnId);
|
|
3254
|
+
turnIdNotified = true;
|
|
3255
|
+
}
|
|
3256
|
+
};
|
|
3257
|
+
const stream = server.createTurn({
|
|
3258
|
+
sessionId,
|
|
3259
|
+
input: buildTurnInput(options),
|
|
3260
|
+
previousTurnId: options.previousTurnId ?? "auto",
|
|
3261
|
+
abortSignal,
|
|
3262
|
+
...options.headers != null ? { headers: options.headers } : {}
|
|
3263
|
+
});
|
|
3264
|
+
try {
|
|
3265
|
+
for await (const update of streamTurnEvents(stream, foldState, groupRootBaseline, notifyTurnId)) {
|
|
3266
|
+
yield update;
|
|
3267
|
+
}
|
|
3268
|
+
} catch (error) {
|
|
3269
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3272
|
+
throw error;
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
async function* resumeTurnStream(server, sessionId, turnId, foldState, abortSignal, afterSequenceNumber, groupRootBaseline) {
|
|
3276
|
+
if (server.subscribeToTurn == null) {
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
if (abortSignal.aborted) {
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
try {
|
|
3283
|
+
yield* streamTurnEvents(
|
|
3284
|
+
server.subscribeToTurn({
|
|
3285
|
+
sessionId,
|
|
3286
|
+
turnId,
|
|
3287
|
+
...afterSequenceNumber != null ? { afterSequenceNumber } : {},
|
|
3288
|
+
abortSignal
|
|
3289
|
+
}),
|
|
3290
|
+
foldState,
|
|
3291
|
+
groupRootBaseline
|
|
3292
|
+
);
|
|
3293
|
+
} catch (error) {
|
|
3294
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
throw error;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
// src/useTrueForgeAgentMessages.ts
|
|
3302
|
+
function buildCompletedTurnState(completedAt, requiredActions = []) {
|
|
3303
|
+
return {
|
|
3304
|
+
status: "done",
|
|
3305
|
+
requiredActions,
|
|
3306
|
+
completedAt
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
function requiredActionsFromActiveUpdate(update) {
|
|
3310
|
+
const custom = update.metadata?.custom;
|
|
3311
|
+
const requiredActions = [];
|
|
3312
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3313
|
+
if (custom?.["pendingMcpAuth"] === true && isMcpServerAuthInfoList(custom["mcpServers"])) {
|
|
3314
|
+
const mcpAuthRequired = {
|
|
3315
|
+
type: "mcp.auth_required",
|
|
3316
|
+
id: crypto.randomUUID(),
|
|
3317
|
+
createdAt,
|
|
3318
|
+
mcpServers: custom["mcpServers"]
|
|
3319
|
+
};
|
|
3320
|
+
requiredActions.push(mcpAuthRequired);
|
|
3321
|
+
}
|
|
3322
|
+
if (update.status?.type === "requires-action") {
|
|
3323
|
+
const approvalThreadId = custom?.[TOOL_APPROVAL_THREAD_ID_CUSTOM_KEY];
|
|
3324
|
+
if (typeof approvalThreadId === "string") {
|
|
3325
|
+
const approvalRequired = {
|
|
3326
|
+
type: "tool.approval_required",
|
|
3327
|
+
id: crypto.randomUUID(),
|
|
3328
|
+
createdAt,
|
|
3329
|
+
threadId: approvalThreadId,
|
|
3330
|
+
toolCalls: []
|
|
3331
|
+
};
|
|
3332
|
+
requiredActions.push(approvalRequired);
|
|
3333
|
+
}
|
|
3334
|
+
const responseThreadId = custom?.[TOOL_RESPONSE_THREAD_ID_CUSTOM_KEY];
|
|
3335
|
+
if (typeof responseThreadId === "string") {
|
|
3336
|
+
const responseRequired = {
|
|
3337
|
+
type: "tool.response_required",
|
|
3338
|
+
id: crypto.randomUUID(),
|
|
3339
|
+
createdAt,
|
|
3340
|
+
threadId: responseThreadId,
|
|
3341
|
+
toolCalls: []
|
|
3342
|
+
};
|
|
3343
|
+
requiredActions.push(responseRequired);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
return requiredActions;
|
|
3347
|
+
}
|
|
3348
|
+
function buildUserTurnInput(content) {
|
|
3349
|
+
return { type: "user.message", content };
|
|
3350
|
+
}
|
|
3351
|
+
function appendTurnInputs(base, continuationInputs) {
|
|
3352
|
+
const existingInputs = base ?? [];
|
|
3353
|
+
if (continuationInputs == null || continuationInputs.length === 0) {
|
|
3354
|
+
return existingInputs;
|
|
3355
|
+
}
|
|
3356
|
+
return [...existingInputs, ...continuationInputs];
|
|
3357
|
+
}
|
|
3358
|
+
function cancelScheduledAnimationFrame(frame) {
|
|
3359
|
+
if (frame != null) {
|
|
3360
|
+
cancelAnimationFrame(frame);
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
function commitActiveStream(snapshot, continuationInputs) {
|
|
3364
|
+
const active = snapshot.activeStream;
|
|
3365
|
+
if (active?.streamComplete !== true) {
|
|
3366
|
+
return snapshot;
|
|
3367
|
+
}
|
|
3368
|
+
const activeSandboxIdValue = active.update.metadata?.custom?.["sandboxId"];
|
|
3369
|
+
const activeSandboxId = typeof activeSandboxIdValue === "string" ? activeSandboxIdValue : void 0;
|
|
3370
|
+
const completedState = buildCompletedTurnState(
|
|
3371
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
3372
|
+
requiredActionsFromActiveUpdate(active.update)
|
|
3373
|
+
);
|
|
3374
|
+
const baseline = snapshot.groupRootBaseline ?? computeGroupRootBaseline(snapshot.turns);
|
|
3375
|
+
const rootModelMessageIds = rootModelMessageIdsSinceBaseline(snapshot.fold, baseline);
|
|
3376
|
+
const lastTurn = snapshot.turns.at(-1);
|
|
3377
|
+
if (lastTurn?.id === active.turnId) {
|
|
3378
|
+
return replaceSessionSnapshot(snapshot, {
|
|
3379
|
+
turns: snapshot.turns.map(
|
|
3380
|
+
(turn) => turn.id === active.turnId ? {
|
|
3381
|
+
...turn,
|
|
3382
|
+
state: completedState,
|
|
3383
|
+
input: appendTurnInputs(turn.input, continuationInputs),
|
|
3384
|
+
rootModelMessageIds,
|
|
3385
|
+
...activeSandboxId != null ? { sandboxId: activeSandboxId } : {}
|
|
3386
|
+
} : turn
|
|
3387
|
+
),
|
|
3388
|
+
pendingUser: void 0,
|
|
3389
|
+
// Custom stream adapters may yield projected content without fold events.
|
|
3390
|
+
// Keep that completed projection until the next stream replaces it.
|
|
3391
|
+
...rootModelMessageIds.length > 0 ? { activeStream: void 0 } : {}
|
|
3392
|
+
});
|
|
3393
|
+
}
|
|
3394
|
+
const record = {
|
|
3395
|
+
id: active.turnId,
|
|
3396
|
+
createdAt: snapshot.pendingUser?.createdAt.toISOString() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3397
|
+
state: completedState,
|
|
3398
|
+
input: appendTurnInputs(
|
|
3399
|
+
snapshot.pendingUser ? [buildUserTurnInput(snapshot.pendingUser.content)] : [],
|
|
3400
|
+
continuationInputs
|
|
3401
|
+
),
|
|
3402
|
+
...snapshot.pendingUser ? { userText: userMessageContentToText(snapshot.pendingUser.content) } : {},
|
|
3403
|
+
rootModelMessageIds,
|
|
3404
|
+
...activeSandboxId != null ? { sandboxId: activeSandboxId } : {}
|
|
3405
|
+
};
|
|
3406
|
+
return replaceSessionSnapshot(snapshot, {
|
|
3407
|
+
turns: [...snapshot.turns, record],
|
|
3408
|
+
pendingUser: void 0,
|
|
3409
|
+
...rootModelMessageIds.length > 0 ? { activeStream: void 0 } : {}
|
|
3410
|
+
});
|
|
3411
|
+
}
|
|
3412
|
+
var MAX_SANDBOX_HISTORY_PAGE_INS = 20;
|
|
3413
|
+
function findSandboxIdInSnapshot(snapshot, turnId) {
|
|
3414
|
+
const active = snapshot.activeStream;
|
|
3415
|
+
if (active?.turnId === turnId) {
|
|
3416
|
+
const sandboxId = active.update.metadata?.custom?.["sandboxId"];
|
|
3417
|
+
if (typeof sandboxId === "string") {
|
|
3418
|
+
return sandboxId;
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
const turns = snapshot.turns;
|
|
3422
|
+
const turnIndex = turns.findIndex((turn) => turn.id === turnId);
|
|
3423
|
+
for (let i = turnIndex === -1 ? turns.length - 1 : turnIndex; i >= 0; i--) {
|
|
3424
|
+
const sandboxId = turns[i]?.sandboxId;
|
|
3425
|
+
if (sandboxId != null) {
|
|
3426
|
+
return sandboxId;
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
return void 0;
|
|
3430
|
+
}
|
|
3431
|
+
async function resolveActiveSessionId(remoteId, resolveConversationSessionId) {
|
|
3432
|
+
if (resolveConversationSessionId != null) {
|
|
3433
|
+
return resolveConversationSessionId(remoteId);
|
|
3434
|
+
}
|
|
3435
|
+
return remoteId;
|
|
3436
|
+
}
|
|
3437
|
+
function resolveTurnInput(snapshot, turnId) {
|
|
3438
|
+
const turnRecord = snapshot.turns.find((turn) => turn.id === turnId);
|
|
3439
|
+
if (turnRecord?.input != null) {
|
|
3440
|
+
return turnRecord.input;
|
|
3441
|
+
}
|
|
3442
|
+
if (snapshot.pendingUser?.turnId === turnId) {
|
|
3443
|
+
return [{ type: "user.message", content: snapshot.pendingUser.content }];
|
|
3444
|
+
}
|
|
3445
|
+
return void 0;
|
|
3446
|
+
}
|
|
3447
|
+
function useTrueForgeAgentMessages({
|
|
3448
|
+
server,
|
|
3449
|
+
sessionId,
|
|
3450
|
+
isMain,
|
|
3451
|
+
isInitialSession,
|
|
3452
|
+
onError,
|
|
3453
|
+
initializeSession,
|
|
3454
|
+
resolveConversationSessionId,
|
|
3455
|
+
getTurnHeaders
|
|
3456
|
+
}) {
|
|
3457
|
+
const [snapshot, setSnapshot] = useState2(createEmptySessionSnapshot);
|
|
3458
|
+
const [isRunning, setIsRunning] = useState2(false);
|
|
3459
|
+
const [isLoading, setIsLoading] = useState2(sessionId != null && (isMain !== false || isInitialSession === true));
|
|
3460
|
+
const [isLoadingOlderHistory, setIsLoadingOlderHistory] = useState2(false);
|
|
3461
|
+
const [loadRetryTrigger, setLoadRetryTrigger] = useState2(0);
|
|
3462
|
+
const [resumeUnavailable, setResumeUnavailable] = useState2(false);
|
|
3463
|
+
const snapshotRef = useRef2(snapshot);
|
|
3464
|
+
snapshotRef.current = snapshot;
|
|
3465
|
+
const sessionIdRef = useRef2(sessionId);
|
|
3466
|
+
sessionIdRef.current = sessionId;
|
|
3467
|
+
const loadOlderInflightRef = useRef2(null);
|
|
3468
|
+
const onErrorRef = useRef2(onError);
|
|
3469
|
+
onErrorRef.current = onError;
|
|
3470
|
+
const resolveConversationSessionIdRef = useRef2(resolveConversationSessionId);
|
|
3471
|
+
resolveConversationSessionIdRef.current = resolveConversationSessionId;
|
|
3472
|
+
const initializeSessionRef = useRef2(initializeSession);
|
|
3473
|
+
initializeSessionRef.current = initializeSession;
|
|
3474
|
+
const getTurnHeadersRef = useRef2(getTurnHeaders);
|
|
3475
|
+
getTurnHeadersRef.current = getTurnHeaders;
|
|
3476
|
+
const createdAtByMessageIdRef = useRef2(/* @__PURE__ */ new Map());
|
|
3477
|
+
const abortControllerRef = useRef2(null);
|
|
3478
|
+
const activeRunRef = useRef2(null);
|
|
3479
|
+
const resumeUnavailableRef = useRef2(false);
|
|
3480
|
+
const runningTurnRef = useRef2(void 0);
|
|
3481
|
+
const loadGenerationRef = useRef2(0);
|
|
3482
|
+
const streamGenerationRef = useRef2(0);
|
|
3483
|
+
const lazilyCreatedSessionIdRef = useRef2(void 0);
|
|
3484
|
+
const initialLoadStartedForRef = useRef2(void 0);
|
|
3485
|
+
const skipInitialPromotionLoadForRef = useRef2(void 0);
|
|
3486
|
+
const markResumeUnavailable = useCallback3((value) => {
|
|
3487
|
+
resumeUnavailableRef.current = value;
|
|
3488
|
+
setResumeUnavailable(value);
|
|
3489
|
+
}, []);
|
|
3490
|
+
const projectOptions = useMemo3(
|
|
3491
|
+
() => ({
|
|
3492
|
+
getCreatedAt: (messageId, fallback, replace = false) => {
|
|
3493
|
+
const cache = createdAtByMessageIdRef.current;
|
|
3494
|
+
const existing = cache.get(messageId);
|
|
3495
|
+
if (existing != null && (!replace || existing.getTime() === fallback.getTime())) {
|
|
3496
|
+
return existing;
|
|
3497
|
+
}
|
|
3498
|
+
cache.set(messageId, fallback);
|
|
3499
|
+
return fallback;
|
|
3500
|
+
}
|
|
3501
|
+
}),
|
|
3502
|
+
[]
|
|
3503
|
+
);
|
|
3504
|
+
const messages = useMemo3(() => projectSessionMessages(snapshot, projectOptions), [snapshot, projectOptions]);
|
|
3505
|
+
const runStream = useCallback3(
|
|
3506
|
+
(createStream, turnIdRef, isContinuation) => {
|
|
3507
|
+
const streamGeneration = ++streamGenerationRef.current;
|
|
3508
|
+
abortControllerRef.current?.abort();
|
|
3509
|
+
const abortController = new AbortController();
|
|
3510
|
+
abortControllerRef.current = abortController;
|
|
3511
|
+
setIsRunning(true);
|
|
3512
|
+
markResumeUnavailable(false);
|
|
3513
|
+
const run = (async () => {
|
|
3514
|
+
let pendingStreamUpdate = null;
|
|
3515
|
+
let streamUpdateRaf = null;
|
|
3516
|
+
const flushPendingStreamUpdate = () => {
|
|
3517
|
+
streamUpdateRaf = null;
|
|
3518
|
+
const pending = pendingStreamUpdate;
|
|
3519
|
+
pendingStreamUpdate = null;
|
|
3520
|
+
if (pending == null || streamGeneration !== streamGenerationRef.current) {
|
|
3521
|
+
return;
|
|
3522
|
+
}
|
|
3523
|
+
const { update, isContinuation: pendingIsContinuation } = pending;
|
|
3524
|
+
setSnapshot(
|
|
3525
|
+
(prev) => replaceSessionSnapshot(prev, {
|
|
3526
|
+
activeStream: {
|
|
3527
|
+
// Read from the ref so we always use the latest ID,
|
|
3528
|
+
// including any gateway ID that arrived after the RAf
|
|
3529
|
+
// was scheduled.
|
|
3530
|
+
turnId: turnIdRef.current,
|
|
3531
|
+
update,
|
|
3532
|
+
isContinuation: pendingIsContinuation
|
|
3533
|
+
}
|
|
3534
|
+
})
|
|
3535
|
+
);
|
|
3536
|
+
};
|
|
3537
|
+
const applyStreamUpdate = (update) => {
|
|
3538
|
+
pendingStreamUpdate = { update, isContinuation };
|
|
3539
|
+
streamUpdateRaf ??= requestAnimationFrame(flushPendingStreamUpdate);
|
|
3540
|
+
};
|
|
3541
|
+
try {
|
|
3542
|
+
for await (const update of createStream(abortController.signal)) {
|
|
3543
|
+
if (abortController.signal.aborted) {
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
applyStreamUpdate(update);
|
|
3547
|
+
}
|
|
3548
|
+
} catch (error) {
|
|
3549
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
onErrorRef.current?.(error);
|
|
3553
|
+
throw error;
|
|
3554
|
+
} finally {
|
|
3555
|
+
cancelScheduledAnimationFrame(streamUpdateRaf);
|
|
3556
|
+
if (streamGeneration === streamGenerationRef.current) {
|
|
3557
|
+
flushPendingStreamUpdate();
|
|
3558
|
+
if (abortControllerRef.current === abortController) {
|
|
3559
|
+
abortControllerRef.current = null;
|
|
3560
|
+
}
|
|
3561
|
+
setIsRunning(false);
|
|
3562
|
+
setSnapshot((prev) => {
|
|
3563
|
+
if (prev.activeStream == null) {
|
|
3564
|
+
return prev;
|
|
3565
|
+
}
|
|
3566
|
+
const marked = replaceSessionSnapshot(prev, {
|
|
3567
|
+
activeStream: {
|
|
3568
|
+
...prev.activeStream,
|
|
3569
|
+
streamComplete: true
|
|
3570
|
+
},
|
|
3571
|
+
requiredActions: {
|
|
3572
|
+
approvals: /* @__PURE__ */ new Map(),
|
|
3573
|
+
toolResponses: /* @__PURE__ */ new Map()
|
|
3574
|
+
}
|
|
3575
|
+
});
|
|
3576
|
+
return commitActiveStream(marked);
|
|
3577
|
+
});
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
})();
|
|
3581
|
+
activeRunRef.current = run;
|
|
3582
|
+
void run.catch(() => void 0).finally(() => {
|
|
3583
|
+
if (activeRunRef.current === run) {
|
|
3584
|
+
activeRunRef.current = null;
|
|
3585
|
+
}
|
|
3586
|
+
});
|
|
3587
|
+
return run;
|
|
3588
|
+
},
|
|
3589
|
+
[onError]
|
|
3590
|
+
);
|
|
3591
|
+
const load = useCallback3(async () => {
|
|
3592
|
+
void loadRetryTrigger;
|
|
3593
|
+
if (sessionId == null) {
|
|
3594
|
+
createdAtByMessageIdRef.current = /* @__PURE__ */ new Map();
|
|
3595
|
+
setSnapshot(createEmptySessionSnapshot());
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
const isEarlyInitialLoad = isMain === false && isInitialSession === true && initialLoadStartedForRef.current !== sessionId;
|
|
3599
|
+
if (isMain === false) {
|
|
3600
|
+
if (!isEarlyInitialLoad) {
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
initialLoadStartedForRef.current = sessionId;
|
|
3604
|
+
skipInitialPromotionLoadForRef.current = sessionId;
|
|
3605
|
+
} else if (isMain === true && skipInitialPromotionLoadForRef.current === sessionId) {
|
|
3606
|
+
skipInitialPromotionLoadForRef.current = void 0;
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
if (isInitialSession === true) {
|
|
3610
|
+
initialLoadStartedForRef.current = sessionId;
|
|
3611
|
+
}
|
|
3612
|
+
if (lazilyCreatedSessionIdRef.current != null && sessionId !== lazilyCreatedSessionIdRef.current) {
|
|
3613
|
+
lazilyCreatedSessionIdRef.current = void 0;
|
|
3614
|
+
}
|
|
3615
|
+
if (sessionId === lazilyCreatedSessionIdRef.current) {
|
|
3616
|
+
return;
|
|
3617
|
+
}
|
|
3618
|
+
const generation = ++loadGenerationRef.current;
|
|
3619
|
+
++streamGenerationRef.current;
|
|
3620
|
+
setIsRunning(false);
|
|
3621
|
+
markResumeUnavailable(false);
|
|
3622
|
+
abortControllerRef.current?.abort();
|
|
3623
|
+
loadOlderInflightRef.current = null;
|
|
3624
|
+
createdAtByMessageIdRef.current = /* @__PURE__ */ new Map();
|
|
3625
|
+
setSnapshot(createEmptySessionSnapshot());
|
|
3626
|
+
setIsLoading(true);
|
|
3627
|
+
setIsLoadingOlderHistory(false);
|
|
3628
|
+
try {
|
|
3629
|
+
const conversationSessionId = await resolveActiveSessionId(sessionId, resolveConversationSessionIdRef.current);
|
|
3630
|
+
const loadedSnapshot = await loadSessionSnapshot(server, conversationSessionId, (snap) => {
|
|
3631
|
+
if (generation === loadGenerationRef.current) {
|
|
3632
|
+
setSnapshot(snap);
|
|
3633
|
+
}
|
|
3634
|
+
});
|
|
3635
|
+
if (generation !== loadGenerationRef.current) {
|
|
3636
|
+
return;
|
|
3637
|
+
}
|
|
3638
|
+
createdAtByMessageIdRef.current = /* @__PURE__ */ new Map();
|
|
3639
|
+
setSnapshot(loadedSnapshot);
|
|
3640
|
+
runningTurnRef.current = loadedSnapshot.runningTurn;
|
|
3641
|
+
setIsLoading(false);
|
|
3642
|
+
if (loadedSnapshot.runningTurn != null) {
|
|
3643
|
+
const turn = loadedSnapshot.runningTurn;
|
|
3644
|
+
if (server.subscribeToTurn == null) {
|
|
3645
|
+
setIsRunning(true);
|
|
3646
|
+
markResumeUnavailable(true);
|
|
3647
|
+
return;
|
|
3648
|
+
}
|
|
3649
|
+
const isContinuation = extractTurnUserText(turn.input) === void 0;
|
|
3650
|
+
void runStream(
|
|
3651
|
+
(signal) => resumeTurnStream(
|
|
3652
|
+
server,
|
|
3653
|
+
conversationSessionId,
|
|
3654
|
+
turn.id,
|
|
3655
|
+
loadedSnapshot.fold,
|
|
3656
|
+
signal,
|
|
3657
|
+
void 0,
|
|
3658
|
+
loadedSnapshot.groupRootBaseline
|
|
3659
|
+
),
|
|
3660
|
+
{ current: turn.id },
|
|
3661
|
+
isContinuation
|
|
3662
|
+
).catch(() => void 0);
|
|
3663
|
+
}
|
|
3664
|
+
} catch (error) {
|
|
3665
|
+
if (generation === loadGenerationRef.current) {
|
|
3666
|
+
if (isEarlyInitialLoad) {
|
|
3667
|
+
initialLoadStartedForRef.current = void 0;
|
|
3668
|
+
skipInitialPromotionLoadForRef.current = void 0;
|
|
3669
|
+
}
|
|
3670
|
+
onErrorRef.current?.(error);
|
|
3671
|
+
}
|
|
3672
|
+
throw error;
|
|
3673
|
+
} finally {
|
|
3674
|
+
if (generation === loadGenerationRef.current) {
|
|
3675
|
+
setIsLoading(false);
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
}, [server, runStream, sessionId, loadRetryTrigger, isMain, isInitialSession]);
|
|
3679
|
+
useEffect2(() => {
|
|
3680
|
+
void load().catch(() => void 0);
|
|
3681
|
+
}, [load]);
|
|
3682
|
+
const sendTurn = useCallback3(
|
|
3683
|
+
async (options) => {
|
|
3684
|
+
const gatewayTurnAccepted = { current: false };
|
|
3685
|
+
let pendingUserWasSet = false;
|
|
3686
|
+
let runStreamStarted = false;
|
|
3687
|
+
let pendingUserTurnId;
|
|
3688
|
+
try {
|
|
3689
|
+
let activeSessionId = sessionId;
|
|
3690
|
+
if (activeSessionId == null) {
|
|
3691
|
+
if (initializeSessionRef.current == null) {
|
|
3692
|
+
throw new Error("Cannot send a turn without an active session.");
|
|
3693
|
+
}
|
|
3694
|
+
const { remoteId } = await initializeSessionRef.current();
|
|
3695
|
+
activeSessionId = remoteId;
|
|
3696
|
+
lazilyCreatedSessionIdRef.current = remoteId;
|
|
3697
|
+
}
|
|
3698
|
+
const conversationSessionId = await resolveActiveSessionId(
|
|
3699
|
+
activeSessionId,
|
|
3700
|
+
resolveConversationSessionIdRef.current
|
|
3701
|
+
);
|
|
3702
|
+
const turnHeaders = await getTurnHeadersRef.current?.();
|
|
3703
|
+
const streamHeaders = turnHeaders != null ? { headers: turnHeaders } : {};
|
|
3704
|
+
const isContinuation = "inputs" in options || "resumeMcpAuth" in options && options.resumeMcpAuth;
|
|
3705
|
+
const continuationTurnId = snapshotRef.current.activeStream?.turnId;
|
|
3706
|
+
const turnId = isContinuation ? (
|
|
3707
|
+
// A paused stream is usually already committed (commitActiveStream
|
|
3708
|
+
// cleared activeStream), so continue under the committed turn's
|
|
3709
|
+
// real id. Never mint a local id for a continuation — it leaks to
|
|
3710
|
+
// the backend via `custom.turnId` (sandbox downloads, edit/retry)
|
|
3711
|
+
// as a turn the gateway has never heard of.
|
|
3712
|
+
continuationTurnId ?? snapshotRef.current.turns.at(-1)?.id ?? crypto.randomUUID()
|
|
3713
|
+
) : crypto.randomUUID();
|
|
3714
|
+
const isFirstTurnInSession = "userMessage" in options && options.previousTurnId === void 0 && snapshotRef.current.turns.length === 0 && snapshotRef.current.pendingUser == null && snapshotRef.current.activeStream == null;
|
|
3715
|
+
const turnIdRef = { current: turnId };
|
|
3716
|
+
const handleGatewayTurnId = (gatewayTurnId) => {
|
|
3717
|
+
const oldId = turnIdRef.current;
|
|
3718
|
+
gatewayTurnAccepted.current = true;
|
|
3719
|
+
if (gatewayTurnId === oldId) {
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
turnIdRef.current = gatewayTurnId;
|
|
3723
|
+
const renamePendingUser = (prev) => {
|
|
3724
|
+
if (prev.pendingUser?.turnId !== oldId) {
|
|
3725
|
+
return prev;
|
|
3726
|
+
}
|
|
3727
|
+
return replaceSessionSnapshot(prev, {
|
|
3728
|
+
pendingUser: {
|
|
3729
|
+
...prev.pendingUser,
|
|
3730
|
+
turnId: gatewayTurnId
|
|
3731
|
+
}
|
|
3732
|
+
});
|
|
3733
|
+
};
|
|
3734
|
+
snapshotRef.current = renamePendingUser(snapshotRef.current);
|
|
3735
|
+
setSnapshot(renamePendingUser);
|
|
3736
|
+
};
|
|
3737
|
+
if ("inputs" in options) {
|
|
3738
|
+
applyUserToolResponsesToFold(snapshotRef.current.fold, options.inputs);
|
|
3739
|
+
}
|
|
3740
|
+
const branchBase = "userMessage" in options ? options.branchFromSnapshot : void 0;
|
|
3741
|
+
let groupRootBaseline;
|
|
3742
|
+
if (branchBase != null && "userMessage" in options) {
|
|
3743
|
+
const rootBucket = branchBase.fold.threads.get(ROOT_THREAD_ID);
|
|
3744
|
+
groupRootBaseline = [...rootBucket?.modelMessageIds ?? []];
|
|
3745
|
+
const nextSnapshot = replaceSessionSnapshot(branchBase, {
|
|
3746
|
+
pendingUser: {
|
|
3747
|
+
turnId,
|
|
3748
|
+
content: options.userMessage,
|
|
3749
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3750
|
+
},
|
|
3751
|
+
activeStream: void 0,
|
|
3752
|
+
groupRootBaseline
|
|
3753
|
+
});
|
|
3754
|
+
snapshotRef.current = nextSnapshot;
|
|
3755
|
+
setSnapshot(nextSnapshot);
|
|
3756
|
+
pendingUserWasSet = true;
|
|
3757
|
+
pendingUserTurnId = turnId;
|
|
3758
|
+
} else {
|
|
3759
|
+
setSnapshot((prev) => commitActiveStream(prev, "inputs" in options ? options.inputs : void 0));
|
|
3760
|
+
if ("userMessage" in options) {
|
|
3761
|
+
const rootBucket = snapshotRef.current.fold.threads.get(ROOT_THREAD_ID);
|
|
3762
|
+
groupRootBaseline = [...rootBucket?.modelMessageIds ?? []];
|
|
3763
|
+
setSnapshot((prev) => {
|
|
3764
|
+
const next = replaceSessionSnapshot(prev, {
|
|
3765
|
+
pendingUser: {
|
|
3766
|
+
turnId,
|
|
3767
|
+
content: options.userMessage,
|
|
3768
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3769
|
+
},
|
|
3770
|
+
activeStream: void 0,
|
|
3771
|
+
groupRootBaseline
|
|
3772
|
+
});
|
|
3773
|
+
snapshotRef.current = next;
|
|
3774
|
+
return next;
|
|
3775
|
+
});
|
|
3776
|
+
pendingUserWasSet = true;
|
|
3777
|
+
pendingUserTurnId = turnId;
|
|
3778
|
+
} else {
|
|
3779
|
+
groupRootBaseline = snapshotRef.current.groupRootBaseline ?? computeGroupRootBaseline(snapshotRef.current.turns);
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
runStreamStarted = true;
|
|
3783
|
+
await runStream(
|
|
3784
|
+
(signal) => {
|
|
3785
|
+
if ("inputs" in options) {
|
|
3786
|
+
return streamTurnContent(
|
|
3787
|
+
server,
|
|
3788
|
+
conversationSessionId,
|
|
3789
|
+
snapshotRef.current.fold,
|
|
3790
|
+
{ inputs: options.inputs, ...streamHeaders },
|
|
3791
|
+
signal,
|
|
3792
|
+
groupRootBaseline,
|
|
3793
|
+
handleGatewayTurnId
|
|
3794
|
+
);
|
|
3795
|
+
}
|
|
3796
|
+
if ("resumeMcpAuth" in options) {
|
|
3797
|
+
return streamTurnContent(
|
|
3798
|
+
server,
|
|
3799
|
+
conversationSessionId,
|
|
3800
|
+
snapshotRef.current.fold,
|
|
3801
|
+
{ resumeMcpAuth: true, ...streamHeaders },
|
|
3802
|
+
signal,
|
|
3803
|
+
groupRootBaseline,
|
|
3804
|
+
handleGatewayTurnId
|
|
3805
|
+
);
|
|
3806
|
+
}
|
|
3807
|
+
return streamTurnContent(
|
|
3808
|
+
server,
|
|
3809
|
+
conversationSessionId,
|
|
3810
|
+
snapshotRef.current.fold,
|
|
3811
|
+
{
|
|
3812
|
+
userMessage: options.userMessage,
|
|
3813
|
+
...options.previousTurnId !== void 0 ? { previousTurnId: options.previousTurnId ?? "none" } : isFirstTurnInSession ? { previousTurnId: "none" } : {},
|
|
3814
|
+
...streamHeaders
|
|
3815
|
+
},
|
|
3816
|
+
signal,
|
|
3817
|
+
groupRootBaseline,
|
|
3818
|
+
handleGatewayTurnId
|
|
3819
|
+
);
|
|
3820
|
+
},
|
|
3821
|
+
turnIdRef,
|
|
3822
|
+
isContinuation
|
|
3823
|
+
);
|
|
3824
|
+
} catch (error) {
|
|
3825
|
+
if ("userMessage" in options && !gatewayTurnAccepted.current) {
|
|
3826
|
+
const branchRollbackSnapshot = options.branchRollbackSnapshot;
|
|
3827
|
+
const canRestoreBranch = branchRollbackSnapshot != null && (snapshotRef.current === options.branchFromSnapshot || snapshotRef.current.pendingUser?.turnId === pendingUserTurnId);
|
|
3828
|
+
if (canRestoreBranch) {
|
|
3829
|
+
snapshotRef.current = branchRollbackSnapshot;
|
|
3830
|
+
setSnapshot(branchRollbackSnapshot);
|
|
3831
|
+
} else if (pendingUserWasSet) {
|
|
3832
|
+
const clearPendingUser = (previous) => {
|
|
3833
|
+
if (previous.pendingUser?.turnId !== pendingUserTurnId) {
|
|
3834
|
+
return previous;
|
|
3835
|
+
}
|
|
3836
|
+
return replaceSessionSnapshot(previous, {
|
|
3837
|
+
pendingUser: void 0
|
|
3838
|
+
});
|
|
3839
|
+
};
|
|
3840
|
+
snapshotRef.current = clearPendingUser(snapshotRef.current);
|
|
3841
|
+
setSnapshot(clearPendingUser);
|
|
3842
|
+
}
|
|
3843
|
+
options.onPreTurnFailure?.();
|
|
3844
|
+
}
|
|
3845
|
+
if (!runStreamStarted) {
|
|
3846
|
+
onErrorRef.current?.(error);
|
|
3847
|
+
}
|
|
3848
|
+
throw error;
|
|
3849
|
+
}
|
|
3850
|
+
},
|
|
3851
|
+
[server, runStream, sessionId]
|
|
3852
|
+
);
|
|
3853
|
+
const cancel = useCallback3(async () => {
|
|
3854
|
+
const activeSessionId = sessionId ?? lazilyCreatedSessionIdRef.current;
|
|
3855
|
+
if (activeSessionId == null) {
|
|
3856
|
+
abortControllerRef.current?.abort();
|
|
3857
|
+
return;
|
|
3858
|
+
}
|
|
3859
|
+
const conversationSessionId = await resolveActiveSessionId(
|
|
3860
|
+
activeSessionId,
|
|
3861
|
+
resolveConversationSessionIdRef.current
|
|
3862
|
+
);
|
|
3863
|
+
await server.cancelSession({ sessionId: conversationSessionId }).catch(() => void 0);
|
|
3864
|
+
await activeRunRef.current?.catch(() => void 0);
|
|
3865
|
+
if (resumeUnavailableRef.current) {
|
|
3866
|
+
markResumeUnavailable(false);
|
|
3867
|
+
setIsRunning(false);
|
|
3868
|
+
}
|
|
3869
|
+
}, [server, sessionId, markResumeUnavailable]);
|
|
3870
|
+
const isRunningRef = useRef2(isRunning);
|
|
3871
|
+
isRunningRef.current = isRunning;
|
|
3872
|
+
const trySendCollectedRequiredActions = useCallback3(
|
|
3873
|
+
(nextSnapshot) => {
|
|
3874
|
+
if (isRunningRef.current) {
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3877
|
+
const projected = projectSessionMessages(nextSnapshot, projectOptions);
|
|
3878
|
+
const paused = findPausedAssistantMessage(projected);
|
|
3879
|
+
if (paused == null || messageHasPendingRequiredActions(paused)) {
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3882
|
+
const inputs = collectRequiredActionInputs(paused);
|
|
3883
|
+
if (inputs.length > 0) {
|
|
3884
|
+
void sendTurn({ inputs }).catch(() => void 0);
|
|
3885
|
+
}
|
|
3886
|
+
},
|
|
3887
|
+
[projectOptions, sendTurn]
|
|
3888
|
+
);
|
|
3889
|
+
const respondToToolApproval = useCallback3(
|
|
3890
|
+
(response) => {
|
|
3891
|
+
const prev = snapshotRef.current;
|
|
3892
|
+
const approvals = new Map(prev.requiredActions.approvals);
|
|
3893
|
+
approvals.set(response.approvalId, {
|
|
3894
|
+
approved: response.approved,
|
|
3895
|
+
...response.reason != null ? { reason: response.reason } : {}
|
|
3896
|
+
});
|
|
3897
|
+
const nextSnapshot = replaceSessionSnapshot(prev, {
|
|
3898
|
+
requiredActions: {
|
|
3899
|
+
...prev.requiredActions,
|
|
3900
|
+
approvals
|
|
3901
|
+
}
|
|
3902
|
+
});
|
|
3903
|
+
setSnapshot(nextSnapshot);
|
|
3904
|
+
trySendCollectedRequiredActions(nextSnapshot);
|
|
3905
|
+
},
|
|
3906
|
+
[trySendCollectedRequiredActions]
|
|
3907
|
+
);
|
|
3908
|
+
const respondToToolResponse = useCallback3(
|
|
3909
|
+
(response) => {
|
|
3910
|
+
const prev = snapshotRef.current;
|
|
3911
|
+
const toolResponses = new Map(prev.requiredActions.toolResponses);
|
|
3912
|
+
toolResponses.set(response.toolCallId, { content: response.content });
|
|
3913
|
+
const nextSnapshot = replaceSessionSnapshot(prev, {
|
|
3914
|
+
requiredActions: {
|
|
3915
|
+
...prev.requiredActions,
|
|
3916
|
+
toolResponses
|
|
3917
|
+
}
|
|
3918
|
+
});
|
|
3919
|
+
setSnapshot(nextSnapshot);
|
|
3920
|
+
trySendCollectedRequiredActions(nextSnapshot);
|
|
3921
|
+
},
|
|
3922
|
+
[trySendCollectedRequiredActions]
|
|
3923
|
+
);
|
|
3924
|
+
const resumeRun = useCallback3(async () => {
|
|
3925
|
+
const turn = runningTurnRef.current;
|
|
3926
|
+
if (turn == null) {
|
|
3927
|
+
return;
|
|
3928
|
+
}
|
|
3929
|
+
if (server.subscribeToTurn == null) {
|
|
3930
|
+
markResumeUnavailable(true);
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
await runStream(
|
|
3934
|
+
(signal) => resumeTurnStream(
|
|
3935
|
+
server,
|
|
3936
|
+
turn.sessionId,
|
|
3937
|
+
turn.id,
|
|
3938
|
+
snapshotRef.current.fold,
|
|
3939
|
+
signal,
|
|
3940
|
+
void 0,
|
|
3941
|
+
snapshotRef.current.groupRootBaseline
|
|
3942
|
+
),
|
|
3943
|
+
{ current: turn.id },
|
|
3944
|
+
true
|
|
3945
|
+
);
|
|
3946
|
+
}, [runStream, server]);
|
|
3947
|
+
const branchFromTurn = useCallback3(
|
|
3948
|
+
async (turnId, userMessage) => {
|
|
3949
|
+
let committed;
|
|
3950
|
+
let previousTurnId;
|
|
3951
|
+
let rewound;
|
|
3952
|
+
try {
|
|
3953
|
+
const activeSessionId = sessionId;
|
|
3954
|
+
if (activeSessionId == null) {
|
|
3955
|
+
throw new Error("Cannot branch from a turn without an active session.");
|
|
3956
|
+
}
|
|
3957
|
+
committed = commitActiveStream(snapshotRef.current);
|
|
3958
|
+
setSnapshot(committed);
|
|
3959
|
+
await cancel();
|
|
3960
|
+
const conversationSessionId = await resolveActiveSessionId(
|
|
3961
|
+
activeSessionId,
|
|
3962
|
+
resolveConversationSessionIdRef.current
|
|
3963
|
+
);
|
|
3964
|
+
previousTurnId = await resolveGatewayBranchPreviousTurnIdForTurn(server, conversationSessionId, turnId);
|
|
3965
|
+
rewound = await buildSnapshotThroughTurn(
|
|
3966
|
+
server,
|
|
3967
|
+
conversationSessionId,
|
|
3968
|
+
previousTurnId === "none" ? null : previousTurnId
|
|
3969
|
+
);
|
|
3970
|
+
createdAtByMessageIdRef.current = /* @__PURE__ */ new Map();
|
|
3971
|
+
snapshotRef.current = rewound;
|
|
3972
|
+
setSnapshot(rewound);
|
|
3973
|
+
} catch (error) {
|
|
3974
|
+
onErrorRef.current?.(error);
|
|
3975
|
+
throw error;
|
|
3976
|
+
}
|
|
3977
|
+
await sendTurn({
|
|
3978
|
+
userMessage,
|
|
3979
|
+
previousTurnId,
|
|
3980
|
+
branchFromSnapshot: rewound,
|
|
3981
|
+
branchRollbackSnapshot: committed
|
|
3982
|
+
});
|
|
3983
|
+
},
|
|
3984
|
+
[cancel, server, sendTurn, sessionId]
|
|
3985
|
+
);
|
|
3986
|
+
const resetFromTurn = useCallback3(
|
|
3987
|
+
async (turnId) => {
|
|
3988
|
+
const committed = commitActiveStream(snapshotRef.current);
|
|
3989
|
+
const originalInput = resolveTurnInput(committed, turnId);
|
|
3990
|
+
if (originalInput == null) {
|
|
3991
|
+
const error = new Error(`Turn ${turnId} not found in session snapshot`);
|
|
3992
|
+
onErrorRef.current?.(error);
|
|
3993
|
+
throw error;
|
|
3994
|
+
}
|
|
3995
|
+
const userMessage = extractTurnUserMessageContent(originalInput);
|
|
3996
|
+
await branchFromTurn(turnId, userMessage);
|
|
3997
|
+
},
|
|
3998
|
+
[branchFromTurn]
|
|
3999
|
+
);
|
|
4000
|
+
const editFromTurn = useCallback3(
|
|
4001
|
+
async (turnId, editedText) => {
|
|
4002
|
+
const committed = commitActiveStream(snapshotRef.current);
|
|
4003
|
+
const originalInput = resolveTurnInput(committed, turnId);
|
|
4004
|
+
if (originalInput == null) {
|
|
4005
|
+
const error = new Error(`Turn ${turnId} not found in session snapshot`);
|
|
4006
|
+
onErrorRef.current?.(error);
|
|
4007
|
+
throw error;
|
|
4008
|
+
}
|
|
4009
|
+
const userMessage = buildEditedUserMessageContent(editedText, originalInput);
|
|
4010
|
+
await branchFromTurn(turnId, userMessage);
|
|
4011
|
+
},
|
|
4012
|
+
[branchFromTurn]
|
|
4013
|
+
);
|
|
4014
|
+
const retryLoad = useCallback3(() => {
|
|
4015
|
+
setLoadRetryTrigger((n) => n + 1);
|
|
4016
|
+
}, []);
|
|
4017
|
+
const hasOlderHistory = snapshot.historyPagination?.hasOlder === true;
|
|
4018
|
+
const loadOlderHistory = useCallback3(async () => {
|
|
4019
|
+
if (sessionId == null || isMain === false) {
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
const requestedSessionId = sessionId;
|
|
4023
|
+
if (sessionIdRef.current !== requestedSessionId) {
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
if (loadOlderInflightRef.current != null) {
|
|
4027
|
+
return loadOlderInflightRef.current;
|
|
4028
|
+
}
|
|
4029
|
+
const current = snapshotRef.current;
|
|
4030
|
+
if (current.historyPagination?.hasOlder !== true) {
|
|
4031
|
+
return;
|
|
4032
|
+
}
|
|
4033
|
+
if (current.historyPagination.olderPageToken == null) {
|
|
4034
|
+
return;
|
|
4035
|
+
}
|
|
4036
|
+
const generation = loadGenerationRef.current;
|
|
4037
|
+
setIsLoadingOlderHistory(true);
|
|
4038
|
+
const run = (async () => {
|
|
4039
|
+
const stillCurrent = () => generation === loadGenerationRef.current && sessionIdRef.current === requestedSessionId;
|
|
4040
|
+
try {
|
|
4041
|
+
const conversationSessionId = await resolveActiveSessionId(
|
|
4042
|
+
requestedSessionId,
|
|
4043
|
+
resolveConversationSessionIdRef.current
|
|
4044
|
+
);
|
|
4045
|
+
if (!stillCurrent()) {
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
const next = await prependOlderSessionHistory(server, conversationSessionId, snapshotRef.current);
|
|
4049
|
+
if (!stillCurrent()) {
|
|
4050
|
+
return;
|
|
4051
|
+
}
|
|
4052
|
+
snapshotRef.current = next;
|
|
4053
|
+
setSnapshot(next);
|
|
4054
|
+
} catch (error) {
|
|
4055
|
+
if (stillCurrent()) {
|
|
4056
|
+
onErrorRef.current?.(error);
|
|
4057
|
+
}
|
|
4058
|
+
throw error;
|
|
4059
|
+
} finally {
|
|
4060
|
+
if (stillCurrent()) {
|
|
4061
|
+
setIsLoadingOlderHistory(false);
|
|
4062
|
+
}
|
|
4063
|
+
loadOlderInflightRef.current = null;
|
|
4064
|
+
}
|
|
4065
|
+
})();
|
|
4066
|
+
loadOlderInflightRef.current = run;
|
|
4067
|
+
return run;
|
|
4068
|
+
}, [server, isMain, sessionId]);
|
|
4069
|
+
const resolveSandboxIdForTurn = useCallback3(
|
|
4070
|
+
async (turnId) => {
|
|
4071
|
+
const generation = loadGenerationRef.current;
|
|
4072
|
+
const requestedSessionId = sessionId;
|
|
4073
|
+
const stillCurrent = () => generation === loadGenerationRef.current && sessionIdRef.current === requestedSessionId;
|
|
4074
|
+
if (!stillCurrent()) {
|
|
4075
|
+
return void 0;
|
|
4076
|
+
}
|
|
4077
|
+
let sandboxId = findSandboxIdInSnapshot(snapshotRef.current, turnId);
|
|
4078
|
+
for (let i = 0; sandboxId == null && stillCurrent() && snapshotRef.current.historyPagination?.hasOlder === true && i < MAX_SANDBOX_HISTORY_PAGE_INS; i++) {
|
|
4079
|
+
await loadOlderHistory();
|
|
4080
|
+
if (!stillCurrent()) {
|
|
4081
|
+
return void 0;
|
|
4082
|
+
}
|
|
4083
|
+
sandboxId = findSandboxIdInSnapshot(snapshotRef.current, turnId);
|
|
4084
|
+
}
|
|
4085
|
+
return stillCurrent() ? sandboxId : void 0;
|
|
4086
|
+
},
|
|
4087
|
+
[loadOlderHistory, sessionId]
|
|
4088
|
+
);
|
|
4089
|
+
return {
|
|
4090
|
+
messages,
|
|
4091
|
+
isRunning,
|
|
4092
|
+
resumeUnavailable,
|
|
4093
|
+
isLoading,
|
|
4094
|
+
isLoadingOlderHistory,
|
|
4095
|
+
hasOlderHistory,
|
|
4096
|
+
loadOlderHistory,
|
|
4097
|
+
resolveSandboxIdForTurn,
|
|
4098
|
+
retryLoad,
|
|
4099
|
+
sendTurn,
|
|
4100
|
+
cancel,
|
|
4101
|
+
respondToToolApproval,
|
|
4102
|
+
respondToToolResponse,
|
|
4103
|
+
resumeRun,
|
|
4104
|
+
branchFromTurn,
|
|
4105
|
+
resetFromTurn,
|
|
4106
|
+
editFromTurn
|
|
4107
|
+
};
|
|
4108
|
+
}
|
|
4109
|
+
|
|
4110
|
+
// src/useTrueForgeAgentRuntime.ts
|
|
4111
|
+
function createDelegatingThreadListAdapter(adapterRef) {
|
|
4112
|
+
return {
|
|
4113
|
+
list: (params) => adapterRef.current.list(params),
|
|
4114
|
+
initialize: (threadId) => adapterRef.current.initialize(threadId),
|
|
4115
|
+
fetch: (threadId) => adapterRef.current.fetch(threadId),
|
|
4116
|
+
rename: (remoteId, newTitle) => adapterRef.current.rename(remoteId, newTitle),
|
|
4117
|
+
archive: (remoteId) => adapterRef.current.archive(remoteId),
|
|
4118
|
+
unarchive: (remoteId) => adapterRef.current.unarchive(remoteId),
|
|
4119
|
+
delete: (remoteId) => adapterRef.current.delete(remoteId),
|
|
4120
|
+
generateTitle: (remoteId, messages) => adapterRef.current.generateTitle(remoteId, messages)
|
|
4121
|
+
};
|
|
4122
|
+
}
|
|
4123
|
+
function useTrueForgeAgentRuntimeImpl(options, pendingAgentSpecRef) {
|
|
4124
|
+
const { server, agent, adapters, onError, ...sharedOptions } = options;
|
|
4125
|
+
const draftBridgeRef = useRef3(agent.mode === "draft" ? createDraftSessionBridge(server) : null);
|
|
4126
|
+
const draftSessionId = useAuiState2(
|
|
4127
|
+
(state) => agent.mode === "draft" ? state.threadListItem.remoteId ?? void 0 : void 0
|
|
4128
|
+
);
|
|
4129
|
+
const sessionId = useAuiState2((state) => state.threadListItem.remoteId ?? void 0);
|
|
4130
|
+
const isMain = useAuiState2((state) => state.threads.mainThreadId === state.threadListItem.id);
|
|
4131
|
+
const isInitialSession = sessionId != null && sessionId === options.initialSessionId;
|
|
4132
|
+
const draftSpec = useDraftAgentSpec({
|
|
4133
|
+
draftSessionId,
|
|
4134
|
+
draftBridge: draftBridgeRef.current,
|
|
4135
|
+
defaultAgentSpec: agent.mode === "draft" ? agent.defaultAgentSpec : { model: { name: "" } },
|
|
4136
|
+
onAgentSpecChange: agent.mode === "draft" ? agent.onAgentSpecChange : void 0,
|
|
4137
|
+
onError
|
|
4138
|
+
});
|
|
4139
|
+
const takeTurnHeaderTimestampRef = useRef3(draftSpec.takeTurnHeaderTimestamp);
|
|
4140
|
+
takeTurnHeaderTimestampRef.current = draftSpec.takeTurnHeaderTimestamp;
|
|
4141
|
+
const getTurnHeaders = useCallback4(async () => {
|
|
4142
|
+
if (agent.mode !== "draft") {
|
|
4143
|
+
return void 0;
|
|
4144
|
+
}
|
|
4145
|
+
const updatedAt = await takeTurnHeaderTimestampRef.current();
|
|
4146
|
+
if (updatedAt == null) {
|
|
4147
|
+
return void 0;
|
|
4148
|
+
}
|
|
4149
|
+
return { [DRAFT_SESSION_LAST_UPDATED_AT_HEADER]: updatedAt };
|
|
4150
|
+
}, [agent.mode]);
|
|
4151
|
+
const aui = useAui3();
|
|
4152
|
+
const initializeSession = useCallback4(() => aui.threadListItem().initialize(), [aui]);
|
|
4153
|
+
const runtimeAdapters = useRuntimeAdapters();
|
|
4154
|
+
const [, setToolStatuses] = useState3({});
|
|
4155
|
+
const {
|
|
4156
|
+
messages,
|
|
4157
|
+
isRunning,
|
|
4158
|
+
resumeUnavailable,
|
|
4159
|
+
isLoading,
|
|
4160
|
+
isLoadingOlderHistory,
|
|
4161
|
+
hasOlderHistory,
|
|
4162
|
+
loadOlderHistory,
|
|
4163
|
+
sendTurn,
|
|
4164
|
+
cancel,
|
|
4165
|
+
respondToToolApproval,
|
|
4166
|
+
respondToToolResponse,
|
|
4167
|
+
resumeRun,
|
|
4168
|
+
editFromTurn,
|
|
4169
|
+
resetFromTurn,
|
|
4170
|
+
resolveSandboxIdForTurn,
|
|
4171
|
+
retryLoad
|
|
4172
|
+
} = useTrueForgeAgentMessages({
|
|
4173
|
+
server,
|
|
4174
|
+
sessionId,
|
|
4175
|
+
isMain,
|
|
4176
|
+
isInitialSession,
|
|
4177
|
+
onError,
|
|
4178
|
+
initializeSession,
|
|
4179
|
+
...agent.mode === "draft" ? { getTurnHeaders } : {}
|
|
4180
|
+
});
|
|
4181
|
+
if (agent.mode === "draft" && draftSpec.agentSpec != null) {
|
|
4182
|
+
pendingAgentSpecRef.current = draftSpec.agentSpec;
|
|
4183
|
+
}
|
|
4184
|
+
const pendingApprovals = useMemo4(() => collectPendingApprovals(messages), [messages]);
|
|
4185
|
+
const pendingToolResponses = useMemo4(() => collectPendingToolResponses(messages), [messages]);
|
|
4186
|
+
const pendingMcpAuth = useMemo4(() => derivePendingMcpAuth(messages), [messages]);
|
|
4187
|
+
const sandboxId = useMemo4(() => deriveSandboxId(messages), [messages]);
|
|
4188
|
+
const resumeMcpAuth = useMemo4(() => () => sendTurn({ resumeMcpAuth: true }), [sendTurn]);
|
|
4189
|
+
const downloadSandboxFile = useCallback4(
|
|
4190
|
+
async ({ turnId, path }) => {
|
|
4191
|
+
if (server.downloadSandboxFile == null) {
|
|
4192
|
+
throw new Error("Downloading a sandbox file requires AgentChatServer.downloadSandboxFile.");
|
|
4193
|
+
}
|
|
4194
|
+
const turnSandboxId = await resolveSandboxIdForTurn(turnId) ?? sandboxId;
|
|
4195
|
+
return await server.downloadSandboxFile(
|
|
4196
|
+
buildSandboxDownloadRequest({
|
|
4197
|
+
sessionId,
|
|
4198
|
+
turnId,
|
|
4199
|
+
path,
|
|
4200
|
+
...turnSandboxId != null ? { sandboxId: turnSandboxId } : {}
|
|
4201
|
+
})
|
|
4202
|
+
);
|
|
4203
|
+
},
|
|
4204
|
+
[server, sessionId, sandboxId, resolveSandboxIdForTurn]
|
|
4205
|
+
);
|
|
4206
|
+
const draftExtras = useMemo4(() => {
|
|
4207
|
+
if (agent.mode !== "draft") {
|
|
4208
|
+
return null;
|
|
4209
|
+
}
|
|
4210
|
+
return {
|
|
4211
|
+
agentSpec: draftSpec.agentSpec,
|
|
4212
|
+
draftSessionId: draftSpec.draftSessionId,
|
|
4213
|
+
isSpecLoading: draftSpec.isSpecLoading,
|
|
4214
|
+
isSpecSyncing: draftSpec.isSpecSyncing,
|
|
4215
|
+
specError: draftSpec.specError,
|
|
4216
|
+
updateAgentSpec: draftSpec.updateAgentSpec,
|
|
4217
|
+
flushAgentSpec: draftSpec.flushAgentSpec,
|
|
4218
|
+
adoptAgentSpec: draftSpec.adoptAgentSpec
|
|
4219
|
+
};
|
|
4220
|
+
}, [agent.mode, draftSpec]);
|
|
4221
|
+
return useExternalStoreRuntime({
|
|
4222
|
+
...pickExternalStoreSharedOptions(sharedOptions),
|
|
4223
|
+
messages,
|
|
4224
|
+
isRunning,
|
|
4225
|
+
isLoading,
|
|
4226
|
+
extras: trueForgeExtras.provide({
|
|
4227
|
+
pendingApprovals,
|
|
4228
|
+
pendingToolResponses,
|
|
4229
|
+
pendingMcpAuth,
|
|
4230
|
+
resumeUnavailable,
|
|
4231
|
+
sandboxId,
|
|
4232
|
+
respondToToolApproval,
|
|
4233
|
+
respondToToolResponse,
|
|
4234
|
+
resumeMcpAuth,
|
|
4235
|
+
downloadSandboxFile,
|
|
4236
|
+
cancel,
|
|
4237
|
+
// resetFromTurn/branchFromTurn/sendTurn already report via onError.
|
|
4238
|
+
resetFromTurn: (turnId) => resetFromTurn(turnId).catch(() => void 0),
|
|
4239
|
+
reload: retryLoad,
|
|
4240
|
+
hasOlderHistory,
|
|
4241
|
+
isLoadingOlderHistory,
|
|
4242
|
+
loadOlderHistory,
|
|
4243
|
+
draft: draftExtras
|
|
4244
|
+
}),
|
|
4245
|
+
unstable_enableToolInvocations: true,
|
|
4246
|
+
setToolStatuses,
|
|
4247
|
+
adapters: {
|
|
4248
|
+
attachments: adapters?.attachments ?? runtimeAdapters?.attachments,
|
|
4249
|
+
speech: adapters?.speech,
|
|
4250
|
+
dictation: adapters?.dictation,
|
|
4251
|
+
voice: adapters?.voice,
|
|
4252
|
+
feedback: adapters?.feedback
|
|
4253
|
+
},
|
|
4254
|
+
onNew: async (message) => {
|
|
4255
|
+
if (!(message.startRun ?? message.role === "user")) {
|
|
4256
|
+
return;
|
|
4257
|
+
}
|
|
4258
|
+
const resumeMcpAuthFlag = message.runConfig?.custom?.[MCP_AUTH_RESUME_RUN_CUSTOM_KEY] === true;
|
|
4259
|
+
if (resumeMcpAuthFlag) {
|
|
4260
|
+
await sendTurn({ resumeMcpAuth: true });
|
|
4261
|
+
return;
|
|
4262
|
+
}
|
|
4263
|
+
const userMessage = buildUserMessageContent(message);
|
|
4264
|
+
await sendTurn({
|
|
4265
|
+
userMessage,
|
|
4266
|
+
// The composer clears before onNew runs. Restore its text only when
|
|
4267
|
+
// the turn failed before turn.created registered it in the backend.
|
|
4268
|
+
onPreTurnFailure: () => {
|
|
4269
|
+
const text = userMessageContentToText(userMessage);
|
|
4270
|
+
const composer = aui.thread().composer();
|
|
4271
|
+
if (text && !composer.getState().text.trim()) {
|
|
4272
|
+
composer.setText(text);
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
});
|
|
4276
|
+
},
|
|
4277
|
+
onCancel: async () => {
|
|
4278
|
+
await cancel();
|
|
4279
|
+
},
|
|
4280
|
+
onRespondToToolApproval: (response) => {
|
|
4281
|
+
respondToToolApproval(response);
|
|
4282
|
+
return Promise.resolve();
|
|
4283
|
+
},
|
|
4284
|
+
onResume: async () => {
|
|
4285
|
+
await resumeRun();
|
|
4286
|
+
},
|
|
4287
|
+
onEdit: async (message) => {
|
|
4288
|
+
const sourceId = message.sourceId;
|
|
4289
|
+
if (sourceId == null) {
|
|
4290
|
+
throw new Error("Could not resolve edited user message.");
|
|
4291
|
+
}
|
|
4292
|
+
const turnId = parseTurnIdFromMessageId(sourceId);
|
|
4293
|
+
const editedText = extractEditedText(message);
|
|
4294
|
+
await editFromTurn(turnId, editedText);
|
|
4295
|
+
}
|
|
4296
|
+
});
|
|
4297
|
+
}
|
|
4298
|
+
function useTrueForgeAgentRuntime(options) {
|
|
4299
|
+
const resolved = resolveTrueForgeAgentRuntimeOptions(options);
|
|
4300
|
+
const { server, agent } = resolved;
|
|
4301
|
+
const pendingAgentSpecRef = useRef3(
|
|
4302
|
+
agent.mode === "draft" ? agent.defaultAgentSpec : void 0
|
|
4303
|
+
);
|
|
4304
|
+
const listSessionsAgentId = resolved.listSessionsAgentId;
|
|
4305
|
+
const listSessionsCreatedByMe = resolved.listSessionsCreatedByMe;
|
|
4306
|
+
const modeThreadListAdapter = useMemo4(() => {
|
|
4307
|
+
if (agent.mode === "draft") {
|
|
4308
|
+
return createTrueForgeDraftThreadListAdapter({
|
|
4309
|
+
server,
|
|
4310
|
+
defaultAgentSpec: agent.defaultAgentSpec,
|
|
4311
|
+
getAgentSpec: () => pendingAgentSpecRef.current ?? agent.defaultAgentSpec,
|
|
4312
|
+
...listSessionsAgentId == null ? {} : { listSessionsAgentId },
|
|
4313
|
+
...listSessionsCreatedByMe == null ? {} : { listSessionsCreatedByMe }
|
|
4314
|
+
});
|
|
4315
|
+
}
|
|
4316
|
+
return createTrueForgeThreadListAdapter({
|
|
4317
|
+
server,
|
|
4318
|
+
agentName: agent.agentName,
|
|
4319
|
+
...listSessionsAgentId == null ? {} : { listSessionsAgentId },
|
|
4320
|
+
...listSessionsCreatedByMe == null ? {} : { listSessionsCreatedByMe }
|
|
4321
|
+
});
|
|
4322
|
+
}, [agent, listSessionsAgentId, listSessionsCreatedByMe, server]);
|
|
4323
|
+
const modeThreadListAdapterRef = useRef3(modeThreadListAdapter);
|
|
4324
|
+
useEffect3(() => {
|
|
4325
|
+
modeThreadListAdapterRef.current = modeThreadListAdapter;
|
|
4326
|
+
}, [modeThreadListAdapter]);
|
|
4327
|
+
const threadListAdapter = useMemo4(
|
|
4328
|
+
() => createDelegatingThreadListAdapter(modeThreadListAdapterRef),
|
|
4329
|
+
[listSessionsAgentId, listSessionsCreatedByMe, server]
|
|
4330
|
+
);
|
|
4331
|
+
return useRemoteThreadListRuntime({
|
|
4332
|
+
allowNesting: true,
|
|
4333
|
+
adapter: threadListAdapter,
|
|
4334
|
+
initialThreadId: resolved.initialSessionId,
|
|
4335
|
+
threadId: resolved.threadId,
|
|
4336
|
+
onThreadIdChange: resolved.onThreadIdChange,
|
|
4337
|
+
runtimeHook: () => useTrueForgeAgentRuntimeImpl(resolved, pendingAgentSpecRef)
|
|
4338
|
+
});
|
|
4339
|
+
}
|
|
4340
|
+
export {
|
|
4341
|
+
ROOT_THREAD_ID,
|
|
4342
|
+
TOOL_RESPONSE_THREAD_ID_CUSTOM_KEY,
|
|
4343
|
+
buildEditedUserMessageContent,
|
|
4344
|
+
buildTurnAssistantContent,
|
|
4345
|
+
buildUserMessageContent,
|
|
4346
|
+
collectApprovalInputs,
|
|
4347
|
+
collectRequiredActionInputs,
|
|
4348
|
+
collectResponseInputs,
|
|
4349
|
+
convertTurnsToThreadMessages,
|
|
4350
|
+
createDraftSessionBridge,
|
|
4351
|
+
createTrueForgeDraftThreadListAdapter,
|
|
4352
|
+
createTrueForgeOwnedSessionsThreadListAdapter,
|
|
4353
|
+
createTrueForgeThreadListAdapter,
|
|
4354
|
+
draftSessionTitle,
|
|
4355
|
+
findPausedAssistantMessage,
|
|
4356
|
+
getSession,
|
|
4357
|
+
getTrueForgeExtras,
|
|
4358
|
+
getTurnMessageContent,
|
|
4359
|
+
isEventDelta,
|
|
4360
|
+
mergeAgentSpec,
|
|
4361
|
+
mergeEventDelta,
|
|
4362
|
+
messageHasPendingApprovals,
|
|
4363
|
+
messageHasPendingRequiredActions,
|
|
4364
|
+
messageHasPendingResponses,
|
|
4365
|
+
parseTurnIdFromMessageId,
|
|
4366
|
+
repositoryItemsFromMessages,
|
|
4367
|
+
toTrueForgeApprovalInputs,
|
|
4368
|
+
trueForgeAttachmentAdapter,
|
|
4369
|
+
trueForgeExtras,
|
|
4370
|
+
tryGetTrueForgeExtras,
|
|
4371
|
+
useTrueForgeAdoptAgentSpec,
|
|
4372
|
+
useTrueForgeAgentRuntime,
|
|
4373
|
+
useTrueForgeAgentSpec,
|
|
4374
|
+
useTrueForgeApprovals,
|
|
4375
|
+
useTrueForgeCancel,
|
|
4376
|
+
useTrueForgeDownloadSandboxFile,
|
|
4377
|
+
useTrueForgeFlushAgentSpec,
|
|
4378
|
+
useTrueForgeHistoryPagination,
|
|
4379
|
+
useTrueForgeMcpAuth,
|
|
4380
|
+
useTrueForgeReload,
|
|
4381
|
+
useTrueForgeResetFromTurn,
|
|
4382
|
+
useTrueForgeRespondToToolApproval,
|
|
4383
|
+
useTrueForgeRespondToToolResponse,
|
|
4384
|
+
useTrueForgeResumeMcpAuth,
|
|
4385
|
+
useTrueForgeResumeUnavailable,
|
|
4386
|
+
useTrueForgeSandboxId,
|
|
4387
|
+
useTrueForgeToolResponses,
|
|
4388
|
+
useTrueForgeTurnId,
|
|
4389
|
+
useTrueForgeUpdateAgentSpec
|
|
4390
|
+
};
|
|
4391
|
+
//# sourceMappingURL=index.js.map
|