@octavus/client-sdk 2.18.0 → 2.20.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/dist/index.cjs +2050 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +884 -0
- package/package.json +12 -5
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2050 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AppError: () => import_core5.AppError,
|
|
24
|
+
ConflictError: () => import_core5.ConflictError,
|
|
25
|
+
ForbiddenError: () => import_core5.ForbiddenError,
|
|
26
|
+
MAIN_THREAD: () => import_core5.MAIN_THREAD,
|
|
27
|
+
NotFoundError: () => import_core5.NotFoundError,
|
|
28
|
+
OCTAVUS_SKILL_TOOLS: () => import_core5.OCTAVUS_SKILL_TOOLS,
|
|
29
|
+
OctavusChat: () => OctavusChat,
|
|
30
|
+
OctavusError: () => import_core5.OctavusError,
|
|
31
|
+
ValidationError: () => import_core5.ValidationError,
|
|
32
|
+
createApiErrorEvent: () => import_core5.createApiErrorEvent,
|
|
33
|
+
createErrorEvent: () => import_core5.createErrorEvent,
|
|
34
|
+
createHttpTransport: () => createHttpTransport,
|
|
35
|
+
createInternalErrorEvent: () => import_core5.createInternalErrorEvent,
|
|
36
|
+
createPollingTransport: () => createPollingTransport,
|
|
37
|
+
createSocketTransport: () => createSocketTransport,
|
|
38
|
+
errorToStreamEvent: () => import_core5.errorToStreamEvent,
|
|
39
|
+
generateId: () => import_core5.generateId,
|
|
40
|
+
getSkillSlugFromToolCall: () => import_core5.getSkillSlugFromToolCall,
|
|
41
|
+
isAbortError: () => import_core5.isAbortError,
|
|
42
|
+
isAuthenticationError: () => import_core5.isAuthenticationError,
|
|
43
|
+
isFileReference: () => import_core5.isFileReference,
|
|
44
|
+
isFileReferenceArray: () => import_core5.isFileReferenceArray,
|
|
45
|
+
isMainThread: () => import_core5.isMainThread,
|
|
46
|
+
isOctavusSkillTool: () => import_core5.isOctavusSkillTool,
|
|
47
|
+
isOtherThread: () => import_core5.isOtherThread,
|
|
48
|
+
isProviderError: () => import_core5.isProviderError,
|
|
49
|
+
isRateLimitError: () => import_core5.isRateLimitError,
|
|
50
|
+
isRetryableError: () => import_core5.isRetryableError,
|
|
51
|
+
isSocketTransport: () => isSocketTransport,
|
|
52
|
+
isToolError: () => import_core5.isToolError,
|
|
53
|
+
isValidationError: () => import_core5.isValidationError,
|
|
54
|
+
parseSSEStream: () => parseSSEStream,
|
|
55
|
+
resolveThread: () => import_core5.resolveThread,
|
|
56
|
+
safeParseStreamEvent: () => import_core5.safeParseStreamEvent,
|
|
57
|
+
safeParseUIMessage: () => import_core5.safeParseUIMessage,
|
|
58
|
+
safeParseUIMessages: () => import_core5.safeParseUIMessages,
|
|
59
|
+
threadForPart: () => import_core5.threadForPart,
|
|
60
|
+
uploadFiles: () => uploadFiles
|
|
61
|
+
});
|
|
62
|
+
module.exports = __toCommonJS(index_exports);
|
|
63
|
+
|
|
64
|
+
// src/chat.ts
|
|
65
|
+
var import_core = require("@octavus/core");
|
|
66
|
+
|
|
67
|
+
// src/files.ts
|
|
68
|
+
var UPLOAD_DEFAULTS = {
|
|
69
|
+
timeoutMs: 6e4,
|
|
70
|
+
maxRetries: 2,
|
|
71
|
+
retryDelayMs: 1e3
|
|
72
|
+
};
|
|
73
|
+
var UploadError = class extends Error {
|
|
74
|
+
constructor(message, retryable, status) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.retryable = retryable;
|
|
77
|
+
this.status = status;
|
|
78
|
+
this.name = "UploadError";
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
function uploadFileWithProgress(url, file, onProgress, timeoutMs) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const xhr = new XMLHttpRequest();
|
|
84
|
+
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
85
|
+
xhr.timeout = timeoutMs;
|
|
86
|
+
xhr.addEventListener("timeout", () => {
|
|
87
|
+
reject(new UploadError(`Upload timed out after ${timeoutMs}ms`, true));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
91
|
+
if (event.lengthComputable) {
|
|
92
|
+
const progress = Math.round(event.loaded / event.total * 100);
|
|
93
|
+
onProgress?.(progress);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
xhr.addEventListener("load", () => {
|
|
97
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
98
|
+
resolve();
|
|
99
|
+
} else {
|
|
100
|
+
const detail = xhr.responseText ? `: ${xhr.responseText}` : "";
|
|
101
|
+
const retryable = xhr.status >= 500 || xhr.status === 429;
|
|
102
|
+
reject(
|
|
103
|
+
new UploadError(
|
|
104
|
+
`Upload failed with status ${xhr.status}${detail}`,
|
|
105
|
+
retryable,
|
|
106
|
+
xhr.status
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
xhr.addEventListener("error", () => {
|
|
112
|
+
reject(new UploadError("Upload failed: network error", true));
|
|
113
|
+
});
|
|
114
|
+
xhr.addEventListener("abort", () => {
|
|
115
|
+
reject(new UploadError("Upload aborted", false));
|
|
116
|
+
});
|
|
117
|
+
xhr.open("PUT", url);
|
|
118
|
+
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
|
119
|
+
xhr.send(file);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function delay(ms) {
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
setTimeout(resolve, ms);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async function uploadFileWithRetry(url, file, onProgress, timeoutMs, maxRetries, retryDelayMs) {
|
|
128
|
+
let lastError;
|
|
129
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
130
|
+
try {
|
|
131
|
+
await uploadFileWithProgress(url, file, onProgress, timeoutMs);
|
|
132
|
+
return;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
lastError = err;
|
|
135
|
+
const isRetryable = err instanceof UploadError && err.retryable;
|
|
136
|
+
if (!isRetryable || attempt >= maxRetries) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
onProgress?.(0);
|
|
140
|
+
await delay(retryDelayMs);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
throw lastError;
|
|
144
|
+
}
|
|
145
|
+
async function uploadFiles(files, options) {
|
|
146
|
+
const fileArray = Array.from(files);
|
|
147
|
+
if (fileArray.length === 0) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
const timeoutMs = options.timeoutMs ?? UPLOAD_DEFAULTS.timeoutMs;
|
|
151
|
+
const maxRetries = options.maxRetries ?? UPLOAD_DEFAULTS.maxRetries;
|
|
152
|
+
const retryDelayMs = options.retryDelayMs ?? UPLOAD_DEFAULTS.retryDelayMs;
|
|
153
|
+
const { files: uploadInfos } = await options.requestUploadUrls(
|
|
154
|
+
fileArray.map((f) => ({
|
|
155
|
+
filename: f.name,
|
|
156
|
+
mediaType: f.type || "application/octet-stream",
|
|
157
|
+
size: f.size
|
|
158
|
+
}))
|
|
159
|
+
);
|
|
160
|
+
const references = [];
|
|
161
|
+
for (let i = 0; i < fileArray.length; i++) {
|
|
162
|
+
const file = fileArray[i];
|
|
163
|
+
const uploadInfo = uploadInfos[i];
|
|
164
|
+
await uploadFileWithRetry(
|
|
165
|
+
uploadInfo.uploadUrl,
|
|
166
|
+
file,
|
|
167
|
+
options.onProgress ? (progress) => options.onProgress(i, progress) : void 0,
|
|
168
|
+
timeoutMs,
|
|
169
|
+
maxRetries,
|
|
170
|
+
retryDelayMs
|
|
171
|
+
);
|
|
172
|
+
references.push({
|
|
173
|
+
id: uploadInfo.id,
|
|
174
|
+
mediaType: file.type || "application/octet-stream",
|
|
175
|
+
url: uploadInfo.downloadUrl,
|
|
176
|
+
filename: file.name,
|
|
177
|
+
size: file.size
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return references;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/chat.ts
|
|
184
|
+
var OPERATION_BLOCK_TYPES = /* @__PURE__ */ new Set(["set-resource", "serialize-thread", "generate-image"]);
|
|
185
|
+
function createUserMessage(input, files) {
|
|
186
|
+
const parts = [];
|
|
187
|
+
if (files && files.length > 0) {
|
|
188
|
+
for (const file of files) {
|
|
189
|
+
parts.push({
|
|
190
|
+
type: "file",
|
|
191
|
+
id: file.id,
|
|
192
|
+
mediaType: file.mediaType,
|
|
193
|
+
url: file.url,
|
|
194
|
+
filename: file.filename,
|
|
195
|
+
size: file.size
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (input.content !== void 0) {
|
|
200
|
+
if (typeof input.content === "string") {
|
|
201
|
+
parts.push({ type: "text", text: input.content, status: "done" });
|
|
202
|
+
} else {
|
|
203
|
+
const typeName = input.content.type ?? "object";
|
|
204
|
+
parts.push({
|
|
205
|
+
type: "object",
|
|
206
|
+
id: (0, import_core.generateId)(),
|
|
207
|
+
typeName,
|
|
208
|
+
object: input.content,
|
|
209
|
+
status: "done"
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
id: (0, import_core.generateId)(),
|
|
215
|
+
role: "user",
|
|
216
|
+
parts,
|
|
217
|
+
status: "done",
|
|
218
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function parsePartialJson(jsonText) {
|
|
222
|
+
if (!jsonText.trim()) {
|
|
223
|
+
return void 0;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
return JSON.parse(jsonText);
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
let fixed = jsonText;
|
|
230
|
+
let openBraces = 0;
|
|
231
|
+
let openBrackets = 0;
|
|
232
|
+
let inString = false;
|
|
233
|
+
let escaped = false;
|
|
234
|
+
for (const char of fixed) {
|
|
235
|
+
if (escaped) {
|
|
236
|
+
escaped = false;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (char === "\\") {
|
|
240
|
+
escaped = true;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (char === '"') {
|
|
244
|
+
inString = !inString;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (!inString) {
|
|
248
|
+
if (char === "{") openBraces += 1;
|
|
249
|
+
else if (char === "}") openBraces -= 1;
|
|
250
|
+
else if (char === "[") openBrackets += 1;
|
|
251
|
+
else if (char === "]") openBrackets -= 1;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (escaped) {
|
|
255
|
+
fixed += "\\";
|
|
256
|
+
}
|
|
257
|
+
if (inString) {
|
|
258
|
+
fixed += '"';
|
|
259
|
+
}
|
|
260
|
+
while (openBrackets > 0) {
|
|
261
|
+
fixed += "]";
|
|
262
|
+
openBrackets -= 1;
|
|
263
|
+
}
|
|
264
|
+
while (openBraces > 0) {
|
|
265
|
+
fixed += "}";
|
|
266
|
+
openBraces -= 1;
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
return JSON.parse(fixed);
|
|
270
|
+
} catch {
|
|
271
|
+
return void 0;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function createEmptyStreamingState() {
|
|
275
|
+
return {
|
|
276
|
+
messageId: (0, import_core.generateId)(),
|
|
277
|
+
parts: [],
|
|
278
|
+
activeBlock: null,
|
|
279
|
+
blocks: /* @__PURE__ */ new Map(),
|
|
280
|
+
currentTextPartIndex: null,
|
|
281
|
+
currentReasoningPartIndex: null,
|
|
282
|
+
currentObjectPartIndex: null,
|
|
283
|
+
accumulatedJson: "",
|
|
284
|
+
activeWorkers: /* @__PURE__ */ new Map(),
|
|
285
|
+
toolInputBuffers: /* @__PURE__ */ new Map()
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function buildMessageFromState(state, status) {
|
|
289
|
+
return {
|
|
290
|
+
id: state.messageId,
|
|
291
|
+
role: "assistant",
|
|
292
|
+
parts: [...state.parts],
|
|
293
|
+
status,
|
|
294
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function finalizeParts(parts, workerError) {
|
|
298
|
+
return parts.map((part) => {
|
|
299
|
+
if (part.type === "text" || part.type === "reasoning") {
|
|
300
|
+
if (part.status === "streaming") {
|
|
301
|
+
return { ...part, status: "done" };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (part.type === "object" && part.status === "streaming") {
|
|
305
|
+
return { ...part, status: "done" };
|
|
306
|
+
}
|
|
307
|
+
if (part.type === "tool-call") {
|
|
308
|
+
if (part.status === "pending" || part.status === "running") {
|
|
309
|
+
return { ...part, status: "cancelled" };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (part.type === "operation" && part.status === "running") {
|
|
313
|
+
return { ...part, status: "cancelled" };
|
|
314
|
+
}
|
|
315
|
+
if (part.type === "worker" && part.status === "running") {
|
|
316
|
+
return {
|
|
317
|
+
...part,
|
|
318
|
+
status: "error",
|
|
319
|
+
error: workerError,
|
|
320
|
+
parts: finalizeParts(part.parts)
|
|
321
|
+
// Recursive for nested parts
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return part;
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
var OctavusChat = class {
|
|
328
|
+
// Private state
|
|
329
|
+
_messages;
|
|
330
|
+
_status = "idle";
|
|
331
|
+
_error = null;
|
|
332
|
+
options;
|
|
333
|
+
transport;
|
|
334
|
+
streamingState = null;
|
|
335
|
+
// Client tool state
|
|
336
|
+
// Keyed by toolName -> array of pending tools for that name
|
|
337
|
+
_pendingToolsByName = /* @__PURE__ */ new Map();
|
|
338
|
+
// Keyed by toolCallId -> pending tool state (for internal lookup when submitting)
|
|
339
|
+
_pendingToolsByCallId = /* @__PURE__ */ new Map();
|
|
340
|
+
// Cache for React useSyncExternalStore compatibility
|
|
341
|
+
_pendingClientToolsCache = {};
|
|
342
|
+
_completedToolResults = [];
|
|
343
|
+
_clientToolAbortController = null;
|
|
344
|
+
// Server tool results from mixed server+client tools (for continuation)
|
|
345
|
+
_serverToolResults = [];
|
|
346
|
+
// Execution ID for continuation (from client-tool-request event)
|
|
347
|
+
_pendingExecutionId = null;
|
|
348
|
+
// Flag indicating automatic client tools have completed and are ready to continue
|
|
349
|
+
// We wait for the finish event before actually continuing to avoid race conditions
|
|
350
|
+
_readyToContinue = false;
|
|
351
|
+
// Flag indicating the finish event with client-tool-calls reason has been received
|
|
352
|
+
// Used to handle the race condition where finish arrives before async tools complete
|
|
353
|
+
_finishEventReceived = false;
|
|
354
|
+
// Tracks whether the rollback anchor has been synced for the current trigger execution.
|
|
355
|
+
// Prevents continuation start events from overwriting the pre-trigger rollback point.
|
|
356
|
+
_rollbackSynced = false;
|
|
357
|
+
// Last trigger snapshot for retry support
|
|
358
|
+
_lastTrigger = null;
|
|
359
|
+
// Listener sets for reactive frameworks
|
|
360
|
+
listeners = /* @__PURE__ */ new Set();
|
|
361
|
+
constructor(options) {
|
|
362
|
+
this.options = options;
|
|
363
|
+
this._messages = options.initialMessages ?? [];
|
|
364
|
+
this.transport = options.transport;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Update mutable options (callbacks and tool handlers) without recreating the instance.
|
|
368
|
+
* Used by the React hook to keep options fresh across renders, but can also be
|
|
369
|
+
* called directly by non-React consumers.
|
|
370
|
+
*
|
|
371
|
+
* `transport` and `initialMessages` are excluded since they're only consumed at construction time.
|
|
372
|
+
*/
|
|
373
|
+
updateOptions(updates) {
|
|
374
|
+
this.options = { ...this.options, ...updates };
|
|
375
|
+
}
|
|
376
|
+
// =========================================================================
|
|
377
|
+
// Public Getters
|
|
378
|
+
// =========================================================================
|
|
379
|
+
get messages() {
|
|
380
|
+
return this._messages;
|
|
381
|
+
}
|
|
382
|
+
get status() {
|
|
383
|
+
return this._status;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* The current error, if any.
|
|
387
|
+
* Contains structured error information including type, source, and retryability.
|
|
388
|
+
*/
|
|
389
|
+
get error() {
|
|
390
|
+
return this._error;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Pending interactive tool calls keyed by tool name.
|
|
394
|
+
* Each tool has bound `submit()` and `cancel()` methods.
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* ```tsx
|
|
398
|
+
* const feedbackTools = pendingClientTools['request-feedback'] ?? [];
|
|
399
|
+
*
|
|
400
|
+
* {feedbackTools.map(tool => (
|
|
401
|
+
* <FeedbackModal
|
|
402
|
+
* key={tool.toolCallId}
|
|
403
|
+
* {...tool.args}
|
|
404
|
+
* onSubmit={(result) => tool.submit(result)}
|
|
405
|
+
* onCancel={() => tool.cancel()}
|
|
406
|
+
* />
|
|
407
|
+
* ))}
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
get pendingClientTools() {
|
|
411
|
+
return this._pendingClientToolsCache;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Whether `retry()` can be called.
|
|
415
|
+
* True when a trigger has been sent and the chat is not currently streaming or awaiting input.
|
|
416
|
+
*/
|
|
417
|
+
get canRetry() {
|
|
418
|
+
return this._lastTrigger !== null && this._status !== "streaming" && this._status !== "awaiting-input";
|
|
419
|
+
}
|
|
420
|
+
// =========================================================================
|
|
421
|
+
// Public State Management
|
|
422
|
+
// =========================================================================
|
|
423
|
+
/**
|
|
424
|
+
* Replace the message list with externally-provided messages.
|
|
425
|
+
*
|
|
426
|
+
* Use this to sync with server-authoritative state (e.g., after an execution
|
|
427
|
+
* completes or when an observer detects new messages from another client).
|
|
428
|
+
*
|
|
429
|
+
* Must NOT be called while streaming — only when status is `idle` or `error`.
|
|
430
|
+
*/
|
|
431
|
+
replaceMessages(messages) {
|
|
432
|
+
this._messages = messages;
|
|
433
|
+
this.notifyListeners();
|
|
434
|
+
}
|
|
435
|
+
// =========================================================================
|
|
436
|
+
// Subscription Methods (for reactive frameworks)
|
|
437
|
+
// =========================================================================
|
|
438
|
+
/**
|
|
439
|
+
* Subscribe to state changes. The callback is called whenever messages, status, or error changes.
|
|
440
|
+
* @returns Unsubscribe function
|
|
441
|
+
*/
|
|
442
|
+
subscribe(listener) {
|
|
443
|
+
this.listeners.add(listener);
|
|
444
|
+
return () => this.listeners.delete(listener);
|
|
445
|
+
}
|
|
446
|
+
notifyListeners() {
|
|
447
|
+
this.listeners.forEach((l) => l());
|
|
448
|
+
}
|
|
449
|
+
// =========================================================================
|
|
450
|
+
// Private Setters (notify listeners)
|
|
451
|
+
// =========================================================================
|
|
452
|
+
setMessages(messages) {
|
|
453
|
+
this._messages = messages;
|
|
454
|
+
this.notifyListeners();
|
|
455
|
+
}
|
|
456
|
+
setStatus(status) {
|
|
457
|
+
this._status = status;
|
|
458
|
+
this.notifyListeners();
|
|
459
|
+
}
|
|
460
|
+
setError(error) {
|
|
461
|
+
this._error = error;
|
|
462
|
+
this.notifyListeners();
|
|
463
|
+
}
|
|
464
|
+
updatePendingClientToolsCache() {
|
|
465
|
+
const cache = {};
|
|
466
|
+
for (const [toolName, tools] of this._pendingToolsByName.entries()) {
|
|
467
|
+
cache[toolName] = tools.map((tool) => ({
|
|
468
|
+
toolCallId: tool.toolCallId,
|
|
469
|
+
toolName: tool.toolName,
|
|
470
|
+
args: tool.args,
|
|
471
|
+
submit: (result) => this.submitToolResult(tool.toolCallId, result),
|
|
472
|
+
cancel: (reason) => this.submitToolResult(tool.toolCallId, void 0, reason ?? "User cancelled")
|
|
473
|
+
}));
|
|
474
|
+
}
|
|
475
|
+
this._pendingClientToolsCache = cache;
|
|
476
|
+
}
|
|
477
|
+
// =========================================================================
|
|
478
|
+
// Public Methods
|
|
479
|
+
// =========================================================================
|
|
480
|
+
/**
|
|
481
|
+
* Trigger the agent and optionally add a user message to the chat.
|
|
482
|
+
*
|
|
483
|
+
* @param triggerName - The trigger name defined in the agent's protocol.yaml
|
|
484
|
+
* @param input - Input parameters for the trigger (variable substitutions)
|
|
485
|
+
* @param options.userMessage - If provided, adds a user message to the chat before triggering
|
|
486
|
+
*
|
|
487
|
+
* @example Send a text message
|
|
488
|
+
* ```typescript
|
|
489
|
+
* await chat.send('user-message',
|
|
490
|
+
* { USER_MESSAGE: message },
|
|
491
|
+
* { userMessage: { content: message } }
|
|
492
|
+
* );
|
|
493
|
+
* ```
|
|
494
|
+
*
|
|
495
|
+
* @example Send a message with file attachments
|
|
496
|
+
* ```typescript
|
|
497
|
+
* await chat.send('user-message',
|
|
498
|
+
* { USER_MESSAGE: message, FILES: fileRefs },
|
|
499
|
+
* { userMessage: { content: message, files: fileRefs } }
|
|
500
|
+
* );
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
async send(triggerName, input, sendOptions) {
|
|
504
|
+
this.transport.stop();
|
|
505
|
+
let fileRefs;
|
|
506
|
+
if (sendOptions?.userMessage?.files) {
|
|
507
|
+
const files = sendOptions.userMessage.files;
|
|
508
|
+
if ((0, import_core.isFileReferenceArray)(files)) {
|
|
509
|
+
fileRefs = files;
|
|
510
|
+
} else if (this.options.requestUploadUrls) {
|
|
511
|
+
fileRefs = await uploadFiles(files, {
|
|
512
|
+
requestUploadUrls: this.options.requestUploadUrls,
|
|
513
|
+
...this.options.uploadOptions
|
|
514
|
+
});
|
|
515
|
+
} else {
|
|
516
|
+
throw new Error(
|
|
517
|
+
"File upload requires requestUploadUrls option. Either provide FileReference[] or configure requestUploadUrls."
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
let processedInput = input;
|
|
522
|
+
if (input?.FILES !== void 0 && !(0, import_core.isFileReferenceArray)(input.FILES)) {
|
|
523
|
+
if (this.options.requestUploadUrls) {
|
|
524
|
+
const inputFiles = input.FILES;
|
|
525
|
+
const uploadedRefs = fileRefs ?? await uploadFiles(inputFiles, {
|
|
526
|
+
requestUploadUrls: this.options.requestUploadUrls,
|
|
527
|
+
...this.options.uploadOptions
|
|
528
|
+
});
|
|
529
|
+
processedInput = { ...input, FILES: uploadedRefs };
|
|
530
|
+
fileRefs = fileRefs ?? uploadedRefs;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
const currentMessages = this._messages;
|
|
534
|
+
this._lastTrigger = {
|
|
535
|
+
triggerName,
|
|
536
|
+
input: processedInput,
|
|
537
|
+
sendOptions: sendOptions?.userMessage ? { userMessage: { content: sendOptions.userMessage.content, files: fileRefs } } : void 0,
|
|
538
|
+
rollbackAfterMessageId: currentMessages[currentMessages.length - 1]?.id ?? null,
|
|
539
|
+
messageCount: currentMessages.length
|
|
540
|
+
};
|
|
541
|
+
if (sendOptions?.userMessage !== void 0) {
|
|
542
|
+
const userMsg = createUserMessage(sendOptions.userMessage, fileRefs);
|
|
543
|
+
this.setMessages([...this._messages, userMsg]);
|
|
544
|
+
}
|
|
545
|
+
await this._executeTrigger(triggerName, processedInput);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Retry the last trigger from the same starting point.
|
|
549
|
+
* Rolls back messages to the state before the last trigger, re-adds the user message
|
|
550
|
+
* (if any), and re-executes. Files are not re-uploaded.
|
|
551
|
+
*
|
|
552
|
+
* No-op if no trigger has been sent yet.
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
* ```typescript
|
|
556
|
+
* // After an error or unsatisfactory result
|
|
557
|
+
* if (chat.canRetry) {
|
|
558
|
+
* await chat.retry();
|
|
559
|
+
* }
|
|
560
|
+
* ```
|
|
561
|
+
*/
|
|
562
|
+
async retry() {
|
|
563
|
+
if (!this._lastTrigger) return;
|
|
564
|
+
this.transport.stop();
|
|
565
|
+
const { triggerName, input, sendOptions, rollbackAfterMessageId, messageCount } = this._lastTrigger;
|
|
566
|
+
const baseMessages = this._messages.slice(0, messageCount);
|
|
567
|
+
if (sendOptions?.userMessage) {
|
|
568
|
+
const fileRefs = sendOptions.userMessage.files;
|
|
569
|
+
const userMsg = createUserMessage(sendOptions.userMessage, fileRefs);
|
|
570
|
+
this.setMessages([...baseMessages, userMsg]);
|
|
571
|
+
} else {
|
|
572
|
+
this.setMessages(baseMessages);
|
|
573
|
+
}
|
|
574
|
+
await this._executeTrigger(triggerName, input, { rollbackAfterMessageId });
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Observe an already-active execution without triggering a new one.
|
|
578
|
+
*
|
|
579
|
+
* Only supported by transports that implement `observe()` (e.g., polling transport).
|
|
580
|
+
* Use this when the page loads and the session is already streaming — the transport
|
|
581
|
+
* will start consuming events without dispatching a new trigger.
|
|
582
|
+
*
|
|
583
|
+
* When using with `initialMessages`, exclude any in-progress assistant message
|
|
584
|
+
* from the initial messages to avoid duplication — the event stream will rebuild it.
|
|
585
|
+
*/
|
|
586
|
+
async observe() {
|
|
587
|
+
if (!this.transport.observe) {
|
|
588
|
+
throw new Error("Transport does not support observe()");
|
|
589
|
+
}
|
|
590
|
+
await this._consumeStream(this.transport.observe());
|
|
591
|
+
}
|
|
592
|
+
async _executeTrigger(triggerName, input, triggerOptions) {
|
|
593
|
+
await this._consumeStream(this.transport.trigger(triggerName, input, triggerOptions));
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Shared streaming logic for `send()`, `retry()`, and `observe()`.
|
|
597
|
+
* Sets up streaming state, consumes an event stream, and handles errors.
|
|
598
|
+
*/
|
|
599
|
+
async _consumeStream(stream) {
|
|
600
|
+
this.setStatus("streaming");
|
|
601
|
+
this.setError(null);
|
|
602
|
+
this.streamingState = createEmptyStreamingState();
|
|
603
|
+
this._pendingToolsByName.clear();
|
|
604
|
+
this._pendingToolsByCallId.clear();
|
|
605
|
+
this._completedToolResults = [];
|
|
606
|
+
this._serverToolResults = [];
|
|
607
|
+
this._pendingExecutionId = null;
|
|
608
|
+
this._readyToContinue = false;
|
|
609
|
+
this._finishEventReceived = false;
|
|
610
|
+
this._rollbackSynced = false;
|
|
611
|
+
this.updatePendingClientToolsCache();
|
|
612
|
+
try {
|
|
613
|
+
for await (const event of stream) {
|
|
614
|
+
if (this.streamingState === null) break;
|
|
615
|
+
this.handleStreamEvent(event, this.streamingState);
|
|
616
|
+
}
|
|
617
|
+
} catch (err) {
|
|
618
|
+
const errorObj = import_core.OctavusError.isInstance(err) ? err : new import_core.OctavusError({
|
|
619
|
+
errorType: "internal_error",
|
|
620
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
621
|
+
source: "client",
|
|
622
|
+
retryable: false,
|
|
623
|
+
cause: err
|
|
624
|
+
});
|
|
625
|
+
const state = this.streamingState;
|
|
626
|
+
if (state !== null) {
|
|
627
|
+
const messages = [...this._messages];
|
|
628
|
+
const lastMsg = messages[messages.length - 1];
|
|
629
|
+
if (state.parts.length > 0) {
|
|
630
|
+
const finalParts = finalizeParts(state.parts, "Stream error");
|
|
631
|
+
const finalMessage = {
|
|
632
|
+
id: state.messageId,
|
|
633
|
+
role: "assistant",
|
|
634
|
+
parts: finalParts,
|
|
635
|
+
status: "done",
|
|
636
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
637
|
+
};
|
|
638
|
+
if (lastMsg?.id === state.messageId) {
|
|
639
|
+
messages[messages.length - 1] = finalMessage;
|
|
640
|
+
} else {
|
|
641
|
+
messages.push(finalMessage);
|
|
642
|
+
}
|
|
643
|
+
this.setMessages(messages);
|
|
644
|
+
} else if (lastMsg?.id === state.messageId) {
|
|
645
|
+
messages.pop();
|
|
646
|
+
this.setMessages(messages);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
this.setError(errorObj);
|
|
650
|
+
this.setStatus("error");
|
|
651
|
+
this.streamingState = null;
|
|
652
|
+
this.options.onError?.(errorObj);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Upload files directly without sending a message.
|
|
657
|
+
* Useful for showing upload progress before sending.
|
|
658
|
+
*
|
|
659
|
+
* @param files - Files to upload
|
|
660
|
+
* @param onProgress - Optional progress callback
|
|
661
|
+
* @returns Array of file references
|
|
662
|
+
*
|
|
663
|
+
* @example
|
|
664
|
+
* ```typescript
|
|
665
|
+
* const fileRefs = await chat.uploadFiles(fileInput.files, (i, progress) => {
|
|
666
|
+
* console.log(`File ${i}: ${progress}%`);
|
|
667
|
+
* });
|
|
668
|
+
* // Later...
|
|
669
|
+
* await chat.send('user-message', { FILES: fileRefs }, { userMessage: { files: fileRefs } });
|
|
670
|
+
* ```
|
|
671
|
+
*/
|
|
672
|
+
async uploadFiles(files, onProgress) {
|
|
673
|
+
if (!this.options.requestUploadUrls) {
|
|
674
|
+
throw new Error("File upload requires requestUploadUrls option");
|
|
675
|
+
}
|
|
676
|
+
return await uploadFiles(files, {
|
|
677
|
+
requestUploadUrls: this.options.requestUploadUrls,
|
|
678
|
+
onProgress,
|
|
679
|
+
...this.options.uploadOptions
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Internal: Submit a result for a pending tool.
|
|
684
|
+
* Called by bound submit/cancel methods on InteractiveTool.
|
|
685
|
+
*/
|
|
686
|
+
submitToolResult(toolCallId, result, error) {
|
|
687
|
+
const pendingTool = this._pendingToolsByCallId.get(toolCallId);
|
|
688
|
+
if (!pendingTool) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
this._pendingToolsByCallId.delete(toolCallId);
|
|
692
|
+
const toolsForName = this._pendingToolsByName.get(pendingTool.toolName);
|
|
693
|
+
if (toolsForName) {
|
|
694
|
+
const filtered = toolsForName.filter((t) => t.toolCallId !== toolCallId);
|
|
695
|
+
if (filtered.length === 0) {
|
|
696
|
+
this._pendingToolsByName.delete(pendingTool.toolName);
|
|
697
|
+
} else {
|
|
698
|
+
this._pendingToolsByName.set(pendingTool.toolName, filtered);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
this.updatePendingClientToolsCache();
|
|
702
|
+
const toolResult = {
|
|
703
|
+
toolCallId,
|
|
704
|
+
toolName: pendingTool.toolName,
|
|
705
|
+
result: error ? void 0 : result,
|
|
706
|
+
error,
|
|
707
|
+
outputVariable: pendingTool.outputVariable,
|
|
708
|
+
blockIndex: pendingTool.blockIndex,
|
|
709
|
+
thread: pendingTool.thread,
|
|
710
|
+
workerId: pendingTool.workerId
|
|
711
|
+
};
|
|
712
|
+
this._completedToolResults.push(toolResult);
|
|
713
|
+
if (error) {
|
|
714
|
+
this.emitToolOutputError(toolCallId, error);
|
|
715
|
+
} else {
|
|
716
|
+
this.emitToolOutputAvailable(toolCallId, result);
|
|
717
|
+
}
|
|
718
|
+
if (this._pendingToolsByCallId.size === 0) {
|
|
719
|
+
void this.continueWithClientToolResults();
|
|
720
|
+
}
|
|
721
|
+
this.notifyListeners();
|
|
722
|
+
}
|
|
723
|
+
/** Stop the current streaming and finalize any partial message */
|
|
724
|
+
stop() {
|
|
725
|
+
if (this._status !== "streaming" && this._status !== "awaiting-input") {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
this._clientToolAbortController?.abort();
|
|
729
|
+
this._clientToolAbortController = null;
|
|
730
|
+
this._pendingToolsByName.clear();
|
|
731
|
+
this._pendingToolsByCallId.clear();
|
|
732
|
+
this._completedToolResults = [];
|
|
733
|
+
this._serverToolResults = [];
|
|
734
|
+
this._pendingExecutionId = null;
|
|
735
|
+
this._readyToContinue = false;
|
|
736
|
+
this._finishEventReceived = false;
|
|
737
|
+
this.updatePendingClientToolsCache();
|
|
738
|
+
this.transport.stop();
|
|
739
|
+
const state = this.streamingState;
|
|
740
|
+
if (state && state.parts.length > 0) {
|
|
741
|
+
const finalParts = finalizeParts(state.parts, "Stopped by user");
|
|
742
|
+
const finalMessage = {
|
|
743
|
+
id: state.messageId,
|
|
744
|
+
role: "assistant",
|
|
745
|
+
parts: finalParts,
|
|
746
|
+
status: "done",
|
|
747
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
748
|
+
};
|
|
749
|
+
const messages = [...this._messages];
|
|
750
|
+
const lastMsg = messages[messages.length - 1];
|
|
751
|
+
if (lastMsg?.id === state.messageId) {
|
|
752
|
+
messages[messages.length - 1] = finalMessage;
|
|
753
|
+
} else {
|
|
754
|
+
messages.push(finalMessage);
|
|
755
|
+
}
|
|
756
|
+
this.setMessages(messages);
|
|
757
|
+
}
|
|
758
|
+
this.streamingState = null;
|
|
759
|
+
this.setStatus("idle");
|
|
760
|
+
this.options.onStop?.();
|
|
761
|
+
}
|
|
762
|
+
// =========================================================================
|
|
763
|
+
// Private Helpers
|
|
764
|
+
// =========================================================================
|
|
765
|
+
/**
|
|
766
|
+
* IMMUTABILITY RULES — all event handlers must follow these patterns:
|
|
767
|
+
*
|
|
768
|
+
* 1. Never mutate an existing part/message object. Some environments (e.g.
|
|
769
|
+
* React Native with Reanimated) freeze objects between renders, so
|
|
770
|
+
* mutations silently fail or throw.
|
|
771
|
+
*
|
|
772
|
+
* 2. Always create a new object via spread and assign it back:
|
|
773
|
+
* GOOD: state.parts[i] = { ...part, text: part.text + delta };
|
|
774
|
+
* BAD: part.text += delta; state.parts[i] = { ...part };
|
|
775
|
+
*
|
|
776
|
+
* 3. For nested worker parts, copy the parts array too:
|
|
777
|
+
* const updatedParts = [...workerPart.parts];
|
|
778
|
+
* updatedParts[i] = { ...part, status: 'done' };
|
|
779
|
+
* state.parts[wi] = { ...workerPart, parts: updatedParts };
|
|
780
|
+
*
|
|
781
|
+
* 4. For the messages array, copy before mutating:
|
|
782
|
+
* const messages = [...this._messages];
|
|
783
|
+
* messages[i] = newMessage; // or messages.pop()
|
|
784
|
+
* this.setMessages(messages);
|
|
785
|
+
*/
|
|
786
|
+
handleStreamEvent(event, state) {
|
|
787
|
+
switch (event.type) {
|
|
788
|
+
case "start":
|
|
789
|
+
if (event.executionId) {
|
|
790
|
+
this.options.onStart?.(event.executionId);
|
|
791
|
+
}
|
|
792
|
+
if (!this._rollbackSynced && this._lastTrigger) {
|
|
793
|
+
if (event.lastMessageId !== void 0) {
|
|
794
|
+
this._lastTrigger.rollbackAfterMessageId = event.lastMessageId;
|
|
795
|
+
}
|
|
796
|
+
this._rollbackSynced = true;
|
|
797
|
+
}
|
|
798
|
+
break;
|
|
799
|
+
case "block-start": {
|
|
800
|
+
const workerId = event.workerId;
|
|
801
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
802
|
+
const block = {
|
|
803
|
+
blockId: event.blockId,
|
|
804
|
+
blockName: event.blockName,
|
|
805
|
+
blockType: event.blockType,
|
|
806
|
+
display: event.display,
|
|
807
|
+
description: event.description,
|
|
808
|
+
outputToChat: event.outputToChat ?? true,
|
|
809
|
+
thread: event.thread,
|
|
810
|
+
reasoning: "",
|
|
811
|
+
text: "",
|
|
812
|
+
toolCalls: /* @__PURE__ */ new Map()
|
|
813
|
+
};
|
|
814
|
+
state.blocks.set(event.blockId, block);
|
|
815
|
+
state.activeBlock = block;
|
|
816
|
+
const isOperation = OPERATION_BLOCK_TYPES.has(event.blockType);
|
|
817
|
+
const isHidden = event.display === "hidden";
|
|
818
|
+
if (isOperation && !isHidden) {
|
|
819
|
+
const thread = event.thread;
|
|
820
|
+
const operationPart = {
|
|
821
|
+
type: "operation",
|
|
822
|
+
operationId: event.blockId,
|
|
823
|
+
name: event.description ?? event.blockName,
|
|
824
|
+
operationType: event.blockType,
|
|
825
|
+
status: "running",
|
|
826
|
+
thread: (0, import_core.threadForPart)(thread)
|
|
827
|
+
};
|
|
828
|
+
if (workerState) {
|
|
829
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
830
|
+
state.parts[workerState.partIndex] = {
|
|
831
|
+
...workerPart,
|
|
832
|
+
parts: [...workerPart.parts, operationPart]
|
|
833
|
+
};
|
|
834
|
+
} else {
|
|
835
|
+
state.parts.push(operationPart);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
state.currentTextPartIndex = null;
|
|
839
|
+
state.currentReasoningPartIndex = null;
|
|
840
|
+
this.updateStreamingMessage();
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
case "block-end": {
|
|
844
|
+
const workerId = event.workerId;
|
|
845
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
846
|
+
if (workerState) {
|
|
847
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
848
|
+
const operationPartIndex = workerPart.parts.findIndex(
|
|
849
|
+
(p) => p.type === "operation" && p.operationId === event.blockId
|
|
850
|
+
);
|
|
851
|
+
if (operationPartIndex >= 0) {
|
|
852
|
+
const part = workerPart.parts[operationPartIndex];
|
|
853
|
+
const updatedParts = [...workerPart.parts];
|
|
854
|
+
updatedParts[operationPartIndex] = { ...part, status: "done" };
|
|
855
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: updatedParts };
|
|
856
|
+
}
|
|
857
|
+
} else {
|
|
858
|
+
const operationPartIndex = state.parts.findIndex(
|
|
859
|
+
(p) => p.type === "operation" && p.operationId === event.blockId
|
|
860
|
+
);
|
|
861
|
+
if (operationPartIndex >= 0) {
|
|
862
|
+
const part = state.parts[operationPartIndex];
|
|
863
|
+
state.parts[operationPartIndex] = { ...part, status: "done" };
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (state.activeBlock?.blockId === event.blockId) {
|
|
867
|
+
state.activeBlock = null;
|
|
868
|
+
}
|
|
869
|
+
this.updateStreamingMessage();
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
case "reasoning-start": {
|
|
873
|
+
const workerId = event.workerId;
|
|
874
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
875
|
+
const reasoningPart = {
|
|
876
|
+
type: "reasoning",
|
|
877
|
+
text: "",
|
|
878
|
+
status: "streaming",
|
|
879
|
+
thread: (0, import_core.threadForPart)(state.activeBlock?.thread)
|
|
880
|
+
};
|
|
881
|
+
if (workerState) {
|
|
882
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
883
|
+
const newParts = [...workerPart.parts, reasoningPart];
|
|
884
|
+
workerState.currentReasoningPartIndex = newParts.length - 1;
|
|
885
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: newParts };
|
|
886
|
+
} else {
|
|
887
|
+
state.parts.push(reasoningPart);
|
|
888
|
+
state.currentReasoningPartIndex = state.parts.length - 1;
|
|
889
|
+
}
|
|
890
|
+
this.updateStreamingMessage();
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
case "reasoning-delta": {
|
|
894
|
+
const workerId = event.workerId;
|
|
895
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
896
|
+
if (workerState) {
|
|
897
|
+
if (workerState.currentReasoningPartIndex !== null) {
|
|
898
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
899
|
+
const part = workerPart.parts[workerState.currentReasoningPartIndex];
|
|
900
|
+
const updatedParts = [...workerPart.parts];
|
|
901
|
+
updatedParts[workerState.currentReasoningPartIndex] = {
|
|
902
|
+
...part,
|
|
903
|
+
text: part.text + event.delta
|
|
904
|
+
};
|
|
905
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: updatedParts };
|
|
906
|
+
}
|
|
907
|
+
} else {
|
|
908
|
+
if (state.currentReasoningPartIndex !== null) {
|
|
909
|
+
const part = state.parts[state.currentReasoningPartIndex];
|
|
910
|
+
state.parts[state.currentReasoningPartIndex] = {
|
|
911
|
+
...part,
|
|
912
|
+
text: part.text + event.delta
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
if (state.activeBlock) {
|
|
916
|
+
state.activeBlock.reasoning += event.delta;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
this.updateStreamingMessage();
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
case "reasoning-end": {
|
|
923
|
+
const workerId = event.workerId;
|
|
924
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
925
|
+
if (workerState) {
|
|
926
|
+
if (workerState.currentReasoningPartIndex !== null) {
|
|
927
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
928
|
+
const part = workerPart.parts[workerState.currentReasoningPartIndex];
|
|
929
|
+
const updatedParts = [...workerPart.parts];
|
|
930
|
+
updatedParts[workerState.currentReasoningPartIndex] = {
|
|
931
|
+
...part,
|
|
932
|
+
status: "done"
|
|
933
|
+
};
|
|
934
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: updatedParts };
|
|
935
|
+
workerState.currentReasoningPartIndex = null;
|
|
936
|
+
}
|
|
937
|
+
} else if (state.currentReasoningPartIndex !== null) {
|
|
938
|
+
const part = state.parts[state.currentReasoningPartIndex];
|
|
939
|
+
state.parts[state.currentReasoningPartIndex] = { ...part, status: "done" };
|
|
940
|
+
state.currentReasoningPartIndex = null;
|
|
941
|
+
}
|
|
942
|
+
this.updateStreamingMessage();
|
|
943
|
+
break;
|
|
944
|
+
}
|
|
945
|
+
case "text-start": {
|
|
946
|
+
const workerId = event.workerId;
|
|
947
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
948
|
+
const thread = (0, import_core.threadForPart)(state.activeBlock?.thread);
|
|
949
|
+
const shouldAddPart = state.activeBlock?.outputToChat !== false || thread !== void 0;
|
|
950
|
+
if (workerState || shouldAddPart) {
|
|
951
|
+
if (event.responseType) {
|
|
952
|
+
const objectPart = {
|
|
953
|
+
type: "object",
|
|
954
|
+
id: event.id,
|
|
955
|
+
typeName: event.responseType,
|
|
956
|
+
partial: void 0,
|
|
957
|
+
object: void 0,
|
|
958
|
+
status: "streaming",
|
|
959
|
+
thread
|
|
960
|
+
};
|
|
961
|
+
if (workerState) {
|
|
962
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
963
|
+
const newParts = [...workerPart.parts, objectPart];
|
|
964
|
+
workerState.currentObjectPartIndex = newParts.length - 1;
|
|
965
|
+
workerState.accumulatedJson = "";
|
|
966
|
+
workerState.currentTextPartIndex = null;
|
|
967
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: newParts };
|
|
968
|
+
} else {
|
|
969
|
+
state.parts.push(objectPart);
|
|
970
|
+
state.currentObjectPartIndex = state.parts.length - 1;
|
|
971
|
+
state.accumulatedJson = "";
|
|
972
|
+
state.currentTextPartIndex = null;
|
|
973
|
+
}
|
|
974
|
+
} else {
|
|
975
|
+
const textPart = {
|
|
976
|
+
type: "text",
|
|
977
|
+
text: "",
|
|
978
|
+
status: "streaming",
|
|
979
|
+
thread
|
|
980
|
+
};
|
|
981
|
+
if (workerState) {
|
|
982
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
983
|
+
const newParts = [...workerPart.parts, textPart];
|
|
984
|
+
workerState.currentTextPartIndex = newParts.length - 1;
|
|
985
|
+
workerState.currentObjectPartIndex = null;
|
|
986
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: newParts };
|
|
987
|
+
} else {
|
|
988
|
+
state.parts.push(textPart);
|
|
989
|
+
state.currentTextPartIndex = state.parts.length - 1;
|
|
990
|
+
state.currentObjectPartIndex = null;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
this.updateStreamingMessage();
|
|
995
|
+
break;
|
|
996
|
+
}
|
|
997
|
+
case "text-delta": {
|
|
998
|
+
const workerId = event.workerId;
|
|
999
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
1000
|
+
if (workerState) {
|
|
1001
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
1002
|
+
if (workerState.currentObjectPartIndex !== null) {
|
|
1003
|
+
workerState.accumulatedJson += event.delta;
|
|
1004
|
+
const part = workerPart.parts[workerState.currentObjectPartIndex];
|
|
1005
|
+
const parsed = parsePartialJson(workerState.accumulatedJson);
|
|
1006
|
+
if (parsed !== void 0) {
|
|
1007
|
+
const updatedParts = [...workerPart.parts];
|
|
1008
|
+
updatedParts[workerState.currentObjectPartIndex] = { ...part, partial: parsed };
|
|
1009
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: updatedParts };
|
|
1010
|
+
}
|
|
1011
|
+
} else if (workerState.currentTextPartIndex !== null) {
|
|
1012
|
+
const part = workerPart.parts[workerState.currentTextPartIndex];
|
|
1013
|
+
const updatedParts = [...workerPart.parts];
|
|
1014
|
+
updatedParts[workerState.currentTextPartIndex] = {
|
|
1015
|
+
...part,
|
|
1016
|
+
text: part.text + event.delta
|
|
1017
|
+
};
|
|
1018
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: updatedParts };
|
|
1019
|
+
}
|
|
1020
|
+
} else {
|
|
1021
|
+
if (state.currentObjectPartIndex !== null) {
|
|
1022
|
+
state.accumulatedJson += event.delta;
|
|
1023
|
+
const part = state.parts[state.currentObjectPartIndex];
|
|
1024
|
+
const parsed = parsePartialJson(state.accumulatedJson);
|
|
1025
|
+
if (parsed !== void 0) {
|
|
1026
|
+
state.parts[state.currentObjectPartIndex] = { ...part, partial: parsed };
|
|
1027
|
+
}
|
|
1028
|
+
} else if (state.currentTextPartIndex !== null) {
|
|
1029
|
+
const part = state.parts[state.currentTextPartIndex];
|
|
1030
|
+
state.parts[state.currentTextPartIndex] = {
|
|
1031
|
+
...part,
|
|
1032
|
+
text: part.text + event.delta
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
if (state.activeBlock) {
|
|
1036
|
+
state.activeBlock.text += event.delta;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
this.updateStreamingMessage();
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
case "text-end": {
|
|
1043
|
+
const workerId = event.workerId;
|
|
1044
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
1045
|
+
if (workerState) {
|
|
1046
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
1047
|
+
const updatedParts = [...workerPart.parts];
|
|
1048
|
+
if (workerState.currentObjectPartIndex !== null) {
|
|
1049
|
+
const part = workerPart.parts[workerState.currentObjectPartIndex];
|
|
1050
|
+
try {
|
|
1051
|
+
const finalObject = JSON.parse(workerState.accumulatedJson);
|
|
1052
|
+
updatedParts[workerState.currentObjectPartIndex] = {
|
|
1053
|
+
...part,
|
|
1054
|
+
object: finalObject,
|
|
1055
|
+
partial: finalObject,
|
|
1056
|
+
status: "done"
|
|
1057
|
+
};
|
|
1058
|
+
} catch {
|
|
1059
|
+
updatedParts[workerState.currentObjectPartIndex] = {
|
|
1060
|
+
...part,
|
|
1061
|
+
status: "error",
|
|
1062
|
+
error: "Failed to parse response as JSON"
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
workerState.currentObjectPartIndex = null;
|
|
1066
|
+
workerState.accumulatedJson = "";
|
|
1067
|
+
} else if (workerState.currentTextPartIndex !== null) {
|
|
1068
|
+
const part = workerPart.parts[workerState.currentTextPartIndex];
|
|
1069
|
+
updatedParts[workerState.currentTextPartIndex] = {
|
|
1070
|
+
...part,
|
|
1071
|
+
status: "done"
|
|
1072
|
+
};
|
|
1073
|
+
workerState.currentTextPartIndex = null;
|
|
1074
|
+
}
|
|
1075
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: updatedParts };
|
|
1076
|
+
} else if (state.currentObjectPartIndex !== null) {
|
|
1077
|
+
const part = state.parts[state.currentObjectPartIndex];
|
|
1078
|
+
try {
|
|
1079
|
+
const finalObject = JSON.parse(state.accumulatedJson);
|
|
1080
|
+
state.parts[state.currentObjectPartIndex] = {
|
|
1081
|
+
...part,
|
|
1082
|
+
object: finalObject,
|
|
1083
|
+
partial: finalObject,
|
|
1084
|
+
status: "done"
|
|
1085
|
+
};
|
|
1086
|
+
} catch {
|
|
1087
|
+
state.parts[state.currentObjectPartIndex] = {
|
|
1088
|
+
...part,
|
|
1089
|
+
status: "error",
|
|
1090
|
+
error: "Failed to parse response as JSON"
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
state.currentObjectPartIndex = null;
|
|
1094
|
+
state.accumulatedJson = "";
|
|
1095
|
+
} else if (state.currentTextPartIndex !== null) {
|
|
1096
|
+
const part = state.parts[state.currentTextPartIndex];
|
|
1097
|
+
state.parts[state.currentTextPartIndex] = { ...part, status: "done" };
|
|
1098
|
+
state.currentTextPartIndex = null;
|
|
1099
|
+
}
|
|
1100
|
+
this.updateStreamingMessage();
|
|
1101
|
+
break;
|
|
1102
|
+
}
|
|
1103
|
+
case "tool-input-start": {
|
|
1104
|
+
const workerId = event.workerId;
|
|
1105
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
1106
|
+
const toolPart = {
|
|
1107
|
+
type: "tool-call",
|
|
1108
|
+
toolCallId: event.toolCallId,
|
|
1109
|
+
toolName: event.toolName,
|
|
1110
|
+
displayName: event.title,
|
|
1111
|
+
args: {},
|
|
1112
|
+
result: void 0,
|
|
1113
|
+
error: void 0,
|
|
1114
|
+
status: "pending",
|
|
1115
|
+
thread: (0, import_core.threadForPart)(state.activeBlock?.thread)
|
|
1116
|
+
};
|
|
1117
|
+
if (workerState) {
|
|
1118
|
+
workerState.toolInputBuffers.set(event.toolCallId, "");
|
|
1119
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
1120
|
+
state.parts[workerState.partIndex] = {
|
|
1121
|
+
...workerPart,
|
|
1122
|
+
parts: [...workerPart.parts, toolPart]
|
|
1123
|
+
};
|
|
1124
|
+
} else {
|
|
1125
|
+
state.toolInputBuffers.set(event.toolCallId, "");
|
|
1126
|
+
state.parts.push(toolPart);
|
|
1127
|
+
if (state.activeBlock) {
|
|
1128
|
+
state.activeBlock.toolCalls.set(event.toolCallId, toolPart);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
this.updateStreamingMessage();
|
|
1132
|
+
break;
|
|
1133
|
+
}
|
|
1134
|
+
case "tool-input-delta": {
|
|
1135
|
+
const workerId = event.workerId;
|
|
1136
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
1137
|
+
if (workerState) {
|
|
1138
|
+
const existing = workerState.toolInputBuffers.get(event.toolCallId) ?? "";
|
|
1139
|
+
const accumulated = existing + event.inputTextDelta;
|
|
1140
|
+
workerState.toolInputBuffers.set(event.toolCallId, accumulated);
|
|
1141
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
1142
|
+
const toolPartIndex = workerPart.parts.findIndex(
|
|
1143
|
+
(p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
|
|
1144
|
+
);
|
|
1145
|
+
if (toolPartIndex >= 0) {
|
|
1146
|
+
const toolPart = workerPart.parts[toolPartIndex];
|
|
1147
|
+
const parsed = parsePartialJson(accumulated);
|
|
1148
|
+
if (parsed !== void 0) {
|
|
1149
|
+
const updatedParts = [...workerPart.parts];
|
|
1150
|
+
updatedParts[toolPartIndex] = {
|
|
1151
|
+
...toolPart,
|
|
1152
|
+
args: parsed
|
|
1153
|
+
};
|
|
1154
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: updatedParts };
|
|
1155
|
+
this.updateStreamingMessage();
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
} else {
|
|
1159
|
+
const existing = state.toolInputBuffers.get(event.toolCallId) ?? "";
|
|
1160
|
+
const accumulated = existing + event.inputTextDelta;
|
|
1161
|
+
state.toolInputBuffers.set(event.toolCallId, accumulated);
|
|
1162
|
+
const toolPartIndex = state.parts.findIndex(
|
|
1163
|
+
(p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
|
|
1164
|
+
);
|
|
1165
|
+
if (toolPartIndex >= 0) {
|
|
1166
|
+
const toolPart = state.parts[toolPartIndex];
|
|
1167
|
+
const parsed = parsePartialJson(accumulated);
|
|
1168
|
+
if (parsed !== void 0) {
|
|
1169
|
+
state.parts[toolPartIndex] = {
|
|
1170
|
+
...toolPart,
|
|
1171
|
+
args: parsed
|
|
1172
|
+
};
|
|
1173
|
+
this.updateStreamingMessage();
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
case "tool-input-end":
|
|
1180
|
+
break;
|
|
1181
|
+
case "tool-input-available": {
|
|
1182
|
+
const workerId = event.workerId;
|
|
1183
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
1184
|
+
if (workerState) {
|
|
1185
|
+
workerState.toolInputBuffers.delete(event.toolCallId);
|
|
1186
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
1187
|
+
const toolPartIndex = workerPart.parts.findIndex(
|
|
1188
|
+
(p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
|
|
1189
|
+
);
|
|
1190
|
+
if (toolPartIndex >= 0) {
|
|
1191
|
+
const part = workerPart.parts[toolPartIndex];
|
|
1192
|
+
const updatedParts = [...workerPart.parts];
|
|
1193
|
+
updatedParts[toolPartIndex] = {
|
|
1194
|
+
...part,
|
|
1195
|
+
args: event.input,
|
|
1196
|
+
status: "running"
|
|
1197
|
+
};
|
|
1198
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: updatedParts };
|
|
1199
|
+
this.updateStreamingMessage();
|
|
1200
|
+
}
|
|
1201
|
+
} else {
|
|
1202
|
+
state.toolInputBuffers.delete(event.toolCallId);
|
|
1203
|
+
const toolPartIndex = state.parts.findIndex(
|
|
1204
|
+
(p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
|
|
1205
|
+
);
|
|
1206
|
+
if (toolPartIndex >= 0) {
|
|
1207
|
+
const part = state.parts[toolPartIndex];
|
|
1208
|
+
state.parts[toolPartIndex] = {
|
|
1209
|
+
...part,
|
|
1210
|
+
args: event.input,
|
|
1211
|
+
status: "running"
|
|
1212
|
+
};
|
|
1213
|
+
this.updateStreamingMessage();
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
break;
|
|
1217
|
+
}
|
|
1218
|
+
case "tool-output-available": {
|
|
1219
|
+
const workerId = event.workerId;
|
|
1220
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
1221
|
+
if (workerState) {
|
|
1222
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
1223
|
+
const toolPartIndex = workerPart.parts.findIndex(
|
|
1224
|
+
(p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
|
|
1225
|
+
);
|
|
1226
|
+
if (toolPartIndex >= 0) {
|
|
1227
|
+
const part = workerPart.parts[toolPartIndex];
|
|
1228
|
+
const updatedParts = [...workerPart.parts];
|
|
1229
|
+
updatedParts[toolPartIndex] = {
|
|
1230
|
+
...part,
|
|
1231
|
+
result: event.output,
|
|
1232
|
+
status: "done"
|
|
1233
|
+
};
|
|
1234
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: updatedParts };
|
|
1235
|
+
this.updateStreamingMessage();
|
|
1236
|
+
}
|
|
1237
|
+
} else {
|
|
1238
|
+
const toolPartIndex = state.parts.findIndex(
|
|
1239
|
+
(p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
|
|
1240
|
+
);
|
|
1241
|
+
if (toolPartIndex >= 0) {
|
|
1242
|
+
const part = state.parts[toolPartIndex];
|
|
1243
|
+
state.parts[toolPartIndex] = {
|
|
1244
|
+
...part,
|
|
1245
|
+
result: event.output,
|
|
1246
|
+
status: "done"
|
|
1247
|
+
};
|
|
1248
|
+
this.updateStreamingMessage();
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
case "tool-output-error": {
|
|
1254
|
+
const workerId = event.workerId;
|
|
1255
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
1256
|
+
if (workerState) {
|
|
1257
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
1258
|
+
const toolPartIndex = workerPart.parts.findIndex(
|
|
1259
|
+
(p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
|
|
1260
|
+
);
|
|
1261
|
+
if (toolPartIndex >= 0) {
|
|
1262
|
+
const part = workerPart.parts[toolPartIndex];
|
|
1263
|
+
const updatedParts = [...workerPart.parts];
|
|
1264
|
+
updatedParts[toolPartIndex] = {
|
|
1265
|
+
...part,
|
|
1266
|
+
error: event.error,
|
|
1267
|
+
status: "error"
|
|
1268
|
+
};
|
|
1269
|
+
state.parts[workerState.partIndex] = { ...workerPart, parts: updatedParts };
|
|
1270
|
+
this.updateStreamingMessage();
|
|
1271
|
+
}
|
|
1272
|
+
} else {
|
|
1273
|
+
const toolPartIndex = state.parts.findIndex(
|
|
1274
|
+
(p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
|
|
1275
|
+
);
|
|
1276
|
+
if (toolPartIndex >= 0) {
|
|
1277
|
+
const part = state.parts[toolPartIndex];
|
|
1278
|
+
state.parts[toolPartIndex] = {
|
|
1279
|
+
...part,
|
|
1280
|
+
error: event.error,
|
|
1281
|
+
status: "error"
|
|
1282
|
+
};
|
|
1283
|
+
this.updateStreamingMessage();
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
break;
|
|
1287
|
+
}
|
|
1288
|
+
case "source": {
|
|
1289
|
+
const workerId = event.workerId;
|
|
1290
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
1291
|
+
const thread = (0, import_core.threadForPart)(state.activeBlock?.thread);
|
|
1292
|
+
let sourcePart;
|
|
1293
|
+
if (event.sourceType === "url") {
|
|
1294
|
+
sourcePart = {
|
|
1295
|
+
type: "source",
|
|
1296
|
+
sourceType: "url",
|
|
1297
|
+
id: event.id,
|
|
1298
|
+
url: event.url,
|
|
1299
|
+
title: event.title,
|
|
1300
|
+
thread
|
|
1301
|
+
};
|
|
1302
|
+
} else {
|
|
1303
|
+
sourcePart = {
|
|
1304
|
+
type: "source",
|
|
1305
|
+
sourceType: "document",
|
|
1306
|
+
id: event.id,
|
|
1307
|
+
mediaType: event.mediaType,
|
|
1308
|
+
title: event.title,
|
|
1309
|
+
filename: event.filename,
|
|
1310
|
+
thread
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
if (workerState) {
|
|
1314
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
1315
|
+
state.parts[workerState.partIndex] = {
|
|
1316
|
+
...workerPart,
|
|
1317
|
+
parts: [...workerPart.parts, sourcePart]
|
|
1318
|
+
};
|
|
1319
|
+
} else {
|
|
1320
|
+
state.parts.push(sourcePart);
|
|
1321
|
+
}
|
|
1322
|
+
this.updateStreamingMessage();
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
case "file-available": {
|
|
1326
|
+
const workerId = event.workerId;
|
|
1327
|
+
const workerState = workerId ? state.activeWorkers.get(workerId) : void 0;
|
|
1328
|
+
const filePart = {
|
|
1329
|
+
type: "file",
|
|
1330
|
+
id: event.id,
|
|
1331
|
+
mediaType: event.mediaType,
|
|
1332
|
+
url: event.url,
|
|
1333
|
+
filename: event.filename,
|
|
1334
|
+
size: event.size,
|
|
1335
|
+
toolCallId: event.toolCallId,
|
|
1336
|
+
thread: (0, import_core.threadForPart)(state.activeBlock?.thread)
|
|
1337
|
+
};
|
|
1338
|
+
if (workerState) {
|
|
1339
|
+
const workerPart = state.parts[workerState.partIndex];
|
|
1340
|
+
state.parts[workerState.partIndex] = {
|
|
1341
|
+
...workerPart,
|
|
1342
|
+
parts: [...workerPart.parts, filePart]
|
|
1343
|
+
};
|
|
1344
|
+
} else {
|
|
1345
|
+
state.parts.push(filePart);
|
|
1346
|
+
}
|
|
1347
|
+
this.updateStreamingMessage();
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
case "resource-update":
|
|
1351
|
+
this.options.onResourceUpdate?.(event.name, event.value);
|
|
1352
|
+
break;
|
|
1353
|
+
case "worker-start": {
|
|
1354
|
+
const existingIndex = state.parts.findIndex(
|
|
1355
|
+
(p) => p.type === "worker" && p.workerId === event.workerId
|
|
1356
|
+
);
|
|
1357
|
+
let partIndex;
|
|
1358
|
+
if (existingIndex !== -1) {
|
|
1359
|
+
const existingPart = state.parts[existingIndex];
|
|
1360
|
+
state.parts[existingIndex] = { ...existingPart, status: "running" };
|
|
1361
|
+
partIndex = existingIndex;
|
|
1362
|
+
} else {
|
|
1363
|
+
const workerPart = {
|
|
1364
|
+
type: "worker",
|
|
1365
|
+
workerId: event.workerId,
|
|
1366
|
+
workerSlug: event.workerSlug,
|
|
1367
|
+
description: event.description,
|
|
1368
|
+
parts: [],
|
|
1369
|
+
status: "running"
|
|
1370
|
+
};
|
|
1371
|
+
state.parts.push(workerPart);
|
|
1372
|
+
partIndex = state.parts.length - 1;
|
|
1373
|
+
}
|
|
1374
|
+
const workerState = {
|
|
1375
|
+
partIndex,
|
|
1376
|
+
currentTextPartIndex: null,
|
|
1377
|
+
currentReasoningPartIndex: null,
|
|
1378
|
+
currentObjectPartIndex: null,
|
|
1379
|
+
accumulatedJson: "",
|
|
1380
|
+
toolInputBuffers: /* @__PURE__ */ new Map()
|
|
1381
|
+
};
|
|
1382
|
+
state.activeWorkers.set(event.workerId, workerState);
|
|
1383
|
+
this.updateStreamingMessage();
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1386
|
+
case "worker-result": {
|
|
1387
|
+
const workerState = state.activeWorkers.get(event.workerId);
|
|
1388
|
+
if (workerState !== void 0) {
|
|
1389
|
+
const part = state.parts[workerState.partIndex];
|
|
1390
|
+
state.parts[workerState.partIndex] = {
|
|
1391
|
+
...part,
|
|
1392
|
+
output: event.output,
|
|
1393
|
+
error: event.error,
|
|
1394
|
+
status: event.error ? "error" : "done",
|
|
1395
|
+
parts: part.parts.map((p) => {
|
|
1396
|
+
if (p.type === "text" || p.type === "reasoning") {
|
|
1397
|
+
if (p.status === "streaming") {
|
|
1398
|
+
return { ...p, status: "done" };
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
if (p.type === "object" && p.status === "streaming") {
|
|
1402
|
+
return { ...p, status: "done" };
|
|
1403
|
+
}
|
|
1404
|
+
return p;
|
|
1405
|
+
})
|
|
1406
|
+
};
|
|
1407
|
+
state.activeWorkers.delete(event.workerId);
|
|
1408
|
+
}
|
|
1409
|
+
this.updateStreamingMessage();
|
|
1410
|
+
break;
|
|
1411
|
+
}
|
|
1412
|
+
case "finish": {
|
|
1413
|
+
if (event.finishReason === "client-tool-calls") {
|
|
1414
|
+
this._finishEventReceived = true;
|
|
1415
|
+
if (this._pendingToolsByCallId.size > 0) {
|
|
1416
|
+
this.setStatus("awaiting-input");
|
|
1417
|
+
} else if (this._readyToContinue) {
|
|
1418
|
+
this._readyToContinue = false;
|
|
1419
|
+
this._finishEventReceived = false;
|
|
1420
|
+
void this.continueWithClientToolResults();
|
|
1421
|
+
}
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
const finalMessage = buildMessageFromState(state, "done");
|
|
1425
|
+
finalMessage.parts = finalMessage.parts.map((part) => {
|
|
1426
|
+
if (part.type === "text" || part.type === "reasoning") {
|
|
1427
|
+
return { ...part, status: "done" };
|
|
1428
|
+
}
|
|
1429
|
+
if (part.type === "object" && part.status === "streaming") {
|
|
1430
|
+
return { ...part, status: "done" };
|
|
1431
|
+
}
|
|
1432
|
+
return part;
|
|
1433
|
+
});
|
|
1434
|
+
const messages = [...this._messages];
|
|
1435
|
+
const lastMsg = messages[messages.length - 1];
|
|
1436
|
+
if (finalMessage.parts.length > 0) {
|
|
1437
|
+
if (lastMsg?.id === state.messageId) {
|
|
1438
|
+
messages[messages.length - 1] = finalMessage;
|
|
1439
|
+
} else {
|
|
1440
|
+
messages.push(finalMessage);
|
|
1441
|
+
}
|
|
1442
|
+
this.setMessages(messages);
|
|
1443
|
+
} else if (lastMsg?.id === state.messageId) {
|
|
1444
|
+
messages.pop();
|
|
1445
|
+
this.setMessages(messages);
|
|
1446
|
+
}
|
|
1447
|
+
this.setStatus("idle");
|
|
1448
|
+
this.streamingState = null;
|
|
1449
|
+
this.options.onFinish?.();
|
|
1450
|
+
break;
|
|
1451
|
+
}
|
|
1452
|
+
case "error": {
|
|
1453
|
+
throw new import_core.OctavusError({
|
|
1454
|
+
errorType: event.errorType,
|
|
1455
|
+
message: event.message,
|
|
1456
|
+
source: event.source,
|
|
1457
|
+
retryable: event.retryable,
|
|
1458
|
+
retryAfter: event.retryAfter,
|
|
1459
|
+
code: event.code,
|
|
1460
|
+
provider: event.provider,
|
|
1461
|
+
tool: event.tool
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
case "tool-request":
|
|
1465
|
+
break;
|
|
1466
|
+
case "client-tool-request":
|
|
1467
|
+
this._pendingExecutionId = event.executionId;
|
|
1468
|
+
this._serverToolResults = event.serverToolResults ?? [];
|
|
1469
|
+
void this.handleClientToolRequest(event.toolCalls, state);
|
|
1470
|
+
break;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
updateStreamingMessage() {
|
|
1474
|
+
const state = this.streamingState;
|
|
1475
|
+
if (!state) return;
|
|
1476
|
+
const msg = buildMessageFromState(state, "streaming");
|
|
1477
|
+
const messages = [...this._messages];
|
|
1478
|
+
const lastMsg = messages[messages.length - 1];
|
|
1479
|
+
if (lastMsg?.id === state.messageId) {
|
|
1480
|
+
messages[messages.length - 1] = msg;
|
|
1481
|
+
} else {
|
|
1482
|
+
messages.push(msg);
|
|
1483
|
+
}
|
|
1484
|
+
this.setMessages(messages);
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Emit a tool-output-available event for a client tool result.
|
|
1488
|
+
*/
|
|
1489
|
+
emitToolOutputAvailable(toolCallId, output) {
|
|
1490
|
+
const state = this.streamingState;
|
|
1491
|
+
if (!state) return;
|
|
1492
|
+
const toolPartIndex = state.parts.findIndex(
|
|
1493
|
+
(p) => p.type === "tool-call" && p.toolCallId === toolCallId
|
|
1494
|
+
);
|
|
1495
|
+
if (toolPartIndex >= 0) {
|
|
1496
|
+
const part = state.parts[toolPartIndex];
|
|
1497
|
+
state.parts[toolPartIndex] = {
|
|
1498
|
+
...part,
|
|
1499
|
+
result: output,
|
|
1500
|
+
status: "done"
|
|
1501
|
+
};
|
|
1502
|
+
this.updateStreamingMessage();
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Emit a tool-output-error event for a client tool result.
|
|
1507
|
+
*/
|
|
1508
|
+
emitToolOutputError(toolCallId, error) {
|
|
1509
|
+
const state = this.streamingState;
|
|
1510
|
+
if (!state) return;
|
|
1511
|
+
const toolPartIndex = state.parts.findIndex(
|
|
1512
|
+
(p) => p.type === "tool-call" && p.toolCallId === toolCallId
|
|
1513
|
+
);
|
|
1514
|
+
if (toolPartIndex >= 0) {
|
|
1515
|
+
const part = state.parts[toolPartIndex];
|
|
1516
|
+
state.parts[toolPartIndex] = {
|
|
1517
|
+
...part,
|
|
1518
|
+
error,
|
|
1519
|
+
status: "error"
|
|
1520
|
+
};
|
|
1521
|
+
this.updateStreamingMessage();
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Continue execution with collected client tool results.
|
|
1526
|
+
*/
|
|
1527
|
+
async continueWithClientToolResults() {
|
|
1528
|
+
if (this._completedToolResults.length === 0) return;
|
|
1529
|
+
if (this._pendingExecutionId === null) {
|
|
1530
|
+
const errorObj = new import_core.OctavusError({
|
|
1531
|
+
errorType: "internal_error",
|
|
1532
|
+
message: "Cannot continue execution: execution ID was lost.",
|
|
1533
|
+
source: "client",
|
|
1534
|
+
retryable: false
|
|
1535
|
+
});
|
|
1536
|
+
this.setError(errorObj);
|
|
1537
|
+
this.setStatus("error");
|
|
1538
|
+
this.options.onError?.(errorObj);
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
const allResults = [...this._serverToolResults, ...this._completedToolResults];
|
|
1542
|
+
const executionId = this._pendingExecutionId;
|
|
1543
|
+
this._serverToolResults = [];
|
|
1544
|
+
this._completedToolResults = [];
|
|
1545
|
+
this._pendingExecutionId = null;
|
|
1546
|
+
this.setStatus("streaming");
|
|
1547
|
+
try {
|
|
1548
|
+
for await (const event of this.transport.continueWithToolResults(executionId, allResults)) {
|
|
1549
|
+
if (this.streamingState === null) break;
|
|
1550
|
+
this.handleStreamEvent(event, this.streamingState);
|
|
1551
|
+
}
|
|
1552
|
+
} catch (err) {
|
|
1553
|
+
const errorObj = import_core.OctavusError.isInstance(err) ? err : new import_core.OctavusError({
|
|
1554
|
+
errorType: "internal_error",
|
|
1555
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
1556
|
+
source: "client",
|
|
1557
|
+
retryable: false,
|
|
1558
|
+
cause: err
|
|
1559
|
+
});
|
|
1560
|
+
this.setError(errorObj);
|
|
1561
|
+
this.setStatus("error");
|
|
1562
|
+
this.streamingState = null;
|
|
1563
|
+
this.options.onError?.(errorObj);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Handle client tool request event.
|
|
1568
|
+
*
|
|
1569
|
+
* IMPORTANT: Interactive tools must be registered synchronously (before any await)
|
|
1570
|
+
* to avoid a race condition where the finish event is processed before tools are added.
|
|
1571
|
+
*/
|
|
1572
|
+
async handleClientToolRequest(toolCalls, state) {
|
|
1573
|
+
this._clientToolAbortController = new AbortController();
|
|
1574
|
+
for (const tc of toolCalls) {
|
|
1575
|
+
const handler = this.options.clientTools?.[tc.toolName];
|
|
1576
|
+
if (handler === "interactive") {
|
|
1577
|
+
const toolState = {
|
|
1578
|
+
toolCallId: tc.toolCallId,
|
|
1579
|
+
toolName: tc.toolName,
|
|
1580
|
+
args: tc.args,
|
|
1581
|
+
source: tc.source,
|
|
1582
|
+
outputVariable: tc.outputVariable,
|
|
1583
|
+
blockIndex: tc.blockIndex,
|
|
1584
|
+
thread: tc.thread,
|
|
1585
|
+
workerId: tc.workerId
|
|
1586
|
+
};
|
|
1587
|
+
this._pendingToolsByCallId.set(tc.toolCallId, toolState);
|
|
1588
|
+
const existing = this._pendingToolsByName.get(tc.toolName) ?? [];
|
|
1589
|
+
this._pendingToolsByName.set(tc.toolName, [...existing, toolState]);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
if (this._pendingToolsByCallId.size > 0) {
|
|
1593
|
+
this.updatePendingClientToolsCache();
|
|
1594
|
+
}
|
|
1595
|
+
for (const tc of toolCalls) {
|
|
1596
|
+
const handler = this.options.clientTools?.[tc.toolName];
|
|
1597
|
+
if (handler === "interactive") {
|
|
1598
|
+
const toolPartIndex = state.parts.findIndex(
|
|
1599
|
+
(p) => p.type === "tool-call" && p.toolCallId === tc.toolCallId
|
|
1600
|
+
);
|
|
1601
|
+
if (toolPartIndex >= 0) {
|
|
1602
|
+
const part = state.parts[toolPartIndex];
|
|
1603
|
+
state.parts[toolPartIndex] = { ...part };
|
|
1604
|
+
}
|
|
1605
|
+
} else if (handler) {
|
|
1606
|
+
try {
|
|
1607
|
+
const collectedFiles = [];
|
|
1608
|
+
const result = await handler(tc.args, {
|
|
1609
|
+
toolCallId: tc.toolCallId,
|
|
1610
|
+
toolName: tc.toolName,
|
|
1611
|
+
signal: this._clientToolAbortController.signal,
|
|
1612
|
+
addFile: (file) => collectedFiles.push(file)
|
|
1613
|
+
});
|
|
1614
|
+
this._completedToolResults.push({
|
|
1615
|
+
toolCallId: tc.toolCallId,
|
|
1616
|
+
toolName: tc.toolName,
|
|
1617
|
+
result,
|
|
1618
|
+
files: collectedFiles.length > 0 ? collectedFiles : void 0,
|
|
1619
|
+
outputVariable: tc.outputVariable,
|
|
1620
|
+
blockIndex: tc.blockIndex,
|
|
1621
|
+
thread: tc.thread,
|
|
1622
|
+
workerId: tc.workerId
|
|
1623
|
+
});
|
|
1624
|
+
this.emitToolOutputAvailable(tc.toolCallId, result);
|
|
1625
|
+
} catch (err) {
|
|
1626
|
+
const errorMessage = err instanceof Error ? err.message : "Tool execution failed";
|
|
1627
|
+
this._completedToolResults.push({
|
|
1628
|
+
toolCallId: tc.toolCallId,
|
|
1629
|
+
toolName: tc.toolName,
|
|
1630
|
+
error: errorMessage,
|
|
1631
|
+
outputVariable: tc.outputVariable,
|
|
1632
|
+
blockIndex: tc.blockIndex,
|
|
1633
|
+
thread: tc.thread,
|
|
1634
|
+
workerId: tc.workerId
|
|
1635
|
+
});
|
|
1636
|
+
this.emitToolOutputError(tc.toolCallId, errorMessage);
|
|
1637
|
+
}
|
|
1638
|
+
} else {
|
|
1639
|
+
const errorMessage = `No client handler for tool: ${tc.toolName}`;
|
|
1640
|
+
this._completedToolResults.push({
|
|
1641
|
+
toolCallId: tc.toolCallId,
|
|
1642
|
+
toolName: tc.toolName,
|
|
1643
|
+
error: errorMessage,
|
|
1644
|
+
outputVariable: tc.outputVariable,
|
|
1645
|
+
blockIndex: tc.blockIndex,
|
|
1646
|
+
thread: tc.thread,
|
|
1647
|
+
workerId: tc.workerId
|
|
1648
|
+
});
|
|
1649
|
+
this.emitToolOutputError(tc.toolCallId, errorMessage);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
if (this._pendingToolsByCallId.size === 0 && this._completedToolResults.length > 0) {
|
|
1653
|
+
this._readyToContinue = true;
|
|
1654
|
+
if (this._finishEventReceived) {
|
|
1655
|
+
this._readyToContinue = false;
|
|
1656
|
+
this._finishEventReceived = false;
|
|
1657
|
+
void this.continueWithClientToolResults();
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
|
|
1663
|
+
// src/stream/reader.ts
|
|
1664
|
+
var import_core2 = require("@octavus/core");
|
|
1665
|
+
async function* parseSSEStream(response, signal) {
|
|
1666
|
+
const reader = response.body?.getReader();
|
|
1667
|
+
if (!reader) {
|
|
1668
|
+
throw new Error("Response body is not readable");
|
|
1669
|
+
}
|
|
1670
|
+
const decoder = new TextDecoder();
|
|
1671
|
+
let buffer = "";
|
|
1672
|
+
try {
|
|
1673
|
+
let reading = true;
|
|
1674
|
+
while (reading) {
|
|
1675
|
+
if (signal?.aborted) {
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
let readResult;
|
|
1679
|
+
try {
|
|
1680
|
+
readResult = await reader.read();
|
|
1681
|
+
} catch (err) {
|
|
1682
|
+
if ((0, import_core2.isAbortError)(err)) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
throw err;
|
|
1686
|
+
}
|
|
1687
|
+
const { done, value } = readResult;
|
|
1688
|
+
if (done) {
|
|
1689
|
+
reading = false;
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1693
|
+
const lines = buffer.split("\n");
|
|
1694
|
+
buffer = lines.pop() ?? "";
|
|
1695
|
+
for (const line of lines) {
|
|
1696
|
+
if (line.startsWith("data: ") && line !== "data: [DONE]") {
|
|
1697
|
+
try {
|
|
1698
|
+
const parsed = (0, import_core2.safeParseStreamEvent)(JSON.parse(line.slice(6)));
|
|
1699
|
+
if (parsed.success) {
|
|
1700
|
+
yield parsed.data;
|
|
1701
|
+
}
|
|
1702
|
+
} catch {
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
} finally {
|
|
1708
|
+
reader.releaseLock();
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
// src/transports/types.ts
|
|
1713
|
+
function isSocketTransport(transport) {
|
|
1714
|
+
return "connect" in transport && "disconnect" in transport && "connectionState" in transport && "onConnectionStateChange" in transport;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/transports/http.ts
|
|
1718
|
+
var import_core3 = require("@octavus/core");
|
|
1719
|
+
function createHttpTransport(options) {
|
|
1720
|
+
let abortController = null;
|
|
1721
|
+
async function* streamResponse(responsePromise) {
|
|
1722
|
+
try {
|
|
1723
|
+
const response = await responsePromise;
|
|
1724
|
+
if (!response.ok) {
|
|
1725
|
+
const errorText = await response.text().catch(() => `Request failed: ${response.status}`);
|
|
1726
|
+
throw new Error(errorText);
|
|
1727
|
+
}
|
|
1728
|
+
if (!response.body) {
|
|
1729
|
+
throw new Error("Response body is empty");
|
|
1730
|
+
}
|
|
1731
|
+
for await (const event of parseSSEStream(response, abortController.signal)) {
|
|
1732
|
+
if (abortController?.signal.aborted) {
|
|
1733
|
+
break;
|
|
1734
|
+
}
|
|
1735
|
+
yield event;
|
|
1736
|
+
}
|
|
1737
|
+
} catch (err) {
|
|
1738
|
+
if ((0, import_core3.isAbortError)(err)) {
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
throw err;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
return {
|
|
1745
|
+
async *trigger(triggerName, input, triggerOptions) {
|
|
1746
|
+
abortController = new AbortController();
|
|
1747
|
+
const request = { type: "trigger", triggerName, input };
|
|
1748
|
+
if (triggerOptions?.rollbackAfterMessageId !== void 0) {
|
|
1749
|
+
request.rollbackAfterMessageId = triggerOptions.rollbackAfterMessageId;
|
|
1750
|
+
}
|
|
1751
|
+
const response = options.request(request, { signal: abortController.signal });
|
|
1752
|
+
yield* streamResponse(response);
|
|
1753
|
+
},
|
|
1754
|
+
async *continueWithToolResults(executionId, toolResults) {
|
|
1755
|
+
abortController = new AbortController();
|
|
1756
|
+
const response = options.request(
|
|
1757
|
+
{ type: "continue", executionId, toolResults },
|
|
1758
|
+
{ signal: abortController.signal }
|
|
1759
|
+
);
|
|
1760
|
+
yield* streamResponse(response);
|
|
1761
|
+
},
|
|
1762
|
+
stop() {
|
|
1763
|
+
abortController?.abort();
|
|
1764
|
+
abortController = null;
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/transports/socket.ts
|
|
1770
|
+
var import_core4 = require("@octavus/core");
|
|
1771
|
+
var SOCKET_OPEN = 1;
|
|
1772
|
+
function createSocketTransport(options) {
|
|
1773
|
+
let socket = null;
|
|
1774
|
+
let eventQueue = [];
|
|
1775
|
+
let eventResolver = null;
|
|
1776
|
+
let isStreaming = false;
|
|
1777
|
+
let connectionState = "disconnected";
|
|
1778
|
+
let connectionError;
|
|
1779
|
+
let connectionPromise = null;
|
|
1780
|
+
const connectionListeners = /* @__PURE__ */ new Set();
|
|
1781
|
+
function setConnectionState(state, error) {
|
|
1782
|
+
connectionState = state;
|
|
1783
|
+
connectionError = error;
|
|
1784
|
+
connectionListeners.forEach((listener) => listener(state, error));
|
|
1785
|
+
}
|
|
1786
|
+
function enqueueEvent(event) {
|
|
1787
|
+
if (eventResolver) {
|
|
1788
|
+
eventResolver(event);
|
|
1789
|
+
eventResolver = null;
|
|
1790
|
+
} else {
|
|
1791
|
+
eventQueue.push(event);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
function nextEvent() {
|
|
1795
|
+
if (eventQueue.length > 0) {
|
|
1796
|
+
return Promise.resolve(eventQueue.shift());
|
|
1797
|
+
}
|
|
1798
|
+
if (!isStreaming) {
|
|
1799
|
+
return Promise.resolve(null);
|
|
1800
|
+
}
|
|
1801
|
+
return new Promise((resolve) => {
|
|
1802
|
+
eventResolver = resolve;
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
function setupSocketHandlers(sock) {
|
|
1806
|
+
sock.onmessage = (e) => {
|
|
1807
|
+
try {
|
|
1808
|
+
const data = typeof e.data === "string" ? JSON.parse(e.data) : e.data;
|
|
1809
|
+
options.onMessage?.(data);
|
|
1810
|
+
const result = (0, import_core4.safeParseStreamEvent)(data);
|
|
1811
|
+
if (result.success) {
|
|
1812
|
+
const event = result.data;
|
|
1813
|
+
enqueueEvent(event);
|
|
1814
|
+
if (event.type === "finish" || event.type === "error") {
|
|
1815
|
+
isStreaming = false;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
} catch {
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
sock.onclose = () => {
|
|
1822
|
+
socket = null;
|
|
1823
|
+
connectionPromise = null;
|
|
1824
|
+
setConnectionState("disconnected");
|
|
1825
|
+
options.onClose?.();
|
|
1826
|
+
isStreaming = false;
|
|
1827
|
+
if (eventResolver) {
|
|
1828
|
+
eventResolver(null);
|
|
1829
|
+
eventResolver = null;
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
async function ensureConnected() {
|
|
1834
|
+
if (socket?.readyState === SOCKET_OPEN) {
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
if (connectionPromise) {
|
|
1838
|
+
await connectionPromise;
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
setConnectionState("connecting");
|
|
1842
|
+
connectionPromise = (async () => {
|
|
1843
|
+
try {
|
|
1844
|
+
const sock = await options.connect();
|
|
1845
|
+
socket = sock;
|
|
1846
|
+
setupSocketHandlers(sock);
|
|
1847
|
+
setConnectionState("connected");
|
|
1848
|
+
} catch (err) {
|
|
1849
|
+
socket = null;
|
|
1850
|
+
connectionPromise = null;
|
|
1851
|
+
const error = err instanceof Error ? err : new Error("Connection failed");
|
|
1852
|
+
setConnectionState("error", error);
|
|
1853
|
+
throw error;
|
|
1854
|
+
}
|
|
1855
|
+
})();
|
|
1856
|
+
await connectionPromise;
|
|
1857
|
+
}
|
|
1858
|
+
return {
|
|
1859
|
+
// =========================================================================
|
|
1860
|
+
// Connection Management
|
|
1861
|
+
// =========================================================================
|
|
1862
|
+
get connectionState() {
|
|
1863
|
+
return connectionState;
|
|
1864
|
+
},
|
|
1865
|
+
onConnectionStateChange(listener) {
|
|
1866
|
+
connectionListeners.add(listener);
|
|
1867
|
+
listener(connectionState, connectionError);
|
|
1868
|
+
return () => connectionListeners.delete(listener);
|
|
1869
|
+
},
|
|
1870
|
+
async connect() {
|
|
1871
|
+
await ensureConnected();
|
|
1872
|
+
},
|
|
1873
|
+
disconnect() {
|
|
1874
|
+
if (socket) {
|
|
1875
|
+
socket.close();
|
|
1876
|
+
socket = null;
|
|
1877
|
+
}
|
|
1878
|
+
connectionPromise = null;
|
|
1879
|
+
isStreaming = false;
|
|
1880
|
+
if (eventResolver) {
|
|
1881
|
+
eventResolver(null);
|
|
1882
|
+
eventResolver = null;
|
|
1883
|
+
}
|
|
1884
|
+
setConnectionState("disconnected");
|
|
1885
|
+
},
|
|
1886
|
+
// =========================================================================
|
|
1887
|
+
// Streaming
|
|
1888
|
+
// =========================================================================
|
|
1889
|
+
async *trigger(triggerName, input, triggerOptions) {
|
|
1890
|
+
await ensureConnected();
|
|
1891
|
+
eventQueue = [];
|
|
1892
|
+
eventResolver = null;
|
|
1893
|
+
isStreaming = true;
|
|
1894
|
+
const message = { type: "trigger", triggerName, input };
|
|
1895
|
+
if (triggerOptions?.rollbackAfterMessageId !== void 0) {
|
|
1896
|
+
message.rollbackAfterMessageId = triggerOptions.rollbackAfterMessageId;
|
|
1897
|
+
}
|
|
1898
|
+
socket.send(JSON.stringify(message));
|
|
1899
|
+
while (true) {
|
|
1900
|
+
const event = await nextEvent();
|
|
1901
|
+
if (event === null) break;
|
|
1902
|
+
yield event;
|
|
1903
|
+
if (event.type === "finish" || event.type === "error") break;
|
|
1904
|
+
}
|
|
1905
|
+
},
|
|
1906
|
+
stop() {
|
|
1907
|
+
if (socket?.readyState === SOCKET_OPEN) {
|
|
1908
|
+
socket.send(JSON.stringify({ type: "stop" }));
|
|
1909
|
+
}
|
|
1910
|
+
isStreaming = false;
|
|
1911
|
+
if (eventResolver) {
|
|
1912
|
+
eventResolver(null);
|
|
1913
|
+
eventResolver = null;
|
|
1914
|
+
}
|
|
1915
|
+
},
|
|
1916
|
+
/**
|
|
1917
|
+
* Continue execution with tool results after client-side tool handling.
|
|
1918
|
+
* @param executionId - The execution ID from the client-tool-request event
|
|
1919
|
+
* @param toolResults - All tool results (server + client) to send
|
|
1920
|
+
*/
|
|
1921
|
+
async *continueWithToolResults(executionId, toolResults) {
|
|
1922
|
+
await ensureConnected();
|
|
1923
|
+
eventQueue = [];
|
|
1924
|
+
eventResolver = null;
|
|
1925
|
+
isStreaming = true;
|
|
1926
|
+
socket.send(
|
|
1927
|
+
JSON.stringify({
|
|
1928
|
+
type: "continue",
|
|
1929
|
+
executionId,
|
|
1930
|
+
toolResults
|
|
1931
|
+
})
|
|
1932
|
+
);
|
|
1933
|
+
while (true) {
|
|
1934
|
+
const event = await nextEvent();
|
|
1935
|
+
if (event === null) break;
|
|
1936
|
+
yield event;
|
|
1937
|
+
if (event.type === "finish" || event.type === "error") break;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// src/transports/polling.ts
|
|
1944
|
+
var DEFAULT_POLL_INTERVAL_MS = 500;
|
|
1945
|
+
function sleep(ms, signal) {
|
|
1946
|
+
return new Promise((resolve) => {
|
|
1947
|
+
const timer = setTimeout(resolve, ms);
|
|
1948
|
+
signal.addEventListener(
|
|
1949
|
+
"abort",
|
|
1950
|
+
() => {
|
|
1951
|
+
clearTimeout(timer);
|
|
1952
|
+
resolve();
|
|
1953
|
+
},
|
|
1954
|
+
{ once: true }
|
|
1955
|
+
);
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
function createPollingTransport(options) {
|
|
1959
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1960
|
+
let abortController = null;
|
|
1961
|
+
function startPolling() {
|
|
1962
|
+
abortController?.abort();
|
|
1963
|
+
abortController = new AbortController();
|
|
1964
|
+
return abortController.signal;
|
|
1965
|
+
}
|
|
1966
|
+
async function* pollLoop(signal) {
|
|
1967
|
+
let cursor = 0;
|
|
1968
|
+
while (!signal.aborted) {
|
|
1969
|
+
const result = await options.onPoll(cursor);
|
|
1970
|
+
if (signal.aborted) break;
|
|
1971
|
+
cursor = result.cursor;
|
|
1972
|
+
for (const event of result.events) {
|
|
1973
|
+
yield event;
|
|
1974
|
+
}
|
|
1975
|
+
if (result.status === "error" && !result.events.some(
|
|
1976
|
+
(event) => typeof event === "object" && event !== null && "type" in event && event.type === "error"
|
|
1977
|
+
)) {
|
|
1978
|
+
throw new Error("Execution failed");
|
|
1979
|
+
}
|
|
1980
|
+
if (result.status !== "streaming") break;
|
|
1981
|
+
await sleep(pollIntervalMs, signal);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
return {
|
|
1985
|
+
async *trigger(triggerName, input) {
|
|
1986
|
+
const signal = startPolling();
|
|
1987
|
+
const result = await options.onTrigger(triggerName, input);
|
|
1988
|
+
if (result.error) throw new Error(result.error);
|
|
1989
|
+
if (signal.aborted) return;
|
|
1990
|
+
yield* pollLoop(signal);
|
|
1991
|
+
},
|
|
1992
|
+
// eslint-disable-next-line require-yield, @typescript-eslint/require-await
|
|
1993
|
+
async *continueWithToolResults() {
|
|
1994
|
+
throw new Error("continueWithToolResults is not supported by polling transport");
|
|
1995
|
+
},
|
|
1996
|
+
async *observe() {
|
|
1997
|
+
yield* pollLoop(startPolling());
|
|
1998
|
+
},
|
|
1999
|
+
stop() {
|
|
2000
|
+
abortController?.abort();
|
|
2001
|
+
abortController = null;
|
|
2002
|
+
options.onStop();
|
|
2003
|
+
}
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
// src/index.ts
|
|
2008
|
+
var import_core5 = require("@octavus/core");
|
|
2009
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2010
|
+
0 && (module.exports = {
|
|
2011
|
+
AppError,
|
|
2012
|
+
ConflictError,
|
|
2013
|
+
ForbiddenError,
|
|
2014
|
+
MAIN_THREAD,
|
|
2015
|
+
NotFoundError,
|
|
2016
|
+
OCTAVUS_SKILL_TOOLS,
|
|
2017
|
+
OctavusChat,
|
|
2018
|
+
OctavusError,
|
|
2019
|
+
ValidationError,
|
|
2020
|
+
createApiErrorEvent,
|
|
2021
|
+
createErrorEvent,
|
|
2022
|
+
createHttpTransport,
|
|
2023
|
+
createInternalErrorEvent,
|
|
2024
|
+
createPollingTransport,
|
|
2025
|
+
createSocketTransport,
|
|
2026
|
+
errorToStreamEvent,
|
|
2027
|
+
generateId,
|
|
2028
|
+
getSkillSlugFromToolCall,
|
|
2029
|
+
isAbortError,
|
|
2030
|
+
isAuthenticationError,
|
|
2031
|
+
isFileReference,
|
|
2032
|
+
isFileReferenceArray,
|
|
2033
|
+
isMainThread,
|
|
2034
|
+
isOctavusSkillTool,
|
|
2035
|
+
isOtherThread,
|
|
2036
|
+
isProviderError,
|
|
2037
|
+
isRateLimitError,
|
|
2038
|
+
isRetryableError,
|
|
2039
|
+
isSocketTransport,
|
|
2040
|
+
isToolError,
|
|
2041
|
+
isValidationError,
|
|
2042
|
+
parseSSEStream,
|
|
2043
|
+
resolveThread,
|
|
2044
|
+
safeParseStreamEvent,
|
|
2045
|
+
safeParseUIMessage,
|
|
2046
|
+
safeParseUIMessages,
|
|
2047
|
+
threadForPart,
|
|
2048
|
+
uploadFiles
|
|
2049
|
+
});
|
|
2050
|
+
//# sourceMappingURL=index.cjs.map
|