@octavus/client-sdk 2.19.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 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