@agents24/react 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/compatibility.json +10 -10
- package/dist/index.js +863 -166
- package/dist/index.js.map +1 -1
- package/dist/initial-thread.d.ts +11 -0
- package/dist/model.d.ts +132 -37
- package/dist/runtime-event-buffer.d.ts +9 -0
- package/dist/state.d.ts +34 -1
- package/dist/use-agent-chat.d.ts +10 -3
- package/dist/use-agent-resources.d.ts +13 -6
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -11,8 +11,34 @@ function isAbortLike(error) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// src/model.ts
|
|
14
|
+
import {
|
|
15
|
+
compressionFromContextWindow,
|
|
16
|
+
mergeContextWindowUpdate,
|
|
17
|
+
normalizeContextCompression
|
|
18
|
+
} from "@agents24/client/protocol";
|
|
19
|
+
function chatAttachmentIds(attachments) {
|
|
20
|
+
return (attachments || []).flatMap((attachment) => typeof attachment.id === "string" && attachment.id.trim() ? [attachment.id.trim()] : []);
|
|
21
|
+
}
|
|
22
|
+
var chatNodeSkipReason = (value) => {
|
|
23
|
+
const normalized = text(value);
|
|
24
|
+
return normalized === "missing_required_input" || normalized === "upstream_input_unavailable" || normalized === "empty_optional_source" ? normalized : null;
|
|
25
|
+
};
|
|
26
|
+
function getThreadActivity(thread) {
|
|
27
|
+
const activeRun = thread.active_run || null;
|
|
28
|
+
const status = String(activeRun?.status || thread.last_run_status || "").trim().toLowerCase() || null;
|
|
29
|
+
return {
|
|
30
|
+
status,
|
|
31
|
+
runId: activeRun?.run_id || thread.last_run_id || null,
|
|
32
|
+
isSpinning: Boolean(activeRun),
|
|
33
|
+
isPaused: status === "paused",
|
|
34
|
+
isFailed: status === "failed",
|
|
35
|
+
isCompleted: status === "completed",
|
|
36
|
+
isCancelled: status === "cancelled" || status === "canceled"
|
|
37
|
+
};
|
|
38
|
+
}
|
|
14
39
|
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15
40
|
var text = (value) => typeof value === "string" && value.trim() ? value : null;
|
|
41
|
+
var finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
16
42
|
var statusToToolState = (value) => {
|
|
17
43
|
const normalized = String(value || "").toLowerCase();
|
|
18
44
|
if (normalized === "failed" || normalized === "error") return "output-error";
|
|
@@ -20,42 +46,167 @@ var statusToToolState = (value) => {
|
|
|
20
46
|
if (["complete", "completed", "done", "success"].includes(normalized)) return "output-available";
|
|
21
47
|
return normalized === "streaming" ? "input-streaming" : "input-available";
|
|
22
48
|
};
|
|
49
|
+
function failureView(value) {
|
|
50
|
+
const candidate = record(value);
|
|
51
|
+
return candidate?.schema_version === "agents24.failure.v1" && text(candidate.failure_id) && text(candidate.code) && text(candidate.message) && typeof candidate.retryable === "boolean" && record(candidate.details) ? candidate : null;
|
|
52
|
+
}
|
|
23
53
|
var allowedActions = {
|
|
24
54
|
tool_review: ["approve", "reject"],
|
|
25
55
|
mcp_auth: ["connect", "skip"],
|
|
26
56
|
user_approval: ["approve", "reject"],
|
|
27
|
-
app_data_permission: ["approve", "reject"]
|
|
57
|
+
app_data_permission: ["approve", "reject"],
|
|
58
|
+
user_input: ["respond"]
|
|
28
59
|
};
|
|
29
|
-
function
|
|
60
|
+
function hitlQuestions(presentation) {
|
|
61
|
+
return (Array.isArray(presentation.questions) ? presentation.questions : []).flatMap((value) => {
|
|
62
|
+
const question = record(value);
|
|
63
|
+
if (!question || !Array.isArray(question.options)) return [];
|
|
64
|
+
const options = question.options.flatMap((rawOption) => {
|
|
65
|
+
const option = record(rawOption);
|
|
66
|
+
return option && text(option.id) && text(option.label) ? [{ id: String(option.id), label: String(option.label) }] : [];
|
|
67
|
+
});
|
|
68
|
+
return text(question.id) && text(question.header) && text(question.question) && options.length >= 2 ? [{
|
|
69
|
+
id: String(question.id),
|
|
70
|
+
header: String(question.header),
|
|
71
|
+
question: String(question.question),
|
|
72
|
+
options,
|
|
73
|
+
multiSelect: question.multi_select === true
|
|
74
|
+
}] : [];
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function hitlResolution(value) {
|
|
78
|
+
const resolution = record(value);
|
|
79
|
+
if (!resolution) return null;
|
|
80
|
+
const answers = (Array.isArray(resolution.answers) ? resolution.answers : []).flatMap((value2) => {
|
|
81
|
+
const answer = record(value2);
|
|
82
|
+
if (!answer || !text(answer.question_id) || !Array.isArray(answer.selected_option_ids)) return [];
|
|
83
|
+
return [{
|
|
84
|
+
questionId: String(answer.question_id),
|
|
85
|
+
selectedOptionIds: answer.selected_option_ids.map(String),
|
|
86
|
+
...text(answer.custom_text) ? { customText: String(answer.custom_text) } : {}
|
|
87
|
+
}];
|
|
88
|
+
});
|
|
89
|
+
return { ...resolution, ...answers.length ? { answers } : {} };
|
|
90
|
+
}
|
|
91
|
+
function partMetadata(block, id) {
|
|
92
|
+
return {
|
|
93
|
+
id,
|
|
94
|
+
runId: text(block.runId) || text(block.run_id),
|
|
95
|
+
sequence: finiteNumber(block.seq),
|
|
96
|
+
timestamp: text(block.ts),
|
|
97
|
+
raw: block
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function toolPresentation(tool) {
|
|
101
|
+
const nested = record(tool.presentation) || {};
|
|
102
|
+
const childRunValue = record(tool.childRun) || record(tool.child_run);
|
|
103
|
+
const childRun = childRunValue && (text(childRunValue.runId || childRunValue.run_id) || text(childRunValue.agentName || childRunValue.agent_name) || text(childRunValue.status)) ? {
|
|
104
|
+
runId: text(childRunValue.runId) || text(childRunValue.run_id),
|
|
105
|
+
agentId: text(childRunValue.agentId) || text(childRunValue.agent_id),
|
|
106
|
+
agentName: text(childRunValue.agentName) || text(childRunValue.agent_name),
|
|
107
|
+
status: text(childRunValue.status),
|
|
108
|
+
error: text(childRunValue.error)
|
|
109
|
+
} : null;
|
|
110
|
+
const childTools = (Array.isArray(tool.childTools) ? tool.childTools : Array.isArray(tool.child_tools) ? tool.child_tools : []).flatMap((value) => {
|
|
111
|
+
const child = record(value);
|
|
112
|
+
if (!child || !text(child.id) || !text(child.title)) return [];
|
|
113
|
+
return [{
|
|
114
|
+
id: String(child.id),
|
|
115
|
+
runId: text(child.runId) || text(child.run_id),
|
|
116
|
+
toolCallId: text(child.toolCallId) || text(child.tool_call_id),
|
|
117
|
+
toolName: String(child.toolName || child.tool_name || child.title),
|
|
118
|
+
title: String(child.title),
|
|
119
|
+
status: String(child.status || ""),
|
|
120
|
+
detail: text(child.detail),
|
|
121
|
+
sequence: finiteNumber(child.seq),
|
|
122
|
+
timestamp: text(child.ts)
|
|
123
|
+
}];
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
...nested,
|
|
127
|
+
title: text(tool.title) || text(nested.title),
|
|
128
|
+
activity: text(tool.activity) || text(nested.activity),
|
|
129
|
+
detail: text(tool.detail) || text(nested.detail),
|
|
130
|
+
category: text(tool.category) || text(nested.category),
|
|
131
|
+
groupKey: text(tool.groupKey) || text(tool.group_key) || text(nested.groupKey) || text(nested.group_key),
|
|
132
|
+
groupLabel: text(tool.groupLabel) || text(tool.group_label) || text(nested.groupLabel) || text(nested.group_label),
|
|
133
|
+
path: text(tool.path),
|
|
134
|
+
threadId: text(tool.threadId) || text(tool.thread_id),
|
|
135
|
+
isExploration: tool.isExploration === true || tool.is_exploration === true,
|
|
136
|
+
childRun,
|
|
137
|
+
childTools,
|
|
138
|
+
activeChildToolId: text(tool.activeChildToolId) || text(tool.active_child_tool_id)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function partsFromResponseBlocks(blocks, fallbackText = "", fallbackPartId = "assistant-text") {
|
|
30
142
|
const input = Array.isArray(blocks) ? blocks : [];
|
|
31
143
|
const parts = input.flatMap((item, index) => {
|
|
32
144
|
const block = record(item);
|
|
33
145
|
if (!block) return [];
|
|
34
146
|
const kind = String(block.kind || "data");
|
|
35
147
|
const id = String(block.id || `${kind}-${index}`);
|
|
148
|
+
const metadata = partMetadata(block, id);
|
|
36
149
|
if (kind === "assistant_text") {
|
|
37
150
|
const value = text(block.text);
|
|
38
|
-
return value ? [{
|
|
151
|
+
return value ? [{ ...metadata, type: "text", kind: "text", text: value }] : [];
|
|
152
|
+
}
|
|
153
|
+
if (kind === "context_compression") {
|
|
154
|
+
const compression = normalizeContextCompression(block.compression);
|
|
155
|
+
if (!compression?.active) return [];
|
|
156
|
+
const status = compression.status === "started" || compression.status === "completed" || compression.status === "failed" ? compression.status : block.status === "complete" ? "completed" : block.status === "error" ? "failed" : "started";
|
|
157
|
+
return [{
|
|
158
|
+
...metadata,
|
|
159
|
+
type: "context-compression",
|
|
160
|
+
kind: "context-compression",
|
|
161
|
+
status,
|
|
162
|
+
compression: { ...compression, status }
|
|
163
|
+
}];
|
|
164
|
+
}
|
|
165
|
+
if (kind === "node_activity") {
|
|
166
|
+
const node = record(block.node);
|
|
167
|
+
const activityId = text(node?.activityId) || text(node?.activity_id) || id;
|
|
168
|
+
const statusValue = String(node?.status || "").toLowerCase();
|
|
169
|
+
const status = ["completed", "failed", "cancelled", "skipped"].includes(statusValue) ? statusValue : "running";
|
|
170
|
+
const label = text(node?.label) || "Working";
|
|
171
|
+
const nodeKind = text(node?.kind) || "node";
|
|
172
|
+
return [{
|
|
173
|
+
...metadata,
|
|
174
|
+
type: "node-activity",
|
|
175
|
+
kind: "node-activity",
|
|
176
|
+
activityId,
|
|
177
|
+
traceStepId: text(node?.traceStepId) || text(node?.trace_step_id) || null,
|
|
178
|
+
label,
|
|
179
|
+
nodeKind,
|
|
180
|
+
status,
|
|
181
|
+
defaultChatVisible: node?.defaultChatVisible === true,
|
|
182
|
+
reason: chatNodeSkipReason(node?.reason),
|
|
183
|
+
failure: failureView(node?.failure)
|
|
184
|
+
}];
|
|
39
185
|
}
|
|
40
186
|
if (kind === "tool" || kind === "tool_call" || kind === "tool_result") {
|
|
41
187
|
const tool = record(block.tool) || block;
|
|
42
|
-
const toolName = String(tool.action || tool.title || "tool");
|
|
188
|
+
const toolName = String(tool.toolName || tool.tool_name || tool.action || tool.title || "tool");
|
|
43
189
|
return [{
|
|
44
|
-
|
|
190
|
+
...metadata,
|
|
45
191
|
type: `tool-${toolName}`,
|
|
46
192
|
kind: "tool",
|
|
47
193
|
toolName,
|
|
48
|
-
|
|
194
|
+
toolKey: text(tool.toolKey) || text(tool.tool_key),
|
|
195
|
+
action: text(tool.action),
|
|
196
|
+
displayName: text(tool.displayName) || text(tool.display_name),
|
|
197
|
+
summary: text(tool.summary),
|
|
198
|
+
toolCallId: text(tool.toolCallId) || text(tool.tool_call_id),
|
|
199
|
+
sourceKind: text(tool.sourceKind) || text(tool.source_kind),
|
|
49
200
|
state: statusToToolState(block.status),
|
|
50
201
|
input: tool.input,
|
|
51
202
|
output: tool.output,
|
|
52
203
|
errorText: text(tool.error || block.error),
|
|
53
|
-
|
|
54
|
-
|
|
204
|
+
failure: failureView(tool.failure),
|
|
205
|
+
presentation: toolPresentation(tool)
|
|
55
206
|
}];
|
|
56
207
|
}
|
|
57
|
-
if (kind === "reasoning_note") return [{
|
|
58
|
-
if (kind === "ui_blocks") return [{
|
|
208
|
+
if (kind === "reasoning_note") return [{ ...metadata, type: "reasoning", kind: "reasoning", label: text(block.label), text: text(block.description), status: text(block.status) }];
|
|
209
|
+
if (kind === "ui_blocks") return [{ ...metadata, type: "ui-blocks", kind: "ui-blocks", state: statusToToolState(block.status), toolCallId: text(block.toolCallId), contractVersion: text(block.contractVersion), bundle: record(block.bundle), errorText: text(block.error) }];
|
|
59
210
|
if (kind === "hitl_request") {
|
|
60
211
|
const hitl = record(block.hitl) || {};
|
|
61
212
|
const hitlKind = String(block.hitlKind || hitl.kind || "");
|
|
@@ -64,7 +215,7 @@ function partsFromResponseBlocks(blocks, fallbackText = "") {
|
|
|
64
215
|
const expected = allowedActions[hitlKind];
|
|
65
216
|
if (actions.length !== expected.length || actions.some((action, actionIndex) => action !== expected[actionIndex])) return [];
|
|
66
217
|
return [{
|
|
67
|
-
|
|
218
|
+
...metadata,
|
|
68
219
|
type: "hitl",
|
|
69
220
|
kind: "hitl",
|
|
70
221
|
interruptId: String(block.interruptId || hitl.interrupt_id || ""),
|
|
@@ -73,63 +224,93 @@ function partsFromResponseBlocks(blocks, fallbackText = "") {
|
|
|
73
224
|
allowedActions: actions,
|
|
74
225
|
status: String(block.status || "pending"),
|
|
75
226
|
presentation: record(hitl.presentation) || hitl,
|
|
76
|
-
|
|
77
|
-
|
|
227
|
+
questions: hitlQuestions(record(hitl.presentation) || {}),
|
|
228
|
+
resolution: hitlResolution(block.resolution)
|
|
78
229
|
}];
|
|
79
230
|
}
|
|
80
|
-
if (kind === "error")
|
|
81
|
-
|
|
82
|
-
|
|
231
|
+
if (kind === "error") {
|
|
232
|
+
const failure = failureView(block.failure) || failureView(block.error);
|
|
233
|
+
return [{
|
|
234
|
+
...metadata,
|
|
235
|
+
type: "error",
|
|
236
|
+
kind: "error",
|
|
237
|
+
errorText: failure?.message || String(block.text || "The run failed."),
|
|
238
|
+
failure
|
|
239
|
+
}];
|
|
240
|
+
}
|
|
241
|
+
if (kind === "source") return [{ ...metadata, type: "source", kind: "source", title: String(block.title || "Source"), url: text(block.url), description: text(block.description) }];
|
|
242
|
+
if (kind === "citation") return [{ ...metadata, type: "citation", kind: "citation", sourceId: text(block.sourceId), label: text(block.label), url: text(block.url) }];
|
|
83
243
|
if (kind === "attachment") {
|
|
84
244
|
const attachment = record(block.attachment) || {};
|
|
85
|
-
return [{
|
|
245
|
+
return [{ ...metadata, type: "attachment", kind: "attachment", attachment: { ...attachment, id: text(attachment.id) || void 0, filename: text(attachment.name) || "Attachment", mediaType: text(attachment.mime_type) || void 0 } }];
|
|
86
246
|
}
|
|
87
|
-
return [{
|
|
247
|
+
return [{ ...metadata, type: "data", kind: "data", name: kind, data: block }];
|
|
88
248
|
});
|
|
89
249
|
if (!parts.some((part) => part.kind === "text") && fallbackText.trim()) {
|
|
90
|
-
parts.push({ id:
|
|
250
|
+
parts.push({ id: fallbackPartId, type: "text", kind: "text", text: fallbackText.trim() });
|
|
91
251
|
}
|
|
92
252
|
return parts;
|
|
93
253
|
}
|
|
94
254
|
function assistantProjectionFromEvent(event) {
|
|
95
255
|
const payload = record(event.payload) || {};
|
|
96
256
|
const content = text(payload.assistant_output_text) || "";
|
|
97
|
-
const fromBlocks = partsFromResponseBlocks(
|
|
98
|
-
|
|
99
|
-
if (event.event === "reasoning.update") return {
|
|
257
|
+
const fromBlocks = partsFromResponseBlocks(
|
|
258
|
+
payload.response_blocks,
|
|
100
259
|
content,
|
|
101
|
-
|
|
260
|
+
`${event.run_id}:assistant-text`
|
|
261
|
+
);
|
|
262
|
+
if (fromBlocks.length) return {
|
|
263
|
+
content: content || fromBlocks.filter((part) => part.kind === "text").map((part) => part.text).join(""),
|
|
264
|
+
parts: fromBlocks
|
|
102
265
|
};
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
266
|
+
const fromPatch = partsFromResponseBlocks(
|
|
267
|
+
payload.response_block === void 0 ? void 0 : [payload.response_block]
|
|
268
|
+
);
|
|
269
|
+
if (fromPatch.length) return { content, parts: fromPatch };
|
|
270
|
+
return { content, parts: [] };
|
|
271
|
+
}
|
|
272
|
+
function modelTurnPartFromEvent(event) {
|
|
273
|
+
const eventName = String(event.event || "");
|
|
274
|
+
const payload = record(event.payload) || {};
|
|
275
|
+
const source = eventName === "run.snapshot" ? record(payload.active_model_turn) : payload;
|
|
276
|
+
if (!source || eventName !== "model.turn.started" && eventName !== "run.snapshot" || source.status !== "running" || !text(source.model_turn_id)) return null;
|
|
277
|
+
const modelTurnId = String(source.model_turn_id);
|
|
278
|
+
const iteration = finiteNumber(source.iteration);
|
|
279
|
+
return {
|
|
280
|
+
id: `model-turn:${modelTurnId}`,
|
|
281
|
+
type: "model-turn",
|
|
282
|
+
kind: "model-turn",
|
|
283
|
+
modelTurnId,
|
|
284
|
+
turnKind: source.kind === "agent" ? "agent" : "model",
|
|
285
|
+
status: "running",
|
|
286
|
+
runId: event.run_id || null,
|
|
287
|
+
sequence: finiteNumber(event.seq),
|
|
288
|
+
timestamp: text(event.ts),
|
|
289
|
+
...iteration === null ? {} : { iteration },
|
|
290
|
+
raw: source
|
|
106
291
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
292
|
+
}
|
|
293
|
+
function contextCompressionPartFromEvent(event, existingParts = []) {
|
|
294
|
+
if (String(event.event || "") !== "context.compression.updated") return null;
|
|
295
|
+
const payload = record(event.payload) || {};
|
|
296
|
+
const compression = normalizeContextCompression(payload.context_compression);
|
|
297
|
+
if (!compression?.active) return null;
|
|
298
|
+
const status = compression.status === "completed" || compression.status === "failed" ? compression.status : "started";
|
|
299
|
+
const activePart = [...existingParts].reverse().find(
|
|
300
|
+
(part) => part.kind === "context-compression" && part.status === "started"
|
|
301
|
+
);
|
|
302
|
+
const sequence = finiteNumber(event.seq);
|
|
303
|
+
const id = status === "started" ? `context-compression:${event.run_id || "run"}:${sequence ?? "active"}` : activePart?.id || `context-compression:${event.run_id || "run"}:${sequence ?? "terminal"}`;
|
|
304
|
+
return {
|
|
305
|
+
id,
|
|
306
|
+
type: "context-compression",
|
|
307
|
+
kind: "context-compression",
|
|
308
|
+
runId: event.run_id || null,
|
|
309
|
+
sequence,
|
|
310
|
+
timestamp: text(event.ts),
|
|
311
|
+
status,
|
|
312
|
+
compression
|
|
110
313
|
};
|
|
111
|
-
if ((event.event === "source.added" || event.event === "citation.added") && record(payload.source)) {
|
|
112
|
-
return { content, parts: partsFromResponseBlocks([payload.source]) };
|
|
113
|
-
}
|
|
114
|
-
if (event.event === "tool.started") {
|
|
115
|
-
const toolName = String(payload.display_name || "Tool");
|
|
116
|
-
return {
|
|
117
|
-
content,
|
|
118
|
-
parts: [{ id: String(payload.tool_call_id || `tool-${event.seq}`), type: `tool-${toolName}`, kind: "tool", toolName, toolCallId: text(payload.tool_call_id), state: "input-available", presentation: { title: toolName }, raw: payload }]
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
if (event.event === "hitl.requested") {
|
|
122
|
-
const hitl = record(payload.hitl) || {};
|
|
123
|
-
const kind = String(hitl.kind || "");
|
|
124
|
-
const actions = Array.isArray(hitl.allowed_actions) ? hitl.allowed_actions.map(String) : [];
|
|
125
|
-
if (kind in allowedActions && actions.length === allowedActions[kind].length && actions.every((action, index) => action === allowedActions[kind][index])) {
|
|
126
|
-
return {
|
|
127
|
-
content,
|
|
128
|
-
parts: [{ id: `hitl-${event.seq}`, type: "hitl", kind: "hitl", interruptId: String(hitl.interrupt_id || ""), hitlKind: kind, message: String(hitl.message || ""), allowedActions: actions, status: "pending", presentation: record(hitl.presentation) || hitl, raw: payload }]
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return { content, parts: [] };
|
|
133
314
|
}
|
|
134
315
|
function threadDetailToMessages(detail) {
|
|
135
316
|
return (detail.turns || []).flatMap((item, index) => {
|
|
@@ -138,7 +319,25 @@ function threadDetailToMessages(detail) {
|
|
|
138
319
|
const assistantText = text(turn.assistant_output_text) || "";
|
|
139
320
|
const runId = text(turn.run_id);
|
|
140
321
|
const createdAt = text(turn.created_at) || (/* @__PURE__ */ new Date(0)).toISOString();
|
|
141
|
-
const
|
|
322
|
+
const rawFeedback = record(turn.feedback);
|
|
323
|
+
const feedback = rawFeedback && (rawFeedback.rating === "like" || rawFeedback.rating === "dislike") && text(rawFeedback.created_at) && text(rawFeedback.updated_at) ? {
|
|
324
|
+
rating: rawFeedback.rating === "like" ? "like" : "dislike",
|
|
325
|
+
reason: [
|
|
326
|
+
"incorrect",
|
|
327
|
+
"irrelevant",
|
|
328
|
+
"incomplete",
|
|
329
|
+
"unsafe",
|
|
330
|
+
"citation_issue",
|
|
331
|
+
"other"
|
|
332
|
+
].includes(String(rawFeedback.reason || "")) ? rawFeedback.reason : null,
|
|
333
|
+
comment: text(rawFeedback.comment),
|
|
334
|
+
created_at: String(rawFeedback.created_at),
|
|
335
|
+
updated_at: String(rawFeedback.updated_at)
|
|
336
|
+
} : null;
|
|
337
|
+
const attachments = Array.isArray(turn.attachments) ? turn.attachments.flatMap((attachment) => {
|
|
338
|
+
const value = record(attachment);
|
|
339
|
+
return value ? [attachmentResultToChatAttachment(value)] : [];
|
|
340
|
+
}) : [];
|
|
142
341
|
const output = [];
|
|
143
342
|
if (userText || attachments.length) output.push({
|
|
144
343
|
id: String(turn.id || `${runId || index}-user`),
|
|
@@ -149,21 +348,45 @@ function threadDetailToMessages(detail) {
|
|
|
149
348
|
attachments,
|
|
150
349
|
messageIndex: typeof turn.turn_index === "number" ? turn.turn_index : index
|
|
151
350
|
});
|
|
152
|
-
if (assistantText || Array.isArray(turn.response_blocks))
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
351
|
+
if (assistantText || Array.isArray(turn.response_blocks)) {
|
|
352
|
+
const assistantParts = partsFromResponseBlocks(
|
|
353
|
+
turn.response_blocks,
|
|
354
|
+
assistantText,
|
|
355
|
+
`${runId || turn.id || index}-assistant-text`
|
|
356
|
+
);
|
|
357
|
+
output.push({
|
|
358
|
+
id: `${runId || turn.id || index}-assistant`,
|
|
359
|
+
role: "assistant",
|
|
360
|
+
runId,
|
|
361
|
+
content: assistantText,
|
|
362
|
+
createdAt: text(turn.completed_at) || createdAt,
|
|
363
|
+
isFinal: !["queued", "running", "paused", "cancelling"].includes(String(turn.status || "").toLowerCase()),
|
|
364
|
+
feedback,
|
|
365
|
+
parts: assistantParts,
|
|
366
|
+
messageIndex: typeof turn.turn_index === "number" ? turn.turn_index : index
|
|
367
|
+
});
|
|
368
|
+
}
|
|
162
369
|
return output;
|
|
163
370
|
});
|
|
164
371
|
}
|
|
372
|
+
function latestThreadContext(detail) {
|
|
373
|
+
let contextWindow = null;
|
|
374
|
+
for (const turn of detail.turns || []) {
|
|
375
|
+
contextWindow = mergeContextWindowUpdate(contextWindow, turn.context_window);
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
contextWindow,
|
|
379
|
+
contextCompression: compressionFromContextWindow(contextWindow)
|
|
380
|
+
};
|
|
381
|
+
}
|
|
165
382
|
function attachmentResultToChatAttachment(result) {
|
|
166
|
-
return {
|
|
383
|
+
return {
|
|
384
|
+
...result,
|
|
385
|
+
id: result.id,
|
|
386
|
+
filename: result.filename,
|
|
387
|
+
mediaType: result.mime_type,
|
|
388
|
+
status: result.processing_status
|
|
389
|
+
};
|
|
167
390
|
}
|
|
168
391
|
|
|
169
392
|
// src/provider.tsx
|
|
@@ -193,6 +416,11 @@ function useAgentClient() {
|
|
|
193
416
|
}
|
|
194
417
|
|
|
195
418
|
// src/state.ts
|
|
419
|
+
import {
|
|
420
|
+
compressionFromContextWindow as compressionFromContextWindow2,
|
|
421
|
+
mergeContextWindowUpdate as mergeContextWindowUpdate2,
|
|
422
|
+
normalizeContextCompression as normalizeContextCompression2
|
|
423
|
+
} from "@agents24/client/protocol";
|
|
196
424
|
var initialAgentChatState = {
|
|
197
425
|
threads: [],
|
|
198
426
|
activeThreadId: null,
|
|
@@ -200,9 +428,62 @@ var initialAgentChatState = {
|
|
|
200
428
|
activeRunId: null,
|
|
201
429
|
cursor: null,
|
|
202
430
|
streamingMessageId: null,
|
|
431
|
+
contextWindow: null,
|
|
432
|
+
contextCompression: null,
|
|
433
|
+
contextCompressionRevision: 0,
|
|
203
434
|
runState: "idle",
|
|
204
|
-
error: null
|
|
435
|
+
error: null,
|
|
436
|
+
operationError: null,
|
|
437
|
+
backgroundError: null
|
|
205
438
|
};
|
|
439
|
+
function captureAgentChatThreadSelection(state) {
|
|
440
|
+
return {
|
|
441
|
+
activeRunId: state.activeRunId,
|
|
442
|
+
activeThreadId: state.activeThreadId,
|
|
443
|
+
cursor: state.cursor,
|
|
444
|
+
messages: state.messages,
|
|
445
|
+
runState: state.runState,
|
|
446
|
+
streamingMessageId: state.streamingMessageId,
|
|
447
|
+
contextWindow: state.contextWindow,
|
|
448
|
+
contextCompression: state.contextCompression,
|
|
449
|
+
contextCompressionRevision: state.contextCompressionRevision
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function restoreAgentChatThreadSelection(state, snapshot) {
|
|
453
|
+
return {
|
|
454
|
+
...state,
|
|
455
|
+
...snapshot,
|
|
456
|
+
runState: snapshot.activeRunId ? "reconnecting" : snapshot.runState
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function withAgentChatOperationError(state, operation, cause) {
|
|
460
|
+
return {
|
|
461
|
+
...state,
|
|
462
|
+
operationError: { kind: "operation", operation, cause }
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function clearAgentChatOperationError(state, operation) {
|
|
466
|
+
if (!state.operationError || operation && state.operationError.operation !== operation) {
|
|
467
|
+
return state;
|
|
468
|
+
}
|
|
469
|
+
return { ...state, operationError: null };
|
|
470
|
+
}
|
|
471
|
+
function withAgentChatBackgroundError(state, cause) {
|
|
472
|
+
return {
|
|
473
|
+
...state,
|
|
474
|
+
backgroundError: { kind: "background", source: "thread_events", cause }
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function clearAgentChatBackgroundError(state) {
|
|
478
|
+
return state.backgroundError ? { ...state, backgroundError: null } : state;
|
|
479
|
+
}
|
|
480
|
+
function reconcileRefreshedThreads(current, incoming, activeThreadId) {
|
|
481
|
+
if (!activeThreadId || incoming.some((thread) => thread.id === activeThreadId)) {
|
|
482
|
+
return incoming;
|
|
483
|
+
}
|
|
484
|
+
const active = current.find((thread) => thread.id === activeThreadId);
|
|
485
|
+
return active?.isHydrated ? [active, ...incoming] : incoming;
|
|
486
|
+
}
|
|
206
487
|
function appendStableStreamingTurn(messages, userMessage, assistantMessage) {
|
|
207
488
|
const next = [...messages];
|
|
208
489
|
if (!next.some((message) => message.id === userMessage.id)) next.push(userMessage);
|
|
@@ -222,6 +503,43 @@ function upsertStableAssistantMessage(messages, input) {
|
|
|
222
503
|
else next[index] = input.update(next[index]);
|
|
223
504
|
return next;
|
|
224
505
|
}
|
|
506
|
+
function appendAssistantDelta(parts, delta, fallbackPartId) {
|
|
507
|
+
const next = [...parts];
|
|
508
|
+
const trailing = next[next.length - 1];
|
|
509
|
+
if (trailing?.kind === "text") {
|
|
510
|
+
next[next.length - 1] = { ...trailing, text: `${trailing.text}${delta}` };
|
|
511
|
+
return next;
|
|
512
|
+
}
|
|
513
|
+
next.push({ id: fallbackPartId, type: "text", kind: "text", text: delta });
|
|
514
|
+
return next;
|
|
515
|
+
}
|
|
516
|
+
function upsertResponseBlockParts(parts, incoming) {
|
|
517
|
+
const next = [...parts];
|
|
518
|
+
for (const part of incoming) {
|
|
519
|
+
const index = next.findIndex((current) => current.id === part.id);
|
|
520
|
+
if (index === -1) next.push(part);
|
|
521
|
+
else next[index] = part;
|
|
522
|
+
}
|
|
523
|
+
return next;
|
|
524
|
+
}
|
|
525
|
+
function reconcileModelTurnParts(parts, event) {
|
|
526
|
+
const eventName = String(event.event || "");
|
|
527
|
+
const payload = event.payload || {};
|
|
528
|
+
const terminal = eventName === "run.completed" || eventName === "run.failed" || eventName === "run.cancelled";
|
|
529
|
+
const modelTurnId = typeof payload.model_turn_id === "string" ? payload.model_turn_id : null;
|
|
530
|
+
let next = parts;
|
|
531
|
+
if (eventName === "run.snapshot" || terminal) {
|
|
532
|
+
next = next.filter((part) => part.kind !== "model-turn");
|
|
533
|
+
} else if (eventName === "assistant.delta" && typeof payload.content === "string" && payload.content) {
|
|
534
|
+
next = next.filter((part) => part.kind !== "model-turn" || part.turnKind !== "agent");
|
|
535
|
+
} else if (eventName.startsWith("tool.") || eventName === "node.status.updated") {
|
|
536
|
+
next = next.filter((part) => part.kind !== "model-turn" || part.turnKind !== "agent");
|
|
537
|
+
} else if ((eventName === "model.turn.failed" || eventName === "model.turn.completed" && payload.outcome !== "tool_calls") && modelTurnId) {
|
|
538
|
+
next = next.filter((part) => part.kind !== "model-turn" || part.modelTurnId !== modelTurnId);
|
|
539
|
+
}
|
|
540
|
+
const active = modelTurnPartFromEvent(event);
|
|
541
|
+
return active ? upsertResponseBlockParts(next, [active]) : next;
|
|
542
|
+
}
|
|
225
543
|
function applyRuntimeEvent(state, event) {
|
|
226
544
|
const projection = assistantProjectionFromEvent(event);
|
|
227
545
|
const eventName = String(event.event || "");
|
|
@@ -229,41 +547,84 @@ function applyRuntimeEvent(state, event) {
|
|
|
229
547
|
const messageId = state.streamingMessageId || `${runId || "run"}-assistant`;
|
|
230
548
|
const terminal = eventName === "run.completed" || eventName === "run.failed" || eventName === "run.cancelled";
|
|
231
549
|
const paused = eventName === "run.paused" || projection.parts.some((part) => part.kind === "hitl" && part.status === "pending");
|
|
232
|
-
const
|
|
550
|
+
const cancelling = eventName === "run.cancelling" || String(event.payload?.status || "") === "cancelling";
|
|
551
|
+
const existing = state.messages.find((message) => message.role === "assistant" && (message.id === messageId || Boolean(runId && message.runId === runId)));
|
|
233
552
|
const payload = event.payload || {};
|
|
553
|
+
const eventThreadId = typeof payload.thread_id === "string" && payload.thread_id.trim() ? payload.thread_id.trim() : null;
|
|
554
|
+
const contextApplies = (!state.activeThreadId || !eventThreadId || eventThreadId === state.activeThreadId) && (!state.activeRunId || !runId || runId === state.activeRunId);
|
|
555
|
+
const contextWindow = contextApplies ? mergeContextWindowUpdate2(state.contextWindow, payload.context_window) : state.contextWindow;
|
|
556
|
+
const explicitCompression = contextApplies ? normalizeContextCompression2(payload.context_compression) : null;
|
|
557
|
+
const contextCompression = contextApplies ? explicitCompression || compressionFromContextWindow2(contextWindow) || state.contextCompression : state.contextCompression;
|
|
558
|
+
const hasLiveCompressionUpdate = Boolean(
|
|
559
|
+
contextApplies && (explicitCompression?.active || compressionFromContextWindow2(
|
|
560
|
+
mergeContextWindowUpdate2(null, payload.context_window)
|
|
561
|
+
)?.active)
|
|
562
|
+
);
|
|
234
563
|
const delta = eventName === "assistant.delta" && typeof payload.content === "string" ? payload.content : "";
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
|
|
564
|
+
const hasCanonicalResponseBlocks = Array.isArray(payload.response_blocks);
|
|
565
|
+
const content = hasCanonicalResponseBlocks ? projection.content : delta ? `${existing?.content || ""}${delta}` : projection.content || existing?.content || "";
|
|
566
|
+
const projectedParts = hasCanonicalResponseBlocks ? projection.parts : delta ? appendAssistantDelta(
|
|
567
|
+
existing?.parts || [],
|
|
568
|
+
delta,
|
|
569
|
+
`${runId || "run"}-assistant-text-${event.seq}`
|
|
570
|
+
) : projection.parts.length ? upsertResponseBlockParts(existing?.parts || [], projection.parts) : existing?.parts || [];
|
|
571
|
+
const lifecycleParts = reconcileModelTurnParts(projectedParts, event);
|
|
572
|
+
const compressionPart = contextApplies && explicitCompression?.active ? contextCompressionPartFromEvent(event, lifecycleParts) : null;
|
|
573
|
+
const parts = compressionPart ? upsertResponseBlockParts(lifecycleParts, [compressionPart]) : lifecycleParts;
|
|
574
|
+
const activeThreadId = eventThreadId || state.activeThreadId;
|
|
575
|
+
const messages = upsertStableAssistantMessage(state.messages, {
|
|
576
|
+
messageId,
|
|
577
|
+
runId,
|
|
578
|
+
create: () => ({
|
|
579
|
+
id: messageId,
|
|
580
|
+
role: "assistant",
|
|
581
|
+
runId,
|
|
582
|
+
content,
|
|
583
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
584
|
+
isFinal: terminal,
|
|
585
|
+
parts
|
|
586
|
+
}),
|
|
587
|
+
update: (message) => ({
|
|
588
|
+
...message,
|
|
589
|
+
runId: runId ?? message.runId,
|
|
590
|
+
content,
|
|
591
|
+
parts,
|
|
592
|
+
isFinal: terminal || message.isFinal
|
|
593
|
+
})
|
|
594
|
+
});
|
|
595
|
+
const threads = activeThreadId ? state.threads.some((thread) => thread.id === activeThreadId) ? state.threads.map((thread) => thread.id === activeThreadId ? {
|
|
596
|
+
...thread,
|
|
597
|
+
messages,
|
|
598
|
+
isHydrated: true,
|
|
599
|
+
last_run_id: runId ?? thread.last_run_id,
|
|
600
|
+
contextWindow,
|
|
601
|
+
contextCompression
|
|
602
|
+
} : thread) : [{
|
|
603
|
+
id: activeThreadId,
|
|
604
|
+
title: null,
|
|
605
|
+
messages,
|
|
606
|
+
isHydrated: true,
|
|
607
|
+
last_run_id: runId,
|
|
608
|
+
contextWindow,
|
|
609
|
+
contextCompression
|
|
610
|
+
}, ...state.threads] : state.threads;
|
|
239
611
|
return {
|
|
240
612
|
...state,
|
|
613
|
+
activeThreadId,
|
|
241
614
|
activeRunId: terminal ? null : runId,
|
|
242
615
|
cursor: typeof event.seq === "number" ? event.seq : state.cursor,
|
|
243
|
-
messages
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
runId,
|
|
250
|
-
content,
|
|
251
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
252
|
-
isFinal: terminal,
|
|
253
|
-
parts
|
|
254
|
-
}),
|
|
255
|
-
update: (message) => ({
|
|
256
|
-
...message,
|
|
257
|
-
runId: runId ?? message.runId,
|
|
258
|
-
content,
|
|
259
|
-
parts,
|
|
260
|
-
isFinal: terminal || message.isFinal
|
|
261
|
-
})
|
|
262
|
-
}),
|
|
263
|
-
runState: paused ? "paused" : terminal ? eventName === "run.completed" ? "completed" : "failed" : "streaming",
|
|
616
|
+
messages,
|
|
617
|
+
threads,
|
|
618
|
+
contextWindow,
|
|
619
|
+
contextCompression,
|
|
620
|
+
contextCompressionRevision: hasLiveCompressionUpdate ? state.contextCompressionRevision + 1 : state.contextCompressionRevision,
|
|
621
|
+
runState: paused ? "paused" : cancelling ? "cancelling" : terminal ? eventName === "run.completed" ? "completed" : eventName === "run.cancelled" ? "cancelled" : "failed" : "streaming",
|
|
264
622
|
streamingMessageId: terminal || paused ? null : messageId
|
|
265
623
|
};
|
|
266
624
|
}
|
|
625
|
+
function applyRuntimeEvents(state, events) {
|
|
626
|
+
return events.reduce(applyRuntimeEvent, state);
|
|
627
|
+
}
|
|
267
628
|
function applyThreadSummaryEvent(threads, event) {
|
|
268
629
|
if (event.event === "snapshot_required") return threads;
|
|
269
630
|
if (event.event === "thread.deleted") return threads.filter((thread) => thread.id !== event.thread_id);
|
|
@@ -278,15 +639,74 @@ function applyThreadSummaryEvent(threads, event) {
|
|
|
278
639
|
|
|
279
640
|
// src/use-agent-chat.ts
|
|
280
641
|
import * as React2 from "react";
|
|
642
|
+
|
|
643
|
+
// src/runtime-event-buffer.ts
|
|
644
|
+
function runtimeEventFrameScheduler() {
|
|
645
|
+
const runtime = globalThis;
|
|
646
|
+
if (typeof runtime.requestAnimationFrame !== "function") return void 0;
|
|
647
|
+
return (callback) => {
|
|
648
|
+
const handle = runtime.requestAnimationFrame?.(callback);
|
|
649
|
+
return () => {
|
|
650
|
+
if (handle !== void 0) runtime.cancelAnimationFrame?.(handle);
|
|
651
|
+
};
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function createRuntimeEventFrameBuffer(apply, scheduleFrame) {
|
|
655
|
+
let pending = [];
|
|
656
|
+
let cancelFrame = null;
|
|
657
|
+
const flush = () => {
|
|
658
|
+
cancelFrame?.();
|
|
659
|
+
cancelFrame = null;
|
|
660
|
+
if (!pending.length) return;
|
|
661
|
+
const events = pending;
|
|
662
|
+
pending = [];
|
|
663
|
+
apply(events);
|
|
664
|
+
};
|
|
665
|
+
return {
|
|
666
|
+
dispose() {
|
|
667
|
+
cancelFrame?.();
|
|
668
|
+
cancelFrame = null;
|
|
669
|
+
pending = [];
|
|
670
|
+
},
|
|
671
|
+
flush,
|
|
672
|
+
push(event) {
|
|
673
|
+
if (event.event !== "assistant.delta" || !scheduleFrame) {
|
|
674
|
+
pending.push(event);
|
|
675
|
+
flush();
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
pending.push(event);
|
|
679
|
+
if (cancelFrame) return;
|
|
680
|
+
cancelFrame = scheduleFrame(() => {
|
|
681
|
+
cancelFrame = null;
|
|
682
|
+
if (!pending.length) return;
|
|
683
|
+
const events = pending;
|
|
684
|
+
pending = [];
|
|
685
|
+
apply(events);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/initial-thread.ts
|
|
692
|
+
function resolveInitialActiveThreadId(options, storedThreadId) {
|
|
693
|
+
return Object.prototype.hasOwnProperty.call(options, "activeThreadId") ? options.activeThreadId ?? null : storedThreadId;
|
|
694
|
+
}
|
|
695
|
+
function shouldHydrateSelectedThread(selectedThreadId, activeThreadId, activeThreadIsHydrated) {
|
|
696
|
+
return Boolean(selectedThreadId) && (selectedThreadId !== activeThreadId || !activeThreadIsHydrated);
|
|
697
|
+
}
|
|
698
|
+
function isCurrentThreadOpenRequest(input) {
|
|
699
|
+
return !input.signalAborted && input.requestGeneration === input.currentGeneration && input.requestedThreadId === input.activeThreadId;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/use-agent-chat.ts
|
|
281
703
|
function summaryToStored(thread) {
|
|
282
704
|
return { ...thread, id: String(thread.id), messages: [], isHydrated: false };
|
|
283
705
|
}
|
|
284
706
|
function activeRunIdFromThread(thread) {
|
|
285
707
|
if (!thread) return null;
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
if (!["queued", "running", "cancelling"].includes(status)) return null;
|
|
289
|
-
return String(active?.run_id || thread.last_run_id || "") || null;
|
|
708
|
+
const activity = getThreadActivity(thread);
|
|
709
|
+
return activity.isSpinning ? activity.runId : null;
|
|
290
710
|
}
|
|
291
711
|
function useAgentChat(options = {}) {
|
|
292
712
|
const runtime = useAgents24Runtime();
|
|
@@ -295,7 +715,10 @@ function useAgentChat(options = {}) {
|
|
|
295
715
|
const threadPageSize = options.threadPageSize ?? 50;
|
|
296
716
|
const [state, setState] = React2.useState(() => ({
|
|
297
717
|
...initialAgentChatState,
|
|
298
|
-
activeThreadId:
|
|
718
|
+
activeThreadId: resolveInitialActiveThreadId(
|
|
719
|
+
options,
|
|
720
|
+
runtime.storage?.loadActiveThreadId?.() ?? null
|
|
721
|
+
),
|
|
299
722
|
threads: runtime.storage?.loadThreads() ?? []
|
|
300
723
|
}));
|
|
301
724
|
const [isLoadingThreads, setIsLoadingThreads] = React2.useState(false);
|
|
@@ -305,6 +728,9 @@ function useAgentChat(options = {}) {
|
|
|
305
728
|
const [isLoadingOlder, setIsLoadingOlder] = React2.useState(false);
|
|
306
729
|
const [isSubmitting, setIsSubmitting] = React2.useState(false);
|
|
307
730
|
const streamControllerRef = React2.useRef(null);
|
|
731
|
+
const threadOpenControllerRef = React2.useRef(null);
|
|
732
|
+
const threadOpenGenerationRef = React2.useRef(0);
|
|
733
|
+
const refreshThreadsPromiseRef = React2.useRef(null);
|
|
308
734
|
const attachRunRef = React2.useRef(async () => void 0);
|
|
309
735
|
const stateRef = React2.useRef(state);
|
|
310
736
|
stateRef.current = state;
|
|
@@ -320,33 +746,87 @@ function useAgentChat(options = {}) {
|
|
|
320
746
|
return next;
|
|
321
747
|
});
|
|
322
748
|
}, [persist]);
|
|
749
|
+
const eventBuffer = React2.useMemo(
|
|
750
|
+
() => createRuntimeEventFrameBuffer(
|
|
751
|
+
(events) => update((current) => applyRuntimeEvents(current, events)),
|
|
752
|
+
runtimeEventFrameScheduler()
|
|
753
|
+
),
|
|
754
|
+
[update]
|
|
755
|
+
);
|
|
756
|
+
const clearOperationError = React2.useCallback((operation) => {
|
|
757
|
+
update((current) => clearAgentChatOperationError(current, operation));
|
|
758
|
+
}, [update]);
|
|
759
|
+
const clearBackgroundError = React2.useCallback(() => {
|
|
760
|
+
update(clearAgentChatBackgroundError);
|
|
761
|
+
}, [update]);
|
|
762
|
+
const failOperation = React2.useCallback((operation, cause) => {
|
|
763
|
+
update((current) => withAgentChatOperationError(current, operation, cause));
|
|
764
|
+
}, [update]);
|
|
765
|
+
const invalidateThreadOpen = React2.useCallback(() => {
|
|
766
|
+
threadOpenGenerationRef.current += 1;
|
|
767
|
+
threadOpenControllerRef.current?.abort("Thread selection changed.");
|
|
768
|
+
threadOpenControllerRef.current = null;
|
|
769
|
+
setIsLoadingHistory(false);
|
|
770
|
+
}, []);
|
|
323
771
|
const detach = React2.useCallback(() => {
|
|
772
|
+
eventBuffer.flush();
|
|
324
773
|
streamControllerRef.current?.abort();
|
|
325
774
|
streamControllerRef.current = null;
|
|
326
775
|
update((current) => ({ ...current, streamingMessageId: null, runState: current.activeRunId ? "reconnecting" : "idle" }));
|
|
327
|
-
}, [update]);
|
|
328
|
-
React2.useEffect(() => () =>
|
|
329
|
-
|
|
776
|
+
}, [eventBuffer, update]);
|
|
777
|
+
React2.useEffect(() => () => {
|
|
778
|
+
eventBuffer.dispose();
|
|
779
|
+
streamControllerRef.current?.abort();
|
|
780
|
+
threadOpenControllerRef.current?.abort();
|
|
781
|
+
}, [eventBuffer]);
|
|
782
|
+
const refreshThreads = React2.useCallback(() => {
|
|
783
|
+
if (refreshThreadsPromiseRef.current) return refreshThreadsPromiseRef.current;
|
|
784
|
+
clearOperationError();
|
|
330
785
|
setIsLoadingThreads(true);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
786
|
+
const request = (async () => {
|
|
787
|
+
try {
|
|
788
|
+
const result = await client.threads.list({ limit: threadPageSize });
|
|
789
|
+
setThreadTotal(result.total);
|
|
790
|
+
update((current) => {
|
|
791
|
+
const byId = new Map(current.threads.map((thread) => [thread.id, thread]));
|
|
792
|
+
const nextThreads = result.items.map((item) => {
|
|
793
|
+
const existing = byId.get(item.id);
|
|
794
|
+
return { ...existing, ...summaryToStored(item), messages: existing?.messages || [], isHydrated: existing?.isHydrated };
|
|
795
|
+
});
|
|
796
|
+
return {
|
|
797
|
+
...current,
|
|
798
|
+
threads: reconcileRefreshedThreads(
|
|
799
|
+
current.threads,
|
|
800
|
+
nextThreads,
|
|
801
|
+
current.activeThreadId
|
|
802
|
+
)
|
|
803
|
+
};
|
|
339
804
|
});
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
805
|
+
} catch (error) {
|
|
806
|
+
if (!isAbortLike(error)) {
|
|
807
|
+
failOperation("refresh_threads", error);
|
|
808
|
+
throw error;
|
|
809
|
+
}
|
|
810
|
+
} finally {
|
|
811
|
+
setIsLoadingThreads(false);
|
|
812
|
+
}
|
|
813
|
+
})();
|
|
814
|
+
refreshThreadsPromiseRef.current = request;
|
|
815
|
+
void request.then(
|
|
816
|
+
() => {
|
|
817
|
+
if (refreshThreadsPromiseRef.current === request) refreshThreadsPromiseRef.current = null;
|
|
818
|
+
},
|
|
819
|
+
() => {
|
|
820
|
+
if (refreshThreadsPromiseRef.current === request) refreshThreadsPromiseRef.current = null;
|
|
821
|
+
}
|
|
822
|
+
);
|
|
823
|
+
return request;
|
|
824
|
+
}, [clearOperationError, client, failOperation, threadPageSize, update]);
|
|
346
825
|
const loadMoreThreads = React2.useCallback(async () => {
|
|
347
826
|
if (isLoadingMoreThreads) return;
|
|
348
827
|
const skip = stateRef.current.threads.length;
|
|
349
828
|
if (threadTotal !== void 0 && skip >= threadTotal) return;
|
|
829
|
+
clearOperationError();
|
|
350
830
|
setIsLoadingMoreThreads(true);
|
|
351
831
|
try {
|
|
352
832
|
const result = await client.threads.list({ skip, limit: threadPageSize });
|
|
@@ -364,64 +844,126 @@ function useAgentChat(options = {}) {
|
|
|
364
844
|
}
|
|
365
845
|
return { ...current, threads: [...byId.values()] };
|
|
366
846
|
});
|
|
847
|
+
} catch (error) {
|
|
848
|
+
if (!isAbortLike(error)) {
|
|
849
|
+
failOperation("load_more_threads", error);
|
|
850
|
+
throw error;
|
|
851
|
+
}
|
|
367
852
|
} finally {
|
|
368
853
|
setIsLoadingMoreThreads(false);
|
|
369
854
|
}
|
|
370
|
-
}, [client, isLoadingMoreThreads, threadPageSize, threadTotal, update]);
|
|
855
|
+
}, [clearOperationError, client, failOperation, isLoadingMoreThreads, threadPageSize, threadTotal, update]);
|
|
371
856
|
const openThread = React2.useCallback(async (threadId) => {
|
|
857
|
+
const previousSelection = captureAgentChatThreadSelection(stateRef.current);
|
|
858
|
+
threadOpenControllerRef.current?.abort("A newer thread was selected.");
|
|
859
|
+
const requestController = runtime.createAbortController();
|
|
860
|
+
threadOpenControllerRef.current = requestController;
|
|
861
|
+
const requestGeneration = threadOpenGenerationRef.current + 1;
|
|
862
|
+
threadOpenGenerationRef.current = requestGeneration;
|
|
372
863
|
detach();
|
|
373
|
-
|
|
864
|
+
clearOperationError();
|
|
865
|
+
update((current) => ({
|
|
866
|
+
...current,
|
|
867
|
+
activeThreadId: threadId,
|
|
868
|
+
activeRunId: null,
|
|
869
|
+
cursor: null,
|
|
870
|
+
error: null,
|
|
871
|
+
messages: current.threads.find((thread) => thread.id === threadId)?.messages || [],
|
|
872
|
+
contextWindow: current.threads.find((thread) => thread.id === threadId)?.contextWindow || null,
|
|
873
|
+
contextCompression: current.threads.find((thread) => thread.id === threadId)?.contextCompression || null,
|
|
874
|
+
contextCompressionRevision: 0,
|
|
875
|
+
runState: "idle",
|
|
876
|
+
streamingMessageId: null
|
|
877
|
+
}));
|
|
374
878
|
setIsLoadingHistory(true);
|
|
375
879
|
try {
|
|
376
|
-
const detail = await client.threads.get({
|
|
880
|
+
const detail = await client.threads.get({
|
|
881
|
+
threadId,
|
|
882
|
+
limit: pageSize,
|
|
883
|
+
signal: requestController.signal
|
|
884
|
+
});
|
|
885
|
+
if (!isCurrentThreadOpenRequest({
|
|
886
|
+
activeThreadId: stateRef.current.activeThreadId,
|
|
887
|
+
currentGeneration: threadOpenGenerationRef.current,
|
|
888
|
+
requestGeneration,
|
|
889
|
+
requestedThreadId: threadId,
|
|
890
|
+
signalAborted: requestController.signal.aborted
|
|
891
|
+
})) return;
|
|
377
892
|
const messages = threadDetailToMessages(detail);
|
|
893
|
+
const context = latestThreadContext(detail);
|
|
378
894
|
const stored = {
|
|
379
895
|
...detail,
|
|
380
896
|
messages,
|
|
381
897
|
isHydrated: true,
|
|
382
898
|
hasOlderTurns: Boolean(detail.paging?.has_more),
|
|
383
|
-
nextBeforeTurnIndex: detail.paging?.next_before_turn_index ?? null
|
|
899
|
+
nextBeforeTurnIndex: detail.paging?.next_before_turn_index ?? null,
|
|
900
|
+
...context
|
|
384
901
|
};
|
|
385
|
-
update((current) => ({
|
|
902
|
+
update((current) => isCurrentThreadOpenRequest({
|
|
903
|
+
activeThreadId: current.activeThreadId,
|
|
904
|
+
currentGeneration: threadOpenGenerationRef.current,
|
|
905
|
+
requestGeneration,
|
|
906
|
+
requestedThreadId: threadId,
|
|
907
|
+
signalAborted: requestController.signal.aborted
|
|
908
|
+
}) ? {
|
|
386
909
|
...current,
|
|
387
910
|
activeThreadId: threadId,
|
|
388
911
|
messages,
|
|
912
|
+
...context,
|
|
913
|
+
contextCompressionRevision: 0,
|
|
389
914
|
activeRunId: activeRunIdFromThread(stored),
|
|
390
915
|
threads: current.threads.some((thread) => thread.id === threadId) ? current.threads.map((thread) => thread.id === threadId ? stored : thread) : [stored, ...current.threads]
|
|
391
|
-
})
|
|
916
|
+
} : current);
|
|
392
917
|
const activeRunId = activeRunIdFromThread(stored);
|
|
393
|
-
if (activeRunId
|
|
918
|
+
if (activeRunId && isCurrentThreadOpenRequest({
|
|
919
|
+
activeThreadId: stateRef.current.activeThreadId,
|
|
920
|
+
currentGeneration: threadOpenGenerationRef.current,
|
|
921
|
+
requestGeneration,
|
|
922
|
+
requestedThreadId: threadId,
|
|
923
|
+
signalAborted: requestController.signal.aborted
|
|
924
|
+
})) await attachRunRef.current(activeRunId).catch(() => void 0);
|
|
925
|
+
} catch (error) {
|
|
926
|
+
if (!isAbortLike(error) && requestGeneration === threadOpenGenerationRef.current) {
|
|
927
|
+
failOperation("open_thread", error);
|
|
928
|
+
update((current) => restoreAgentChatThreadSelection(current, previousSelection));
|
|
929
|
+
if (previousSelection.activeRunId) {
|
|
930
|
+
void attachRunRef.current(previousSelection.activeRunId, previousSelection.cursor ?? void 0).catch(() => void 0);
|
|
931
|
+
}
|
|
932
|
+
throw error;
|
|
933
|
+
}
|
|
394
934
|
} finally {
|
|
395
|
-
|
|
935
|
+
if (requestGeneration === threadOpenGenerationRef.current) {
|
|
936
|
+
if (threadOpenControllerRef.current === requestController) threadOpenControllerRef.current = null;
|
|
937
|
+
setIsLoadingHistory(false);
|
|
938
|
+
}
|
|
396
939
|
}
|
|
397
|
-
}, [client, detach, pageSize, update]);
|
|
940
|
+
}, [clearOperationError, client, detach, failOperation, pageSize, runtime, update]);
|
|
398
941
|
const consumeEvent = React2.useCallback((event) => {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (!next.activeThreadId) return next;
|
|
402
|
-
return {
|
|
403
|
-
...next,
|
|
404
|
-
threads: next.threads.map((thread) => thread.id === next.activeThreadId ? { ...thread, messages: next.messages, isHydrated: true, last_run_id: next.activeRunId } : thread)
|
|
405
|
-
};
|
|
406
|
-
});
|
|
407
|
-
}, [update]);
|
|
942
|
+
eventBuffer.push(event);
|
|
943
|
+
}, [eventBuffer]);
|
|
408
944
|
const attachRun = React2.useCallback(async (runId, cursor) => {
|
|
409
945
|
streamControllerRef.current?.abort();
|
|
410
946
|
const controller = runtime.createAbortController();
|
|
411
947
|
streamControllerRef.current = controller;
|
|
948
|
+
clearOperationError();
|
|
412
949
|
update((current) => ({ ...current, activeRunId: runId, runState: "reconnecting", error: null }));
|
|
413
950
|
try {
|
|
414
951
|
const result = await client.runs.attach({ runId, cursor, signal: controller.signal }, consumeEvent);
|
|
415
952
|
update((current) => ({ ...current, activeRunId: result.detached ? runId : current.activeRunId, cursor: result.cursor ?? current.cursor, runState: result.detached ? "reconnecting" : current.runState }));
|
|
416
953
|
} catch (error) {
|
|
417
|
-
if (!isAbortLike(error))
|
|
954
|
+
if (!isAbortLike(error)) {
|
|
955
|
+
update((current) => ({ ...current, runState: "failed", streamingMessageId: null }));
|
|
956
|
+
failOperation("attach_run", error);
|
|
957
|
+
throw error;
|
|
958
|
+
}
|
|
418
959
|
} finally {
|
|
419
960
|
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
420
961
|
}
|
|
421
|
-
}, [client, consumeEvent, runtime, update]);
|
|
962
|
+
}, [clearOperationError, client, consumeEvent, failOperation, runtime, update]);
|
|
422
963
|
attachRunRef.current = attachRun;
|
|
423
964
|
const submit = React2.useCallback(async (input) => {
|
|
424
965
|
if (!input.text.trim() && !input.attachmentIds?.length) return;
|
|
966
|
+
invalidateThreadOpen();
|
|
425
967
|
detach();
|
|
426
968
|
const controller = runtime.createAbortController();
|
|
427
969
|
streamControllerRef.current = controller;
|
|
@@ -430,76 +972,208 @@ function useAgentChat(options = {}) {
|
|
|
430
972
|
const idempotencyKey = runtime.createId();
|
|
431
973
|
const userMessage = { id: userId, role: "user", content: input.text.trim(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), parts: input.text.trim() ? [{ id: `${userId}-text`, type: "text", kind: "text", text: input.text.trim() }] : [], attachments: input.attachments };
|
|
432
974
|
const assistantMessage = { id: assistantId, role: "assistant", content: "", createdAt: (/* @__PURE__ */ new Date()).toISOString(), isFinal: false, parts: [] };
|
|
975
|
+
clearOperationError();
|
|
433
976
|
update((current) => ({ ...current, messages: appendStableStreamingTurn(current.messages, userMessage, assistantMessage), streamingMessageId: assistantId, runState: "streaming", error: null }));
|
|
434
977
|
setIsSubmitting(true);
|
|
435
978
|
try {
|
|
436
979
|
const result = await client.chat.stream({ input: input.text.trim(), threadId: stateRef.current.activeThreadId || void 0, attachmentIds: input.attachmentIds, requestedModelId: input.requestedModelId, idempotencyKey, signal: controller.signal }, consumeEvent);
|
|
437
980
|
update((current) => ({ ...current, activeThreadId: result.threadId || current.activeThreadId, activeRunId: result.detached ? result.runId : current.activeRunId, cursor: result.cursor, runState: result.detached ? "reconnecting" : current.runState }));
|
|
438
|
-
if (result.threadId) await refreshThreads();
|
|
981
|
+
if (result.threadId) await refreshThreads().catch(() => void 0);
|
|
439
982
|
} catch (error) {
|
|
440
|
-
if (!isAbortLike(error))
|
|
983
|
+
if (!isAbortLike(error)) {
|
|
984
|
+
update((current) => ({ ...current, runState: "failed", streamingMessageId: null }));
|
|
985
|
+
failOperation("submit", error);
|
|
986
|
+
throw error;
|
|
987
|
+
}
|
|
441
988
|
} finally {
|
|
442
989
|
setIsSubmitting(false);
|
|
443
990
|
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
444
991
|
}
|
|
445
|
-
}, [client, consumeEvent, detach, refreshThreads, runtime, update]);
|
|
992
|
+
}, [clearOperationError, client, consumeEvent, detach, failOperation, invalidateThreadOpen, refreshThreads, runtime, update]);
|
|
446
993
|
const startNewThread = React2.useCallback(() => {
|
|
994
|
+
invalidateThreadOpen();
|
|
447
995
|
detach();
|
|
448
996
|
update((current) => ({ ...initialAgentChatState, threads: current.threads }));
|
|
449
|
-
}, [detach, update]);
|
|
997
|
+
}, [detach, invalidateThreadOpen, update]);
|
|
450
998
|
const deleteThread = React2.useCallback(async (threadId) => {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
999
|
+
if (stateRef.current.activeThreadId === threadId) invalidateThreadOpen();
|
|
1000
|
+
clearOperationError();
|
|
1001
|
+
try {
|
|
1002
|
+
await client.threads.delete({ threadId, idempotencyKey: runtime.createId() });
|
|
1003
|
+
update((current) => ({
|
|
1004
|
+
...current,
|
|
1005
|
+
activeThreadId: current.activeThreadId === threadId ? null : current.activeThreadId,
|
|
1006
|
+
messages: current.activeThreadId === threadId ? [] : current.messages,
|
|
1007
|
+
contextWindow: current.activeThreadId === threadId ? null : current.contextWindow,
|
|
1008
|
+
contextCompression: current.activeThreadId === threadId ? null : current.contextCompression,
|
|
1009
|
+
contextCompressionRevision: current.activeThreadId === threadId ? 0 : current.contextCompressionRevision,
|
|
1010
|
+
threads: current.threads.filter((thread) => thread.id !== threadId)
|
|
1011
|
+
}));
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
if (!isAbortLike(error)) {
|
|
1014
|
+
failOperation("delete_thread", error);
|
|
1015
|
+
throw error;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}, [clearOperationError, client, failOperation, invalidateThreadOpen, runtime, update]);
|
|
454
1019
|
const cancelRun = React2.useCallback(async () => {
|
|
455
1020
|
const runId = stateRef.current.activeRunId;
|
|
456
|
-
if (!runId) return;
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
update((current) =>
|
|
460
|
-
|
|
461
|
-
|
|
1021
|
+
if (!runId || stateRef.current.runState === "cancelling") return;
|
|
1022
|
+
const previousRunState = stateRef.current.runState;
|
|
1023
|
+
clearOperationError();
|
|
1024
|
+
update((current) => current.activeRunId === runId ? { ...current, runState: "cancelling" } : current);
|
|
1025
|
+
try {
|
|
1026
|
+
const result = await client.runs.cancel({ runId, idempotencyKey: runtime.createId() });
|
|
1027
|
+
if (result.status === "cancelled") {
|
|
1028
|
+
detach();
|
|
1029
|
+
update((current) => ({
|
|
1030
|
+
...current,
|
|
1031
|
+
activeRunId: null,
|
|
1032
|
+
runState: "cancelled",
|
|
1033
|
+
streamingMessageId: null
|
|
1034
|
+
}));
|
|
1035
|
+
} else if (result.status === "cancelling") {
|
|
1036
|
+
update((current) => ({
|
|
1037
|
+
...current,
|
|
1038
|
+
activeRunId: runId,
|
|
1039
|
+
runState: "cancelling"
|
|
1040
|
+
}));
|
|
1041
|
+
} else {
|
|
1042
|
+
detach();
|
|
1043
|
+
update((current) => ({
|
|
1044
|
+
...current,
|
|
1045
|
+
activeRunId: null,
|
|
1046
|
+
runState: result.status === "completed" ? "completed" : "failed",
|
|
1047
|
+
streamingMessageId: null
|
|
1048
|
+
}));
|
|
1049
|
+
}
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
if (!isAbortLike(error)) {
|
|
1052
|
+
update((current) => current.activeRunId === runId ? { ...current, runState: previousRunState } : current);
|
|
1053
|
+
failOperation("cancel_run", error);
|
|
1054
|
+
throw error;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}, [clearOperationError, client, detach, failOperation, runtime, update]);
|
|
1058
|
+
const resumeHitl = React2.useCallback(async (part, response) => {
|
|
462
1059
|
const runId = stateRef.current.activeRunId || stateRef.current.messages.find((message) => message.parts.includes(part))?.runId;
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
1060
|
+
clearOperationError();
|
|
1061
|
+
if (!runId) {
|
|
1062
|
+
const error = new Error("No paused run is available to resume.");
|
|
1063
|
+
failOperation("resume_hitl", error);
|
|
1064
|
+
throw error;
|
|
1065
|
+
}
|
|
1066
|
+
try {
|
|
1067
|
+
const result = await client.hitl.resume({
|
|
1068
|
+
runId,
|
|
1069
|
+
interruptId: part.interruptId,
|
|
1070
|
+
...response,
|
|
1071
|
+
idempotencyKey: runtime.createId()
|
|
1072
|
+
});
|
|
1073
|
+
await attachRun(result.run_id || runId);
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
if (!isAbortLike(error)) failOperation("resume_hitl", error);
|
|
1076
|
+
throw error;
|
|
1077
|
+
}
|
|
1078
|
+
}, [attachRun, clearOperationError, client, failOperation, runtime]);
|
|
1079
|
+
const setFeedback = React2.useCallback(async (message, input) => {
|
|
1080
|
+
const runId = message.runId;
|
|
1081
|
+
if (!runId || message.role !== "assistant" || message.isFinal === false) {
|
|
1082
|
+
const error = new Error("Only persisted final responses can receive feedback.");
|
|
1083
|
+
failOperation("set_feedback", error);
|
|
1084
|
+
throw error;
|
|
1085
|
+
}
|
|
1086
|
+
const previous = message.feedback ?? null;
|
|
1087
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1088
|
+
const optimistic = input.rating ? {
|
|
1089
|
+
rating: input.rating,
|
|
1090
|
+
reason: input.rating === "dislike" ? input.reason ?? null : null,
|
|
1091
|
+
comment: input.rating === "dislike" ? input.comment?.trim() || null : null,
|
|
1092
|
+
created_at: previous?.created_at || timestamp,
|
|
1093
|
+
updated_at: timestamp
|
|
1094
|
+
} : null;
|
|
1095
|
+
const replaceFeedback = (current, feedback) => {
|
|
1096
|
+
const messages = current.messages.map(
|
|
1097
|
+
(item) => item.id === message.id ? { ...item, feedback } : item
|
|
1098
|
+
);
|
|
1099
|
+
return {
|
|
1100
|
+
...current,
|
|
1101
|
+
messages,
|
|
1102
|
+
threads: current.threads.map((thread) => ({
|
|
1103
|
+
...thread,
|
|
1104
|
+
messages: thread.messages.map(
|
|
1105
|
+
(item) => item.id === message.id ? { ...item, feedback } : item
|
|
1106
|
+
)
|
|
1107
|
+
}))
|
|
1108
|
+
};
|
|
1109
|
+
};
|
|
1110
|
+
clearOperationError();
|
|
1111
|
+
update((current) => replaceFeedback(current, optimistic));
|
|
1112
|
+
try {
|
|
1113
|
+
const result = await client.runs.setFeedback({ runId, ...input });
|
|
1114
|
+
update((current) => replaceFeedback(current, result.feedback));
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
update(
|
|
1117
|
+
(current) => withAgentChatOperationError(
|
|
1118
|
+
replaceFeedback(current, previous),
|
|
1119
|
+
"set_feedback",
|
|
1120
|
+
error
|
|
1121
|
+
)
|
|
1122
|
+
);
|
|
1123
|
+
throw error;
|
|
1124
|
+
}
|
|
1125
|
+
}, [clearOperationError, client, failOperation, update]);
|
|
467
1126
|
const loadOlder = React2.useCallback(async () => {
|
|
468
1127
|
const thread = stateRef.current.threads.find((item) => item.id === stateRef.current.activeThreadId);
|
|
469
1128
|
if (!thread?.nextBeforeTurnIndex) return;
|
|
1129
|
+
clearOperationError();
|
|
470
1130
|
setIsLoadingOlder(true);
|
|
471
1131
|
try {
|
|
472
|
-
const detail = await client.threads.get({ threadId: thread.id, limit: pageSize, beforeTurnIndex: thread.nextBeforeTurnIndex
|
|
1132
|
+
const detail = await client.threads.get({ threadId: thread.id, limit: pageSize, beforeTurnIndex: thread.nextBeforeTurnIndex });
|
|
473
1133
|
const older = threadDetailToMessages(detail);
|
|
474
1134
|
update((current) => ({ ...current, messages: [...older, ...current.messages.filter((message) => !older.some((item) => item.id === message.id))], threads: current.threads.map((item) => item.id === thread.id ? { ...item, hasOlderTurns: Boolean(detail.paging?.has_more), nextBeforeTurnIndex: detail.paging?.next_before_turn_index ?? null } : item) }));
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
if (!isAbortLike(error)) {
|
|
1137
|
+
failOperation("load_older_turns", error);
|
|
1138
|
+
throw error;
|
|
1139
|
+
}
|
|
475
1140
|
} finally {
|
|
476
1141
|
setIsLoadingOlder(false);
|
|
477
1142
|
}
|
|
478
|
-
}, [client, pageSize, update]);
|
|
1143
|
+
}, [clearOperationError, client, failOperation, pageSize, update]);
|
|
479
1144
|
const regenerate = React2.useCallback(async (message) => {
|
|
480
1145
|
const index = stateRef.current.messages.findIndex((item) => item.id === message.id);
|
|
481
1146
|
const user = stateRef.current.messages.slice(0, index).reverse().find((item) => item.role === "user");
|
|
482
|
-
if (user) await submit({
|
|
1147
|
+
if (user) await submit({
|
|
1148
|
+
text: user.content,
|
|
1149
|
+
attachmentIds: chatAttachmentIds(user.attachments),
|
|
1150
|
+
attachments: user.attachments
|
|
1151
|
+
});
|
|
483
1152
|
}, [submit]);
|
|
484
|
-
React2.useEffect(() => {
|
|
485
|
-
void refreshThreads();
|
|
486
|
-
}, [refreshThreads]);
|
|
487
1153
|
React2.useEffect(() => {
|
|
488
1154
|
const controller = runtime.createAbortController();
|
|
1155
|
+
clearBackgroundError();
|
|
489
1156
|
void client.threads.events({ signal: controller.signal }, async (event) => {
|
|
490
1157
|
if (event.event === "snapshot_required") {
|
|
1158
|
+
clearBackgroundError();
|
|
491
1159
|
await refreshThreads();
|
|
492
1160
|
return;
|
|
493
1161
|
}
|
|
494
|
-
update((current) => ({
|
|
1162
|
+
update((current) => ({
|
|
1163
|
+
...clearAgentChatBackgroundError(current),
|
|
1164
|
+
threads: applyThreadSummaryEvent(current.threads, event)
|
|
1165
|
+
}));
|
|
495
1166
|
}).catch((error) => {
|
|
496
|
-
if (!isAbortLike(error)) update((current) => (
|
|
1167
|
+
if (!isAbortLike(error)) update((current) => withAgentChatBackgroundError(current, error));
|
|
497
1168
|
});
|
|
498
1169
|
return () => controller.abort();
|
|
499
|
-
}, [client, refreshThreads, runtime, update]);
|
|
1170
|
+
}, [clearBackgroundError, client, refreshThreads, runtime.createAbortController, update]);
|
|
500
1171
|
React2.useEffect(() => {
|
|
501
1172
|
const threadId = options.activeThreadId;
|
|
502
|
-
|
|
1173
|
+
const activeThread2 = stateRef.current.threads.find((thread) => thread.id === stateRef.current.activeThreadId);
|
|
1174
|
+
if (shouldHydrateSelectedThread(threadId, stateRef.current.activeThreadId, Boolean(activeThread2?.isHydrated))) {
|
|
1175
|
+
void openThread(threadId).catch(() => void 0);
|
|
1176
|
+
}
|
|
503
1177
|
}, [openThread, options.activeThreadId]);
|
|
504
1178
|
const activeThread = state.threads.find((thread) => thread.id === state.activeThreadId) || null;
|
|
505
1179
|
const hasMoreThreads = threadTotal === void 0 ? state.threads.length >= threadPageSize : state.threads.length < threadTotal;
|
|
@@ -515,6 +1189,8 @@ function useAgentChat(options = {}) {
|
|
|
515
1189
|
isSubmitting,
|
|
516
1190
|
attachRun,
|
|
517
1191
|
cancelRun,
|
|
1192
|
+
clearBackgroundError,
|
|
1193
|
+
clearOperationError,
|
|
518
1194
|
deleteThread,
|
|
519
1195
|
detach,
|
|
520
1196
|
loadOlder,
|
|
@@ -523,6 +1199,7 @@ function useAgentChat(options = {}) {
|
|
|
523
1199
|
refreshThreads,
|
|
524
1200
|
regenerate,
|
|
525
1201
|
resumeHitl,
|
|
1202
|
+
setFeedback,
|
|
526
1203
|
startNewThread,
|
|
527
1204
|
submit
|
|
528
1205
|
};
|
|
@@ -594,7 +1271,7 @@ function useAgentThread(threadId, options = {}) {
|
|
|
594
1271
|
setIsLoading(false);
|
|
595
1272
|
}
|
|
596
1273
|
}
|
|
597
|
-
}, [client, createAbortController, options.beforeTurnIndex, options.
|
|
1274
|
+
}, [client, createAbortController, options.beforeTurnIndex, options.limit, threadId]);
|
|
598
1275
|
React3.useEffect(() => {
|
|
599
1276
|
void load();
|
|
600
1277
|
return () => controllerRef.current?.abort();
|
|
@@ -639,17 +1316,24 @@ function useAgentAttachments() {
|
|
|
639
1316
|
const { client, createId } = useAgents24Runtime();
|
|
640
1317
|
const [uploads, setUploads] = React3.useState({});
|
|
641
1318
|
const [isUploading, setIsUploading] = React3.useState(false);
|
|
642
|
-
const
|
|
1319
|
+
const uploadBatch = React3.useCallback(async (input) => {
|
|
643
1320
|
setIsUploading(true);
|
|
644
1321
|
try {
|
|
645
|
-
const
|
|
646
|
-
setUploads((current) => (
|
|
647
|
-
|
|
1322
|
+
const results = await client.attachments.uploadBatch({ uploads: input.uploads, threadId: input.threadId, idempotencyKey: createId(), signal: input.signal });
|
|
1323
|
+
setUploads((current) => Object.fromEntries([
|
|
1324
|
+
...Object.entries(current),
|
|
1325
|
+
...results.map((result) => [result.id, result])
|
|
1326
|
+
]));
|
|
1327
|
+
return results.map(attachmentResultToChatAttachment);
|
|
648
1328
|
} finally {
|
|
649
1329
|
setIsUploading(false);
|
|
650
1330
|
}
|
|
651
1331
|
}, [client, createId]);
|
|
652
|
-
|
|
1332
|
+
const createContentAccess = React3.useCallback(
|
|
1333
|
+
(input) => client.attachments.createContentAccess(input),
|
|
1334
|
+
[client]
|
|
1335
|
+
);
|
|
1336
|
+
return { createContentAccess, isUploading, uploadBatch, uploads };
|
|
653
1337
|
}
|
|
654
1338
|
function useAgentHitl() {
|
|
655
1339
|
const { client, createId } = useAgents24Runtime();
|
|
@@ -685,13 +1369,24 @@ export {
|
|
|
685
1369
|
Agents24Provider,
|
|
686
1370
|
appendStableStreamingTurn,
|
|
687
1371
|
applyRuntimeEvent,
|
|
1372
|
+
applyRuntimeEvents,
|
|
688
1373
|
applyThreadSummaryEvent,
|
|
689
1374
|
assistantProjectionFromEvent,
|
|
690
1375
|
attachmentResultToChatAttachment,
|
|
1376
|
+
captureAgentChatThreadSelection,
|
|
1377
|
+
chatAttachmentIds,
|
|
1378
|
+
clearAgentChatBackgroundError,
|
|
1379
|
+
clearAgentChatOperationError,
|
|
1380
|
+
contextCompressionPartFromEvent,
|
|
691
1381
|
createPortableId,
|
|
1382
|
+
getThreadActivity,
|
|
692
1383
|
initialAgentChatState,
|
|
693
1384
|
isAbortLike,
|
|
1385
|
+
latestThreadContext,
|
|
1386
|
+
modelTurnPartFromEvent,
|
|
694
1387
|
partsFromResponseBlocks,
|
|
1388
|
+
reconcileRefreshedThreads,
|
|
1389
|
+
restoreAgentChatThreadSelection,
|
|
695
1390
|
threadDetailToMessages,
|
|
696
1391
|
upsertStableAssistantMessage,
|
|
697
1392
|
useAgentAttachments,
|
|
@@ -702,6 +1397,8 @@ export {
|
|
|
702
1397
|
useAgentRun,
|
|
703
1398
|
useAgentThread,
|
|
704
1399
|
useAgentThreads,
|
|
705
|
-
useAgents24Runtime
|
|
1400
|
+
useAgents24Runtime,
|
|
1401
|
+
withAgentChatBackgroundError,
|
|
1402
|
+
withAgentChatOperationError
|
|
706
1403
|
};
|
|
707
1404
|
//# sourceMappingURL=index.js.map
|