@ai-matrx/agents 0.5.0 → 0.6.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 +46 -0
- package/README.md +45 -16
- package/dist/index.cjs +415 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +396 -3
- package/dist/index.js.map +1 -1
- package/dist/matrx/index.cjs +401 -26
- package/dist/matrx/index.cjs.map +1 -1
- package/dist/matrx/index.d.cts +345 -1
- package/dist/matrx/index.d.ts +345 -1
- package/dist/matrx/index.js +382 -3
- package/dist/matrx/index.js.map +1 -1
- package/dist/presentation/result.cjs +23 -4
- package/dist/presentation/result.cjs.map +1 -1
- package/dist/presentation/result.js +3 -3
- package/dist/presentation/result.js.map +1 -1
- package/dist/projection/request.cjs +25 -6
- package/dist/projection/request.cjs.map +1 -1
- package/dist/projection/request.js +5 -3
- package/dist/projection/request.js.map +1 -1
- package/dist/projection/workflow.cjs +25 -6
- package/dist/projection/workflow.cjs.map +1 -1
- package/dist/projection/workflow.js +5 -3
- package/dist/projection/workflow.js.map +1 -1
- package/dist/react/index.cjs +1038 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +540 -0
- package/dist/react/index.d.ts +540 -0
- package/dist/react/index.js +1016 -0
- package/dist/react/index.js.map +1 -0
- package/dist/stream/ndjson.cjs +26 -7
- package/dist/stream/ndjson.cjs.map +1 -1
- package/dist/stream/ndjson.js +6 -3
- package/dist/stream/ndjson.js.map +1 -1
- package/dist/stream/sse.cjs +25 -6
- package/dist/stream/sse.cjs.map +1 -1
- package/dist/stream/sse.js +5 -3
- package/dist/stream/sse.js.map +1 -1
- package/package.json +28 -2
|
@@ -0,0 +1,1016 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// react/use-agent-run.ts
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
+
|
|
6
|
+
// projection/request.ts
|
|
7
|
+
var asRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
8
|
+
var asString = (value) => typeof value === "string" ? value : null;
|
|
9
|
+
var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
10
|
+
function createAgentRequestProjection(input) {
|
|
11
|
+
return {
|
|
12
|
+
requestId: input.requestId,
|
|
13
|
+
conversationId: input.conversationId ?? null,
|
|
14
|
+
status: "pending",
|
|
15
|
+
answer: "",
|
|
16
|
+
reasoning: "",
|
|
17
|
+
reasoningActive: false,
|
|
18
|
+
phase: null,
|
|
19
|
+
phaseHistory: [],
|
|
20
|
+
operations: {},
|
|
21
|
+
tools: {},
|
|
22
|
+
renderBlocks: {},
|
|
23
|
+
renderBlockOrder: [],
|
|
24
|
+
completion: null,
|
|
25
|
+
error: null,
|
|
26
|
+
lastTransportSeq: 0,
|
|
27
|
+
eventCount: 0
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function toolStatus(event) {
|
|
31
|
+
switch (event) {
|
|
32
|
+
case "tool_started":
|
|
33
|
+
return "started";
|
|
34
|
+
case "tool_step":
|
|
35
|
+
return "step";
|
|
36
|
+
case "tool_result_preview":
|
|
37
|
+
return "preview";
|
|
38
|
+
case "tool_completed":
|
|
39
|
+
return "completed";
|
|
40
|
+
case "tool_error":
|
|
41
|
+
return "error";
|
|
42
|
+
case "tool_delegated":
|
|
43
|
+
return "delegated";
|
|
44
|
+
default:
|
|
45
|
+
return "progress";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function projectAgentEvent(current, event) {
|
|
49
|
+
const streamSeq = asNumber(event.stream_seq);
|
|
50
|
+
if (streamSeq !== null && streamSeq <= current.lastTransportSeq) return current;
|
|
51
|
+
const data = asRecord(event.data);
|
|
52
|
+
const next = {
|
|
53
|
+
...current,
|
|
54
|
+
status: current.status === "pending" ? "streaming" : current.status,
|
|
55
|
+
lastTransportSeq: streamSeq ?? current.lastTransportSeq,
|
|
56
|
+
eventCount: current.eventCount + 1
|
|
57
|
+
};
|
|
58
|
+
switch (event.event) {
|
|
59
|
+
case "chunk": {
|
|
60
|
+
const text = asString(data.text);
|
|
61
|
+
return text === null ? next : { ...next, answer: current.answer + text };
|
|
62
|
+
}
|
|
63
|
+
case "reasoning_chunk": {
|
|
64
|
+
const text = asString(data.text);
|
|
65
|
+
return text === null ? next : {
|
|
66
|
+
...next,
|
|
67
|
+
reasoning: current.reasoning + text,
|
|
68
|
+
reasoningActive: true
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
case "reasoning":
|
|
72
|
+
return {
|
|
73
|
+
...next,
|
|
74
|
+
reasoningActive: data.state === "started"
|
|
75
|
+
};
|
|
76
|
+
case "phase": {
|
|
77
|
+
const phase = asString(data.phase);
|
|
78
|
+
if (phase === null) return next;
|
|
79
|
+
return {
|
|
80
|
+
...next,
|
|
81
|
+
phase,
|
|
82
|
+
phaseHistory: [...current.phaseHistory, phase]
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
case "init": {
|
|
86
|
+
const operationId = asString(data.operation_id);
|
|
87
|
+
const operation = asString(data.operation);
|
|
88
|
+
if (operationId === null || operation === null) return next;
|
|
89
|
+
return {
|
|
90
|
+
...next,
|
|
91
|
+
operations: {
|
|
92
|
+
...current.operations,
|
|
93
|
+
[operationId]: {
|
|
94
|
+
operationId,
|
|
95
|
+
operation,
|
|
96
|
+
parentOperationId: asString(data.parent_operation_id),
|
|
97
|
+
status: "active",
|
|
98
|
+
metadata: Object.keys(asRecord(data.metadata)).length ? asRecord(data.metadata) : null,
|
|
99
|
+
result: null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
case "completion": {
|
|
105
|
+
const operationId = asString(data.operation_id);
|
|
106
|
+
const operation = asString(data.operation);
|
|
107
|
+
const rawStatus = asString(data.status);
|
|
108
|
+
const status = rawStatus === "failed" || rawStatus === "cancelled" ? rawStatus : "success";
|
|
109
|
+
const result = asRecord(data.result);
|
|
110
|
+
const operations = operationId ? {
|
|
111
|
+
...current.operations,
|
|
112
|
+
[operationId]: {
|
|
113
|
+
...current.operations[operationId] ?? {
|
|
114
|
+
operationId,
|
|
115
|
+
operation: operation ?? "unknown",
|
|
116
|
+
parentOperationId: null,
|
|
117
|
+
metadata: null
|
|
118
|
+
},
|
|
119
|
+
status,
|
|
120
|
+
result
|
|
121
|
+
}
|
|
122
|
+
} : current.operations;
|
|
123
|
+
if (operation === "user_request") {
|
|
124
|
+
return {
|
|
125
|
+
...next,
|
|
126
|
+
operations,
|
|
127
|
+
completion: data,
|
|
128
|
+
status: status === "success" ? "complete" : status === "cancelled" ? "cancelled" : "error"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return { ...next, operations };
|
|
132
|
+
}
|
|
133
|
+
case "tool_event": {
|
|
134
|
+
const callId = asString(data.call_id);
|
|
135
|
+
const toolName = asString(data.tool_name);
|
|
136
|
+
const lifecycle = asString(data.event);
|
|
137
|
+
if (callId === null || toolName === null || lifecycle === null) return next;
|
|
138
|
+
const status = toolStatus(lifecycle);
|
|
139
|
+
return {
|
|
140
|
+
...next,
|
|
141
|
+
status: status === "delegated" ? "awaiting-tools" : status === "completed" || status === "error" ? "streaming" : next.status,
|
|
142
|
+
tools: {
|
|
143
|
+
...current.tools,
|
|
144
|
+
[callId]: {
|
|
145
|
+
callId,
|
|
146
|
+
toolName,
|
|
147
|
+
status,
|
|
148
|
+
message: asString(data.message),
|
|
149
|
+
data: Object.keys(asRecord(data.data)).length ? asRecord(data.data) : null
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
case "render_block": {
|
|
155
|
+
const blockId = asString(data.blockId);
|
|
156
|
+
const blockIndex = asNumber(data.blockIndex);
|
|
157
|
+
const type = asString(data.type);
|
|
158
|
+
if (blockId === null || blockIndex === null || type === null) return next;
|
|
159
|
+
const alreadyKnown = Object.hasOwn(current.renderBlocks, blockId);
|
|
160
|
+
return {
|
|
161
|
+
...next,
|
|
162
|
+
renderBlocks: {
|
|
163
|
+
...current.renderBlocks,
|
|
164
|
+
[blockId]: {
|
|
165
|
+
blockId,
|
|
166
|
+
blockIndex,
|
|
167
|
+
type,
|
|
168
|
+
status: data.status === "complete" || data.status === "error" ? data.status : "streaming",
|
|
169
|
+
content: asString(data.content),
|
|
170
|
+
data: Object.keys(asRecord(data.data)).length ? asRecord(data.data) : null,
|
|
171
|
+
metadata: Object.keys(asRecord(data.metadata)).length ? asRecord(data.metadata) : null
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
renderBlockOrder: alreadyKnown ? current.renderBlockOrder : [...current.renderBlockOrder, blockId]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
case "error":
|
|
178
|
+
return { ...next, status: "error", error: data };
|
|
179
|
+
case "end":
|
|
180
|
+
return {
|
|
181
|
+
...next,
|
|
182
|
+
status: current.status === "error" || current.status === "cancelled" ? current.status : "complete",
|
|
183
|
+
reasoningActive: false
|
|
184
|
+
};
|
|
185
|
+
case "data": {
|
|
186
|
+
const conversationId = data.type === "conversation_id" ? asString(data.conversation_id) : null;
|
|
187
|
+
return conversationId === null ? next : { ...next, conversationId };
|
|
188
|
+
}
|
|
189
|
+
default:
|
|
190
|
+
return next;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// matrx/transport.ts
|
|
195
|
+
var MatrxApiError = class extends Error {
|
|
196
|
+
name = "MatrxApiError";
|
|
197
|
+
/** HTTP status of the failed response. */
|
|
198
|
+
status;
|
|
199
|
+
/** Machine code from the server body (`code`, or `detail.code`), when present. */
|
|
200
|
+
code;
|
|
201
|
+
/** The parsed server error body, verbatim (undefined when unparsable). */
|
|
202
|
+
serverDetail;
|
|
203
|
+
/** The request path the failure came from (server-relative). */
|
|
204
|
+
path;
|
|
205
|
+
constructor(args) {
|
|
206
|
+
super(
|
|
207
|
+
args.message ?? extractMatrxErrorMessage(args.serverDetail) ?? `HTTP ${args.status}`
|
|
208
|
+
);
|
|
209
|
+
this.status = args.status;
|
|
210
|
+
this.path = args.path;
|
|
211
|
+
this.serverDetail = args.serverDetail;
|
|
212
|
+
this.code = extractMatrxErrorCode(args.serverDetail);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
function isRecord(value) {
|
|
216
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
217
|
+
}
|
|
218
|
+
function nonBlankString(value) {
|
|
219
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
220
|
+
}
|
|
221
|
+
function extractMatrxErrorMessage(serverDetail) {
|
|
222
|
+
if (!isRecord(serverDetail)) return void 0;
|
|
223
|
+
const userMessage = nonBlankString(serverDetail.user_message);
|
|
224
|
+
if (userMessage) return userMessage;
|
|
225
|
+
const message = nonBlankString(serverDetail.message);
|
|
226
|
+
if (message) return message;
|
|
227
|
+
if (Array.isArray(serverDetail.details)) {
|
|
228
|
+
const messages = serverDetail.details.map((entry) => {
|
|
229
|
+
if (!isRecord(entry)) return void 0;
|
|
230
|
+
const detailMessage = nonBlankString(entry.message);
|
|
231
|
+
if (!detailMessage) return void 0;
|
|
232
|
+
const field = nonBlankString(entry.field);
|
|
233
|
+
return field ? `${field}: ${detailMessage}` : detailMessage;
|
|
234
|
+
}).filter((m) => typeof m === "string");
|
|
235
|
+
if (messages.length > 0) return messages.join("; ");
|
|
236
|
+
}
|
|
237
|
+
const detail = serverDetail.detail;
|
|
238
|
+
if (isRecord(detail)) {
|
|
239
|
+
const detailMessage = nonBlankString(detail.message) ?? nonBlankString(detail.user_message);
|
|
240
|
+
if (detailMessage) return detailMessage;
|
|
241
|
+
}
|
|
242
|
+
if (typeof detail === "string" && detail.trim()) return detail;
|
|
243
|
+
if (Array.isArray(detail)) {
|
|
244
|
+
const messages = detail.map(
|
|
245
|
+
(entry) => isRecord(entry) ? nonBlankString(entry.msg) : void 0
|
|
246
|
+
).filter((m) => typeof m === "string");
|
|
247
|
+
if (messages.length > 0) return messages.join("; ");
|
|
248
|
+
}
|
|
249
|
+
return void 0;
|
|
250
|
+
}
|
|
251
|
+
function extractMatrxErrorCode(serverDetail) {
|
|
252
|
+
if (!isRecord(serverDetail)) return null;
|
|
253
|
+
const topLevel = nonBlankString(serverDetail.code);
|
|
254
|
+
if (topLevel) return topLevel;
|
|
255
|
+
const detail = serverDetail.detail;
|
|
256
|
+
if (isRecord(detail)) {
|
|
257
|
+
const nested = nonBlankString(detail.code);
|
|
258
|
+
if (nested) return nested;
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// matrx/client.ts
|
|
264
|
+
import { isNetError } from "@ai-matrx/data";
|
|
265
|
+
import { credentialToHeaders } from "@ai-matrx/data";
|
|
266
|
+
|
|
267
|
+
// matrx/org-context.ts
|
|
268
|
+
var OrganizationContextError = class extends Error {
|
|
269
|
+
code;
|
|
270
|
+
constructor(code, message) {
|
|
271
|
+
super(message);
|
|
272
|
+
this.name = "OrganizationContextError";
|
|
273
|
+
this.code = code;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// matrx/client.ts
|
|
278
|
+
function classifyStatus(status) {
|
|
279
|
+
return status >= 400 && status < 500 ? "validation_error" : "http_error";
|
|
280
|
+
}
|
|
281
|
+
function normalizeMatrxError(err) {
|
|
282
|
+
if (err instanceof MatrxApiError) {
|
|
283
|
+
return {
|
|
284
|
+
type: classifyStatus(err.status),
|
|
285
|
+
message: err.message,
|
|
286
|
+
status: err.status,
|
|
287
|
+
serverDetail: err.serverDetail,
|
|
288
|
+
...err.code ? { code: err.code } : {},
|
|
289
|
+
name: err.name
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (err instanceof OrganizationContextError) {
|
|
293
|
+
return {
|
|
294
|
+
type: "validation_error",
|
|
295
|
+
message: err.message,
|
|
296
|
+
code: err.code,
|
|
297
|
+
name: err.name,
|
|
298
|
+
...err.stack !== void 0 ? { stack: err.stack } : {}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
302
|
+
return { type: "abort_error", message: "Request was cancelled." };
|
|
303
|
+
}
|
|
304
|
+
if (isNetError(err)) {
|
|
305
|
+
if (err.code === "aborted") {
|
|
306
|
+
return { type: "abort_error", message: err.message };
|
|
307
|
+
}
|
|
308
|
+
if (err.code === "http") {
|
|
309
|
+
const status = err.status ?? 0;
|
|
310
|
+
return { type: classifyStatus(status), message: err.message, status };
|
|
311
|
+
}
|
|
312
|
+
return { type: "network_error", message: err.message };
|
|
313
|
+
}
|
|
314
|
+
if (err instanceof Error) {
|
|
315
|
+
const httpMatch = err.message.match(/HTTP (\d+):\s*(.*)/);
|
|
316
|
+
if (httpMatch) {
|
|
317
|
+
const status = parseInt(httpMatch[1], 10);
|
|
318
|
+
return {
|
|
319
|
+
type: classifyStatus(status),
|
|
320
|
+
message: httpMatch[2] ?? err.message,
|
|
321
|
+
status
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return { type: "network_error", message: err.message };
|
|
325
|
+
}
|
|
326
|
+
return { type: "unknown", message: String(err ?? "Unknown error") };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// stream/ndjson.ts
|
|
330
|
+
var DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;
|
|
331
|
+
function isRecord2(value) {
|
|
332
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
333
|
+
}
|
|
334
|
+
function normalizeMatrxStreamEnvelope(value) {
|
|
335
|
+
if (!isRecord2(value)) return null;
|
|
336
|
+
if (typeof value.event === "string") {
|
|
337
|
+
const streamSeq = typeof value.stream_seq === "number" && Number.isFinite(value.stream_seq) ? value.stream_seq : void 0;
|
|
338
|
+
return {
|
|
339
|
+
event: value.event,
|
|
340
|
+
data: value.data,
|
|
341
|
+
...streamSeq === void 0 ? {} : { stream_seq: streamSeq }
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (value.e === "c" && typeof value.t === "string") {
|
|
345
|
+
return { event: "chunk", data: { text: value.t } };
|
|
346
|
+
}
|
|
347
|
+
if (value.e === "r" && typeof value.t === "string") {
|
|
348
|
+
return { event: "reasoning_chunk", data: { text: value.t } };
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
function createMatrxNdjsonFramer(options = {}) {
|
|
353
|
+
const decoder = new TextDecoder();
|
|
354
|
+
let buffer = "";
|
|
355
|
+
let lineNumber = 0;
|
|
356
|
+
let finished = false;
|
|
357
|
+
const assertOpen = () => {
|
|
358
|
+
if (finished) throw new Error("Matrx NDJSON framer is already finished");
|
|
359
|
+
};
|
|
360
|
+
const parseLine = (line, atCompletion) => {
|
|
361
|
+
const currentLineNumber = ++lineNumber;
|
|
362
|
+
const trimmed = line.trim();
|
|
363
|
+
if (!trimmed) return null;
|
|
364
|
+
let parsed;
|
|
365
|
+
try {
|
|
366
|
+
parsed = JSON.parse(trimmed);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
options.onMalformedLine?.({
|
|
369
|
+
line: trimmed,
|
|
370
|
+
error,
|
|
371
|
+
lineNumber: currentLineNumber,
|
|
372
|
+
atCompletion
|
|
373
|
+
});
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
const envelope = normalizeMatrxStreamEnvelope(parsed);
|
|
377
|
+
if (!envelope) {
|
|
378
|
+
options.onUnknownEnvelope?.(parsed);
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
options.onValidEnvelope?.({
|
|
382
|
+
raw: parsed,
|
|
383
|
+
envelope,
|
|
384
|
+
line: trimmed,
|
|
385
|
+
lineNumber: currentLineNumber,
|
|
386
|
+
atCompletion
|
|
387
|
+
});
|
|
388
|
+
return envelope;
|
|
389
|
+
};
|
|
390
|
+
const pushDecodedText = (fragment) => {
|
|
391
|
+
buffer += fragment;
|
|
392
|
+
const lines = buffer.split("\n");
|
|
393
|
+
buffer = lines.pop() ?? "";
|
|
394
|
+
const envelopes = [];
|
|
395
|
+
for (const line of lines) {
|
|
396
|
+
const envelope = parseLine(line, false);
|
|
397
|
+
if (envelope) envelopes.push(envelope);
|
|
398
|
+
}
|
|
399
|
+
return envelopes;
|
|
400
|
+
};
|
|
401
|
+
return {
|
|
402
|
+
pushText(fragment) {
|
|
403
|
+
assertOpen();
|
|
404
|
+
return pushDecodedText(decoder.decode() + fragment);
|
|
405
|
+
},
|
|
406
|
+
pushBytes(fragment) {
|
|
407
|
+
assertOpen();
|
|
408
|
+
return pushDecodedText(decoder.decode(fragment, { stream: true }));
|
|
409
|
+
},
|
|
410
|
+
finish() {
|
|
411
|
+
assertOpen();
|
|
412
|
+
finished = true;
|
|
413
|
+
const envelopes = pushDecodedText(decoder.decode());
|
|
414
|
+
if (buffer.length > 0) {
|
|
415
|
+
const envelope = parseLine(buffer, true);
|
|
416
|
+
buffer = "";
|
|
417
|
+
if (envelope) envelopes.push(envelope);
|
|
418
|
+
}
|
|
419
|
+
return envelopes;
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function readAheadLimit(value) {
|
|
424
|
+
const limit = value ?? DEFAULT_MATRX_NDJSON_READ_AHEAD;
|
|
425
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
426
|
+
throw new RangeError("maxReadAhead must be a positive safe integer");
|
|
427
|
+
}
|
|
428
|
+
return limit;
|
|
429
|
+
}
|
|
430
|
+
async function* readMatrxNdjsonStream(body, options = {}) {
|
|
431
|
+
const maxReadAhead = readAheadLimit(options.maxReadAhead);
|
|
432
|
+
const queue = [];
|
|
433
|
+
let queuedEventCount = 0;
|
|
434
|
+
let wakeConsumer = null;
|
|
435
|
+
let wakeProducer = null;
|
|
436
|
+
let readerFinished = false;
|
|
437
|
+
let consumerClosed = false;
|
|
438
|
+
const wakeWaitingConsumer = () => {
|
|
439
|
+
const wake = wakeConsumer;
|
|
440
|
+
wakeConsumer = null;
|
|
441
|
+
wake?.();
|
|
442
|
+
};
|
|
443
|
+
const wakeWaitingProducer = () => {
|
|
444
|
+
const wake = wakeProducer;
|
|
445
|
+
wakeProducer = null;
|
|
446
|
+
wake?.();
|
|
447
|
+
};
|
|
448
|
+
const enqueueTerminal = (item) => {
|
|
449
|
+
if (consumerClosed) return;
|
|
450
|
+
queue.push(item);
|
|
451
|
+
wakeWaitingConsumer();
|
|
452
|
+
};
|
|
453
|
+
const enqueueEvent = async (value) => {
|
|
454
|
+
while (queuedEventCount >= maxReadAhead && !consumerClosed && !options.signal?.aborted) {
|
|
455
|
+
await new Promise((resolve) => {
|
|
456
|
+
wakeProducer = resolve;
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (consumerClosed || options.signal?.aborted) return false;
|
|
460
|
+
queue.push({ kind: "event", value });
|
|
461
|
+
queuedEventCount += 1;
|
|
462
|
+
wakeWaitingConsumer();
|
|
463
|
+
return true;
|
|
464
|
+
};
|
|
465
|
+
const waitForReadCapacity = async () => {
|
|
466
|
+
while (queuedEventCount >= maxReadAhead && !consumerClosed && !options.signal?.aborted) {
|
|
467
|
+
await new Promise((resolve) => {
|
|
468
|
+
wakeProducer = resolve;
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
return !consumerClosed && !options.signal?.aborted;
|
|
472
|
+
};
|
|
473
|
+
const reader = body.getReader();
|
|
474
|
+
const framer = createMatrxNdjsonFramer(options);
|
|
475
|
+
const onAbort = () => {
|
|
476
|
+
wakeWaitingProducer();
|
|
477
|
+
wakeWaitingConsumer();
|
|
478
|
+
void reader.cancel(options.signal?.reason).catch(() => void 0);
|
|
479
|
+
};
|
|
480
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
481
|
+
if (options.signal?.aborted) onAbort();
|
|
482
|
+
const readerPromise = (async () => {
|
|
483
|
+
try {
|
|
484
|
+
while (!options.signal?.aborted && !consumerClosed) {
|
|
485
|
+
if (!await waitForReadCapacity()) return;
|
|
486
|
+
const { value, done } = await reader.read();
|
|
487
|
+
if (done) break;
|
|
488
|
+
for (const envelope of framer.pushBytes(value)) {
|
|
489
|
+
if (!await enqueueEvent(envelope)) return;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (!options.signal?.aborted && !consumerClosed) {
|
|
493
|
+
for (const envelope of framer.finish()) {
|
|
494
|
+
if (!await enqueueEvent(envelope)) return;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
} catch (error) {
|
|
498
|
+
const aborted = options.signal?.aborted || consumerClosed || error instanceof Error && error.name === "AbortError";
|
|
499
|
+
if (!aborted) enqueueTerminal({ kind: "error", error });
|
|
500
|
+
} finally {
|
|
501
|
+
readerFinished = true;
|
|
502
|
+
reader.releaseLock();
|
|
503
|
+
enqueueTerminal({ kind: "done" });
|
|
504
|
+
}
|
|
505
|
+
})();
|
|
506
|
+
try {
|
|
507
|
+
while (true) {
|
|
508
|
+
if (queue.length === 0) {
|
|
509
|
+
if (options.signal?.aborted || readerFinished) return;
|
|
510
|
+
await new Promise((resolve) => {
|
|
511
|
+
wakeConsumer = resolve;
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
const item = queue.shift();
|
|
515
|
+
if (item?.kind === "event") queuedEventCount -= 1;
|
|
516
|
+
wakeWaitingProducer();
|
|
517
|
+
if (!item || item.kind === "done") return;
|
|
518
|
+
if (item.kind === "error") throw item.error;
|
|
519
|
+
yield item.value;
|
|
520
|
+
}
|
|
521
|
+
} finally {
|
|
522
|
+
consumerClosed = true;
|
|
523
|
+
wakeWaitingProducer();
|
|
524
|
+
wakeWaitingConsumer();
|
|
525
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
526
|
+
if (!readerFinished) {
|
|
527
|
+
await reader.cancel().catch(() => void 0);
|
|
528
|
+
}
|
|
529
|
+
await readerPromise;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// matrx/internal.ts
|
|
534
|
+
function encodePathSegment(value) {
|
|
535
|
+
return encodeURIComponent(value);
|
|
536
|
+
}
|
|
537
|
+
function buildQuery(params) {
|
|
538
|
+
const search = new URLSearchParams();
|
|
539
|
+
for (const [key, value] of Object.entries(params)) {
|
|
540
|
+
if (value === void 0) continue;
|
|
541
|
+
if (Array.isArray(value)) {
|
|
542
|
+
for (const entry of value) search.append(key, entry);
|
|
543
|
+
} else {
|
|
544
|
+
search.append(key, String(value));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const encoded = search.toString();
|
|
548
|
+
return encoded ? `?${encoded}` : "";
|
|
549
|
+
}
|
|
550
|
+
async function readServerDetail(response) {
|
|
551
|
+
try {
|
|
552
|
+
return await response.json();
|
|
553
|
+
} catch {
|
|
554
|
+
return void 0;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
async function throwApiError(path, response) {
|
|
558
|
+
throw new MatrxApiError({
|
|
559
|
+
status: response.status,
|
|
560
|
+
path,
|
|
561
|
+
serverDetail: await readServerDetail(response)
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
async function requestJson(transport, path, options) {
|
|
565
|
+
const hasBody = options.method !== "GET" && options.body !== void 0;
|
|
566
|
+
const response = await transport.fetch(path, {
|
|
567
|
+
method: options.method,
|
|
568
|
+
headers: hasBody ? { "Content-Type": "application/json" } : {},
|
|
569
|
+
...hasBody ? { body: JSON.stringify(options.body) } : {},
|
|
570
|
+
...options.signal ? { signal: options.signal } : {}
|
|
571
|
+
});
|
|
572
|
+
if (!response.ok) return throwApiError(path, response);
|
|
573
|
+
return await response.json();
|
|
574
|
+
}
|
|
575
|
+
function toRunHandle(response, options) {
|
|
576
|
+
return {
|
|
577
|
+
requestId: response.headers.get("X-Request-ID"),
|
|
578
|
+
conversationId: response.headers.get("X-Conversation-ID"),
|
|
579
|
+
events: readMatrxNdjsonStream(response.body, {
|
|
580
|
+
...options.signal ? { signal: options.signal } : {},
|
|
581
|
+
...options.maxReadAhead !== void 0 ? { maxReadAhead: options.maxReadAhead } : {},
|
|
582
|
+
...options.onMalformedLine ? { onMalformedLine: options.onMalformedLine } : {},
|
|
583
|
+
...options.onUnknownEnvelope ? { onUnknownEnvelope: options.onUnknownEnvelope } : {},
|
|
584
|
+
...options.onValidEnvelope ? { onValidEnvelope: options.onValidEnvelope } : {}
|
|
585
|
+
}),
|
|
586
|
+
response
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
async function requestStream(transport, path, options) {
|
|
590
|
+
const hasBody = options.method !== "GET" && options.body !== void 0;
|
|
591
|
+
const response = await transport.fetch(path, {
|
|
592
|
+
method: options.method,
|
|
593
|
+
headers: {
|
|
594
|
+
...hasBody ? { "Content-Type": "application/json" } : {},
|
|
595
|
+
...options.headers
|
|
596
|
+
},
|
|
597
|
+
...hasBody ? { body: JSON.stringify(options.body) } : {},
|
|
598
|
+
...options.signal ? { signal: options.signal } : {}
|
|
599
|
+
});
|
|
600
|
+
if (!response.ok) return throwApiError(path, response);
|
|
601
|
+
if (!response.body) {
|
|
602
|
+
throw new MatrxApiError({
|
|
603
|
+
status: response.status,
|
|
604
|
+
path,
|
|
605
|
+
serverDetail: { code: "missing_response_body" },
|
|
606
|
+
message: "The streaming response carried no body."
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
return response;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// matrx/run.ts
|
|
613
|
+
async function streamCall(transport, path, body, options) {
|
|
614
|
+
const response = await requestStream(transport, path, {
|
|
615
|
+
method: "POST",
|
|
616
|
+
// This client IS the streaming path — `stream: true` always, last so a
|
|
617
|
+
// caller-supplied value can never flip the response off NDJSON.
|
|
618
|
+
body: { ...body, stream: true },
|
|
619
|
+
...options.signal ? { signal: options.signal } : {}
|
|
620
|
+
});
|
|
621
|
+
return toRunHandle(response, options);
|
|
622
|
+
}
|
|
623
|
+
function startAgentRun(transport, agentId, request, options = {}) {
|
|
624
|
+
return streamCall(
|
|
625
|
+
transport,
|
|
626
|
+
`/ai/agents/${encodePathSegment(agentId)}`,
|
|
627
|
+
request,
|
|
628
|
+
options
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
function continueAgentConversation(transport, conversationId, request, options = {}) {
|
|
632
|
+
return streamCall(
|
|
633
|
+
transport,
|
|
634
|
+
`/ai/conversations/${encodePathSegment(conversationId)}`,
|
|
635
|
+
request,
|
|
636
|
+
options
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
function cancelAgentRun(transport, requestId, options = {}) {
|
|
640
|
+
const query = buildQuery(
|
|
641
|
+
options.mode === "interrupt" ? { mode: "interrupt" } : {}
|
|
642
|
+
);
|
|
643
|
+
return requestJson(
|
|
644
|
+
transport,
|
|
645
|
+
`/ai/cancel/${encodePathSegment(requestId)}${query}`,
|
|
646
|
+
{
|
|
647
|
+
method: "POST",
|
|
648
|
+
...options.signal ? { signal: options.signal } : {}
|
|
649
|
+
}
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// stream/sse.ts
|
|
654
|
+
var FRAME_SEPARATOR = /\r\n\r\n|\n\n|\r\r/;
|
|
655
|
+
var LINE_SEPARATOR = /\r\n|\n|\r/;
|
|
656
|
+
function parseMatrxSseFrame(frame) {
|
|
657
|
+
let event = "message";
|
|
658
|
+
let id = null;
|
|
659
|
+
const dataLines = [];
|
|
660
|
+
let sawData = false;
|
|
661
|
+
for (const line of frame.split(LINE_SEPARATOR)) {
|
|
662
|
+
if (line.startsWith(":")) continue;
|
|
663
|
+
if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
664
|
+
else if (line.startsWith("data:")) {
|
|
665
|
+
sawData = true;
|
|
666
|
+
dataLines.push(line.slice(5).replace(/^ /, ""));
|
|
667
|
+
} else if (line.startsWith("id:")) id = line.slice(3).trim();
|
|
668
|
+
}
|
|
669
|
+
const seqCandidate = id !== null && id !== "" ? Number(id) : NaN;
|
|
670
|
+
const seq = Number.isSafeInteger(seqCandidate) && seqCandidate >= 0 ? seqCandidate : null;
|
|
671
|
+
return { event, id, seq, data: sawData ? dataLines.join("\n") : null };
|
|
672
|
+
}
|
|
673
|
+
function createMatrxSseFramer() {
|
|
674
|
+
let buffer = "";
|
|
675
|
+
return {
|
|
676
|
+
push(chunk) {
|
|
677
|
+
buffer += chunk;
|
|
678
|
+
const frames = [];
|
|
679
|
+
for (; ; ) {
|
|
680
|
+
const sep = FRAME_SEPARATOR.exec(buffer);
|
|
681
|
+
if (sep === null) break;
|
|
682
|
+
const frame = buffer.slice(0, sep.index);
|
|
683
|
+
buffer = buffer.slice(sep.index + sep[0].length);
|
|
684
|
+
frames.push(parseMatrxSseFrame(frame));
|
|
685
|
+
}
|
|
686
|
+
return frames;
|
|
687
|
+
},
|
|
688
|
+
flush() {
|
|
689
|
+
const rest = buffer;
|
|
690
|
+
buffer = "";
|
|
691
|
+
return { incomplete: rest.length > 0 ? rest : null };
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
async function* readMatrxSseStream(stream, options = {}) {
|
|
696
|
+
const reader = stream.getReader();
|
|
697
|
+
const decoder = new TextDecoder();
|
|
698
|
+
const framer = createMatrxSseFramer();
|
|
699
|
+
try {
|
|
700
|
+
for (; ; ) {
|
|
701
|
+
const { value, done } = await reader.read();
|
|
702
|
+
if (done) break;
|
|
703
|
+
const frames = framer.push(decoder.decode(value, { stream: true }));
|
|
704
|
+
for (const frame of frames) yield frame;
|
|
705
|
+
}
|
|
706
|
+
const tail = framer.push(decoder.decode());
|
|
707
|
+
for (const frame of tail) yield frame;
|
|
708
|
+
const { incomplete } = framer.flush();
|
|
709
|
+
if (incomplete !== null) options.onIncomplete?.(incomplete);
|
|
710
|
+
} finally {
|
|
711
|
+
reader.releaseLock();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// matrx/operations.ts
|
|
716
|
+
var RUNTIME_STATUSES = /* @__PURE__ */ new Set([
|
|
717
|
+
"pending",
|
|
718
|
+
"running",
|
|
719
|
+
"paused",
|
|
720
|
+
"waiting_input",
|
|
721
|
+
"completed",
|
|
722
|
+
"failed",
|
|
723
|
+
"cancelled"
|
|
724
|
+
]);
|
|
725
|
+
function parseEndStatus(data) {
|
|
726
|
+
if (data === null) return null;
|
|
727
|
+
try {
|
|
728
|
+
const parsed = JSON.parse(data);
|
|
729
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed.status === "string") {
|
|
730
|
+
const status = parsed.status;
|
|
731
|
+
return RUNTIME_STATUSES.has(status) ? status : null;
|
|
732
|
+
}
|
|
733
|
+
} catch {
|
|
734
|
+
}
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
async function* followRuntimeOperationEvents(transport, executionId, options = {}) {
|
|
738
|
+
let cursor = options.lastEventSeq ?? 0;
|
|
739
|
+
const headers = { Accept: "text/event-stream" };
|
|
740
|
+
if (cursor > 0) headers["Last-Event-ID"] = String(cursor);
|
|
741
|
+
const response = await requestStream(
|
|
742
|
+
transport,
|
|
743
|
+
`/runtime/executions/${encodePathSegment(executionId)}/events/stream`,
|
|
744
|
+
{
|
|
745
|
+
method: "GET",
|
|
746
|
+
headers,
|
|
747
|
+
...options.signal ? { signal: options.signal } : {}
|
|
748
|
+
}
|
|
749
|
+
);
|
|
750
|
+
const frames = readMatrxSseStream(
|
|
751
|
+
response.body,
|
|
752
|
+
options.onIncomplete ? { onIncomplete: options.onIncomplete } : {}
|
|
753
|
+
);
|
|
754
|
+
for await (const frame of frames) {
|
|
755
|
+
if (frame.event === "end") {
|
|
756
|
+
yield { type: "end", status: parseEndStatus(frame.data), cursor };
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (frame.event === "execution_event" && frame.data !== null) {
|
|
760
|
+
let event;
|
|
761
|
+
try {
|
|
762
|
+
event = JSON.parse(frame.data);
|
|
763
|
+
} catch (error) {
|
|
764
|
+
options.onMalformedFrame?.(frame, error);
|
|
765
|
+
yield { type: "liveness", cursor };
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
if (frame.seq !== null && frame.seq > cursor) cursor = frame.seq;
|
|
769
|
+
yield { type: "event", event, seq: frame.seq, cursor };
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
yield { type: "liveness", cursor };
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
async function followRuntimeOperationToEnd(transport, executionId, options) {
|
|
776
|
+
const stallTimeoutMs = options.stallTimeoutMs ?? 45e3;
|
|
777
|
+
const reconnectLimit = options.reconnectLimit ?? 60;
|
|
778
|
+
const reconnectDelayMs = options.reconnectDelayMs ?? 2e3;
|
|
779
|
+
const outer = options.signal;
|
|
780
|
+
let cursor = options.lastEventSeq ?? 0;
|
|
781
|
+
let failures = 0;
|
|
782
|
+
while (!outer?.aborted && failures < reconnectLimit) {
|
|
783
|
+
const attempt = new AbortController();
|
|
784
|
+
const onOuterAbort = () => attempt.abort();
|
|
785
|
+
outer?.addEventListener("abort", onOuterAbort, { once: true });
|
|
786
|
+
let stallTimer = null;
|
|
787
|
+
const armStall = () => {
|
|
788
|
+
if (stallTimer !== null) clearTimeout(stallTimer);
|
|
789
|
+
stallTimer = setTimeout(() => attempt.abort(), stallTimeoutMs);
|
|
790
|
+
};
|
|
791
|
+
try {
|
|
792
|
+
const items = followRuntimeOperationEvents(transport, executionId, {
|
|
793
|
+
lastEventSeq: cursor,
|
|
794
|
+
signal: attempt.signal,
|
|
795
|
+
...options.onMalformedFrame ? { onMalformedFrame: options.onMalformedFrame } : {}
|
|
796
|
+
});
|
|
797
|
+
armStall();
|
|
798
|
+
for await (const item of items) {
|
|
799
|
+
armStall();
|
|
800
|
+
failures = 0;
|
|
801
|
+
cursor = item.cursor;
|
|
802
|
+
if (item.type === "end") {
|
|
803
|
+
return { ended: true, status: item.status };
|
|
804
|
+
}
|
|
805
|
+
if (item.type === "event") {
|
|
806
|
+
options.onEvent(item.event, item.seq);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
failures += 1;
|
|
810
|
+
} catch (error) {
|
|
811
|
+
if (outer?.aborted) break;
|
|
812
|
+
failures += 1;
|
|
813
|
+
options.onAttemptError?.(error);
|
|
814
|
+
} finally {
|
|
815
|
+
if (stallTimer !== null) clearTimeout(stallTimer);
|
|
816
|
+
outer?.removeEventListener("abort", onOuterAbort);
|
|
817
|
+
}
|
|
818
|
+
if (!outer?.aborted && failures < reconnectLimit) {
|
|
819
|
+
await new Promise((r) => setTimeout(r, reconnectDelayMs));
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return { ended: false, status: null };
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// react/use-agent-run.ts
|
|
826
|
+
function useAgentRun(options) {
|
|
827
|
+
const [phase, setPhase] = useState("idle");
|
|
828
|
+
const [projection, setProjection] = useState(
|
|
829
|
+
null
|
|
830
|
+
);
|
|
831
|
+
const [error, setError] = useState(null);
|
|
832
|
+
const optionsRef = useRef(options);
|
|
833
|
+
optionsRef.current = options;
|
|
834
|
+
const runSeqRef = useRef(0);
|
|
835
|
+
const controllerRef = useRef(null);
|
|
836
|
+
const requestIdRef = useRef(null);
|
|
837
|
+
const transportRef = useRef(null);
|
|
838
|
+
const abort = useCallback(() => {
|
|
839
|
+
controllerRef.current?.abort();
|
|
840
|
+
}, []);
|
|
841
|
+
useEffect(() => () => controllerRef.current?.abort(), []);
|
|
842
|
+
const runStream = useCallback(
|
|
843
|
+
async (open, seedConversationId) => {
|
|
844
|
+
const seq = ++runSeqRef.current;
|
|
845
|
+
const isCurrent = () => runSeqRef.current === seq;
|
|
846
|
+
controllerRef.current?.abort();
|
|
847
|
+
const controller = new AbortController();
|
|
848
|
+
controllerRef.current = controller;
|
|
849
|
+
requestIdRef.current = null;
|
|
850
|
+
const opts = optionsRef.current;
|
|
851
|
+
const transport = typeof opts.transport === "function" ? opts.transport() : opts.transport;
|
|
852
|
+
transportRef.current = transport;
|
|
853
|
+
setPhase("starting");
|
|
854
|
+
setError(null);
|
|
855
|
+
setProjection(null);
|
|
856
|
+
try {
|
|
857
|
+
const handle = await open(transport, {
|
|
858
|
+
...opts.streamOptions,
|
|
859
|
+
signal: controller.signal
|
|
860
|
+
});
|
|
861
|
+
requestIdRef.current = handle.requestId;
|
|
862
|
+
let current = createAgentRequestProjection({
|
|
863
|
+
requestId: handle.requestId ?? crypto.randomUUID(),
|
|
864
|
+
conversationId: handle.conversationId ?? seedConversationId
|
|
865
|
+
});
|
|
866
|
+
if (isCurrent()) {
|
|
867
|
+
setPhase("streaming");
|
|
868
|
+
setProjection(current);
|
|
869
|
+
}
|
|
870
|
+
for await (const envelope of handle.events) {
|
|
871
|
+
current = projectAgentEvent(current, envelope);
|
|
872
|
+
if (isCurrent()) {
|
|
873
|
+
setProjection(current);
|
|
874
|
+
optionsRef.current.onProjection?.(current);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (isCurrent()) {
|
|
878
|
+
setPhase(
|
|
879
|
+
current.status === "error" ? "error" : current.status === "cancelled" ? "cancelled" : "complete"
|
|
880
|
+
);
|
|
881
|
+
if (current.status === "error") {
|
|
882
|
+
const failure = {
|
|
883
|
+
type: "unknown",
|
|
884
|
+
message: typeof current.error?.user_message === "string" && current.error.user_message || typeof current.error?.message === "string" && current.error.message || "The agent run failed",
|
|
885
|
+
serverDetail: current.error
|
|
886
|
+
};
|
|
887
|
+
setError(failure);
|
|
888
|
+
optionsRef.current.onError?.(failure);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return current;
|
|
892
|
+
} catch (err) {
|
|
893
|
+
const failure = normalizeMatrxError(err);
|
|
894
|
+
if (isCurrent()) {
|
|
895
|
+
setPhase(failure.type === "abort_error" ? "cancelled" : "error");
|
|
896
|
+
if (failure.type !== "abort_error") {
|
|
897
|
+
setError(failure);
|
|
898
|
+
optionsRef.current.onError?.(failure);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
throw err;
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
[]
|
|
905
|
+
);
|
|
906
|
+
const start = useCallback(
|
|
907
|
+
(agentId, request) => runStream(
|
|
908
|
+
(transport, callOptions) => startAgentRun(transport, agentId, request, callOptions),
|
|
909
|
+
request.conversation_id
|
|
910
|
+
),
|
|
911
|
+
[runStream]
|
|
912
|
+
);
|
|
913
|
+
const continueConversation = useCallback(
|
|
914
|
+
(conversationId, request) => runStream(
|
|
915
|
+
(transport, callOptions) => continueAgentConversation(
|
|
916
|
+
transport,
|
|
917
|
+
conversationId,
|
|
918
|
+
request,
|
|
919
|
+
callOptions
|
|
920
|
+
),
|
|
921
|
+
conversationId
|
|
922
|
+
),
|
|
923
|
+
[runStream]
|
|
924
|
+
);
|
|
925
|
+
const cancel = useCallback(
|
|
926
|
+
async (mode = "cancel") => {
|
|
927
|
+
const requestId = requestIdRef.current;
|
|
928
|
+
const transport = transportRef.current;
|
|
929
|
+
if (!requestId || !transport) return null;
|
|
930
|
+
return cancelAgentRun(transport, requestId, { mode });
|
|
931
|
+
},
|
|
932
|
+
[]
|
|
933
|
+
);
|
|
934
|
+
const reset = useCallback(() => {
|
|
935
|
+
runSeqRef.current += 1;
|
|
936
|
+
controllerRef.current?.abort();
|
|
937
|
+
controllerRef.current = null;
|
|
938
|
+
requestIdRef.current = null;
|
|
939
|
+
setPhase("idle");
|
|
940
|
+
setProjection(null);
|
|
941
|
+
setError(null);
|
|
942
|
+
}, []);
|
|
943
|
+
return {
|
|
944
|
+
phase,
|
|
945
|
+
projection,
|
|
946
|
+
requestId: projection?.requestId ?? null,
|
|
947
|
+
conversationId: projection?.conversationId ?? null,
|
|
948
|
+
error,
|
|
949
|
+
start,
|
|
950
|
+
continueConversation,
|
|
951
|
+
cancel,
|
|
952
|
+
abort,
|
|
953
|
+
reset
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// react/use-follow-runtime-operation.ts
|
|
958
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
959
|
+
function useFollowRuntimeOperation(options) {
|
|
960
|
+
const [following, setFollowing] = useState2(false);
|
|
961
|
+
const [ended, setEnded] = useState2(false);
|
|
962
|
+
const [status, setStatus] = useState2(
|
|
963
|
+
null
|
|
964
|
+
);
|
|
965
|
+
const optionsRef = useRef2(options);
|
|
966
|
+
optionsRef.current = options;
|
|
967
|
+
const { executionId, enabled = true } = options;
|
|
968
|
+
const active = enabled && !!executionId;
|
|
969
|
+
const follow = useCallback2(
|
|
970
|
+
(id, signal, isCurrent) => {
|
|
971
|
+
const opts = optionsRef.current;
|
|
972
|
+
const transport = typeof opts.transport === "function" ? opts.transport() : opts.transport;
|
|
973
|
+
return followRuntimeOperationToEnd(transport, id, {
|
|
974
|
+
signal,
|
|
975
|
+
...opts.lastEventSeq !== void 0 ? { lastEventSeq: opts.lastEventSeq } : {},
|
|
976
|
+
...opts.stallTimeoutMs !== void 0 ? { stallTimeoutMs: opts.stallTimeoutMs } : {},
|
|
977
|
+
...opts.reconnectLimit !== void 0 ? { reconnectLimit: opts.reconnectLimit } : {},
|
|
978
|
+
...opts.reconnectDelayMs !== void 0 ? { reconnectDelayMs: opts.reconnectDelayMs } : {},
|
|
979
|
+
onEvent: (event, seq) => {
|
|
980
|
+
if (isCurrent()) optionsRef.current.onEvent?.(event, seq);
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
},
|
|
984
|
+
[]
|
|
985
|
+
);
|
|
986
|
+
useEffect2(() => {
|
|
987
|
+
if (!active || !executionId) return;
|
|
988
|
+
let current = true;
|
|
989
|
+
const controller = new AbortController();
|
|
990
|
+
setFollowing(true);
|
|
991
|
+
setEnded(false);
|
|
992
|
+
setStatus(null);
|
|
993
|
+
void follow(executionId, controller.signal, () => current).then((result) => {
|
|
994
|
+
if (!current) return;
|
|
995
|
+
setFollowing(false);
|
|
996
|
+
setEnded(result.ended);
|
|
997
|
+
setStatus(result.status);
|
|
998
|
+
optionsRef.current.onSettled?.(result);
|
|
999
|
+
}).catch(() => {
|
|
1000
|
+
if (!current) return;
|
|
1001
|
+
setFollowing(false);
|
|
1002
|
+
optionsRef.current.onSettled?.({ ended: false, status: null });
|
|
1003
|
+
});
|
|
1004
|
+
return () => {
|
|
1005
|
+
current = false;
|
|
1006
|
+
controller.abort();
|
|
1007
|
+
setFollowing(false);
|
|
1008
|
+
};
|
|
1009
|
+
}, [active, executionId, follow]);
|
|
1010
|
+
return { following, ended, status };
|
|
1011
|
+
}
|
|
1012
|
+
export {
|
|
1013
|
+
useAgentRun,
|
|
1014
|
+
useFollowRuntimeOperation
|
|
1015
|
+
};
|
|
1016
|
+
//# sourceMappingURL=index.js.map
|