@octavus/server-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 ADDED
@@ -0,0 +1,1109 @@
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
+ AgentSession: () => AgentSession,
24
+ AgentSessionsApi: () => AgentSessionsApi,
25
+ AgentsApi: () => AgentsApi,
26
+ ApiError: () => ApiError,
27
+ AppError: () => import_core5.AppError,
28
+ ConflictError: () => import_core5.ConflictError,
29
+ FilesApi: () => FilesApi,
30
+ ForbiddenError: () => import_core5.ForbiddenError,
31
+ MAIN_THREAD: () => import_core5.MAIN_THREAD,
32
+ NotFoundError: () => import_core5.NotFoundError,
33
+ OCTAVUS_SKILL_TOOLS: () => import_core5.OCTAVUS_SKILL_TOOLS,
34
+ OctavusClient: () => OctavusClient,
35
+ OctavusError: () => import_core5.OctavusError,
36
+ Resource: () => Resource,
37
+ ValidationError: () => import_core5.ValidationError,
38
+ WorkerError: () => WorkerError,
39
+ WorkersApi: () => WorkersApi,
40
+ createApiErrorEvent: () => import_core5.createApiErrorEvent,
41
+ createErrorEvent: () => import_core5.createErrorEvent,
42
+ createInternalErrorEvent: () => import_core5.createInternalErrorEvent,
43
+ errorToStreamEvent: () => import_core5.errorToStreamEvent,
44
+ generateId: () => import_core5.generateId,
45
+ getSkillSlugFromToolCall: () => import_core5.getSkillSlugFromToolCall,
46
+ isAbortError: () => import_core5.isAbortError,
47
+ isAuthenticationError: () => import_core5.isAuthenticationError,
48
+ isFileReference: () => import_core5.isFileReference,
49
+ isFileReferenceArray: () => import_core5.isFileReferenceArray,
50
+ isMainThread: () => import_core5.isMainThread,
51
+ isOctavusSkillTool: () => import_core5.isOctavusSkillTool,
52
+ isOtherThread: () => import_core5.isOtherThread,
53
+ isProviderError: () => import_core5.isProviderError,
54
+ isRateLimitError: () => import_core5.isRateLimitError,
55
+ isRetryableError: () => import_core5.isRetryableError,
56
+ isToolError: () => import_core5.isToolError,
57
+ isValidationError: () => import_core5.isValidationError,
58
+ normalizeToolResultImages: () => normalizeToolResultImages,
59
+ resolveThread: () => import_core5.resolveThread,
60
+ safeParseStreamEvent: () => import_core5.safeParseStreamEvent,
61
+ safeParseUIMessage: () => import_core5.safeParseUIMessage,
62
+ safeParseUIMessages: () => import_core5.safeParseUIMessages,
63
+ threadForPart: () => import_core5.threadForPart,
64
+ toSSEStream: () => toSSEStream
65
+ });
66
+ module.exports = __toCommonJS(index_exports);
67
+
68
+ // src/api-error.ts
69
+ var import_zod = require("zod");
70
+ var ApiErrorResponseSchema = import_zod.z.object({
71
+ error: import_zod.z.string().optional(),
72
+ message: import_zod.z.string().optional(),
73
+ code: import_zod.z.string().optional()
74
+ });
75
+ var ApiError = class extends Error {
76
+ constructor(message, status, code) {
77
+ super(message);
78
+ this.status = status;
79
+ this.code = code;
80
+ this.name = "ApiError";
81
+ }
82
+ };
83
+ async function parseApiError(response, defaultMessage) {
84
+ const fallbackMessage = `${defaultMessage}: ${response.statusText}`;
85
+ try {
86
+ const json = await response.json();
87
+ const parsed = ApiErrorResponseSchema.safeParse(json);
88
+ if (parsed.success) {
89
+ return {
90
+ message: parsed.data.error ?? parsed.data.message ?? fallbackMessage,
91
+ code: parsed.data.code
92
+ };
93
+ }
94
+ } catch {
95
+ }
96
+ return { message: fallbackMessage };
97
+ }
98
+ async function throwApiError(response, defaultMessage) {
99
+ const { message, code } = await parseApiError(response, defaultMessage);
100
+ throw new ApiError(message, response.status, code);
101
+ }
102
+
103
+ // src/base-api-client.ts
104
+ var BaseApiClient = class {
105
+ config;
106
+ constructor(config) {
107
+ this.config = config;
108
+ }
109
+ async httpGet(path, schema) {
110
+ const response = await fetch(`${this.config.baseUrl}${path}`, {
111
+ method: "GET",
112
+ headers: this.config.getHeaders()
113
+ });
114
+ if (!response.ok) {
115
+ await throwApiError(response, "Request failed");
116
+ }
117
+ const data = await response.json();
118
+ return schema.parse(data);
119
+ }
120
+ async httpPost(path, body, schema) {
121
+ const response = await fetch(`${this.config.baseUrl}${path}`, {
122
+ method: "POST",
123
+ headers: this.config.getHeaders(),
124
+ body: JSON.stringify(body)
125
+ });
126
+ if (!response.ok) {
127
+ await throwApiError(response, "Request failed");
128
+ }
129
+ const data = await response.json();
130
+ return schema.parse(data);
131
+ }
132
+ async httpPatch(path, body, schema) {
133
+ const response = await fetch(`${this.config.baseUrl}${path}`, {
134
+ method: "PATCH",
135
+ headers: this.config.getHeaders(),
136
+ body: JSON.stringify(body)
137
+ });
138
+ if (!response.ok) {
139
+ await throwApiError(response, "Request failed");
140
+ }
141
+ const data = await response.json();
142
+ return schema.parse(data);
143
+ }
144
+ async httpDelete(path, schema) {
145
+ const response = await fetch(`${this.config.baseUrl}${path}`, {
146
+ method: "DELETE",
147
+ headers: this.config.getHeaders()
148
+ });
149
+ if (!response.ok) {
150
+ await throwApiError(response, "Request failed");
151
+ }
152
+ const data = await response.json();
153
+ return schema.parse(data);
154
+ }
155
+ };
156
+
157
+ // src/agent-types.ts
158
+ var import_zod2 = require("zod");
159
+ var agentFormatSchema = import_zod2.z.enum(["interactive", "worker"]);
160
+ var agentSettingsSchema = import_zod2.z.object({
161
+ slug: import_zod2.z.string(),
162
+ name: import_zod2.z.string(),
163
+ description: import_zod2.z.string().optional(),
164
+ format: agentFormatSchema
165
+ });
166
+ var agentPromptSchema = import_zod2.z.object({
167
+ name: import_zod2.z.string(),
168
+ content: import_zod2.z.string()
169
+ });
170
+ var agentSchema = import_zod2.z.object({
171
+ slug: import_zod2.z.string(),
172
+ id: import_zod2.z.string(),
173
+ name: import_zod2.z.string(),
174
+ description: import_zod2.z.string().nullable(),
175
+ format: agentFormatSchema,
176
+ createdAt: import_zod2.z.string(),
177
+ updatedAt: import_zod2.z.string(),
178
+ projectId: import_zod2.z.string()
179
+ });
180
+ var agentsResponseSchema = import_zod2.z.object({
181
+ agents: import_zod2.z.array(agentSchema)
182
+ });
183
+ var agentDefinitionSchema = import_zod2.z.object({
184
+ settings: agentSettingsSchema,
185
+ protocol: import_zod2.z.string(),
186
+ prompts: import_zod2.z.array(agentPromptSchema),
187
+ id: import_zod2.z.string()
188
+ });
189
+
190
+ // src/agents.ts
191
+ var AgentsApi = class extends BaseApiClient {
192
+ /** List all agents in the project */
193
+ async list() {
194
+ const response = await this.httpGet("/api/agents", agentsResponseSchema);
195
+ return response.agents;
196
+ }
197
+ /** Get a single agent by ID */
198
+ async get(agentId) {
199
+ return await this.httpGet(`/api/agents/${agentId}`, agentDefinitionSchema);
200
+ }
201
+ };
202
+
203
+ // src/agent-sessions.ts
204
+ var import_zod3 = require("zod");
205
+ var import_core3 = require("@octavus/core");
206
+
207
+ // src/session.ts
208
+ var import_core2 = require("@octavus/core");
209
+
210
+ // src/streaming.ts
211
+ var import_core = require("@octavus/core");
212
+ async function* executeStream(config, payload, signal) {
213
+ let toolResults = payload.toolResults;
214
+ let executionId = payload.executionId;
215
+ let continueLoop = true;
216
+ while (continueLoop) {
217
+ if (signal?.aborted) {
218
+ yield { type: "finish", finishReason: "stop" };
219
+ return;
220
+ }
221
+ const body = config.buildBody({ executionId, toolResults });
222
+ let response;
223
+ try {
224
+ response = await fetch(config.url, {
225
+ method: "POST",
226
+ headers: config.config.getHeaders(),
227
+ body: JSON.stringify(body),
228
+ signal
229
+ });
230
+ } catch (err) {
231
+ if ((0, import_core.isAbortError)(err)) {
232
+ yield { type: "finish", finishReason: "stop" };
233
+ return;
234
+ }
235
+ throw err;
236
+ }
237
+ if (!response.ok) {
238
+ const { message } = await parseApiError(response, config.errorContext ?? "Request failed");
239
+ yield (0, import_core.createApiErrorEvent)(response.status, message);
240
+ return;
241
+ }
242
+ if (!response.body) {
243
+ yield (0, import_core.createInternalErrorEvent)("Response body is not readable");
244
+ return;
245
+ }
246
+ toolResults = void 0;
247
+ const reader = response.body.getReader();
248
+ const decoder = new TextDecoder();
249
+ let buffer = "";
250
+ let pendingToolCalls = null;
251
+ let streamDone = false;
252
+ while (!streamDone) {
253
+ if (signal?.aborted) {
254
+ reader.releaseLock();
255
+ yield { type: "finish", finishReason: "stop" };
256
+ return;
257
+ }
258
+ let readResult;
259
+ try {
260
+ readResult = await reader.read();
261
+ } catch (err) {
262
+ if ((0, import_core.isAbortError)(err)) {
263
+ reader.releaseLock();
264
+ yield { type: "finish", finishReason: "stop" };
265
+ return;
266
+ }
267
+ throw err;
268
+ }
269
+ const { done, value } = readResult;
270
+ if (done) {
271
+ streamDone = true;
272
+ continue;
273
+ }
274
+ buffer += decoder.decode(value, { stream: true });
275
+ const lines = buffer.split("\n");
276
+ buffer = lines.pop() ?? "";
277
+ for (const line of lines) {
278
+ if (line.startsWith("data: ") && line !== "data: [DONE]") {
279
+ try {
280
+ const parsed = (0, import_core.safeParseStreamEvent)(JSON.parse(line.slice(6)));
281
+ if (!parsed.success) {
282
+ continue;
283
+ }
284
+ const event = parsed.data;
285
+ if (event.type === "start" && event.executionId) {
286
+ executionId = event.executionId;
287
+ }
288
+ if (event.type === "tool-request") {
289
+ pendingToolCalls = event.toolCalls;
290
+ continue;
291
+ }
292
+ if (event.type === "finish") {
293
+ if (event.finishReason === "tool-calls" && pendingToolCalls) {
294
+ continue;
295
+ }
296
+ yield event;
297
+ continueLoop = false;
298
+ continue;
299
+ }
300
+ if (event.type === "resource-update" && config.onResourceUpdate) {
301
+ config.onResourceUpdate(event.name, event.value);
302
+ }
303
+ yield event;
304
+ } catch {
305
+ }
306
+ }
307
+ }
308
+ }
309
+ if (signal?.aborted) {
310
+ yield { type: "finish", finishReason: "stop" };
311
+ return;
312
+ }
313
+ if (pendingToolCalls && pendingToolCalls.length > 0) {
314
+ const serverTools = pendingToolCalls.filter((tc) => config.toolHandlers[tc.toolName]);
315
+ const clientTools = pendingToolCalls.filter((tc) => !config.toolHandlers[tc.toolName]);
316
+ const serverResults = await Promise.all(
317
+ serverTools.map(async (tc) => {
318
+ const handler = config.toolHandlers[tc.toolName];
319
+ try {
320
+ const result = await handler(tc.args);
321
+ return {
322
+ toolCallId: tc.toolCallId,
323
+ toolName: tc.toolName,
324
+ result,
325
+ outputVariable: tc.outputVariable,
326
+ blockIndex: tc.blockIndex,
327
+ thread: tc.thread,
328
+ workerId: tc.workerId
329
+ };
330
+ } catch (err) {
331
+ return {
332
+ toolCallId: tc.toolCallId,
333
+ toolName: tc.toolName,
334
+ error: err instanceof Error ? err.message : "Tool execution failed",
335
+ outputVariable: tc.outputVariable,
336
+ blockIndex: tc.blockIndex,
337
+ thread: tc.thread,
338
+ workerId: tc.workerId
339
+ };
340
+ }
341
+ })
342
+ );
343
+ if (config.onToolResults && serverResults.length > 0) {
344
+ await config.onToolResults(serverResults);
345
+ }
346
+ for (const tr of serverResults) {
347
+ if (tr.error) {
348
+ yield { type: "tool-output-error", toolCallId: tr.toolCallId, error: tr.error };
349
+ } else {
350
+ yield { type: "tool-output-available", toolCallId: tr.toolCallId, output: tr.result };
351
+ }
352
+ }
353
+ if (clientTools.length > 0) {
354
+ if (config.rejectClientToolCalls) {
355
+ const rejectedResults = clientTools.map((tc) => ({
356
+ toolCallId: tc.toolCallId,
357
+ toolName: tc.toolName,
358
+ error: `Tool "${tc.toolName}" is not available. No handler is registered for this tool.`,
359
+ outputVariable: tc.outputVariable,
360
+ blockIndex: tc.blockIndex,
361
+ thread: tc.thread,
362
+ workerId: tc.workerId
363
+ }));
364
+ for (const tr of rejectedResults) {
365
+ yield { type: "tool-output-error", toolCallId: tr.toolCallId, error: tr.error };
366
+ }
367
+ toolResults = [...serverResults, ...rejectedResults];
368
+ } else {
369
+ if (!executionId) {
370
+ yield (0, import_core.createInternalErrorEvent)("Missing executionId for client-tool-request");
371
+ return;
372
+ }
373
+ yield {
374
+ type: "client-tool-request",
375
+ executionId,
376
+ toolCalls: clientTools,
377
+ serverToolResults: serverResults.length > 0 ? serverResults : void 0
378
+ };
379
+ yield { type: "finish", finishReason: "client-tool-calls", executionId };
380
+ continueLoop = false;
381
+ }
382
+ } else {
383
+ toolResults = serverResults;
384
+ }
385
+ } else {
386
+ continueLoop = false;
387
+ }
388
+ }
389
+ }
390
+
391
+ // src/session.ts
392
+ var SSE_HEARTBEAT_INTERVAL_MS = 15e3;
393
+ function toSSEStream(events) {
394
+ const encoder = new TextEncoder();
395
+ const heartbeatBytes = encoder.encode(": heartbeat\n\n");
396
+ return new ReadableStream({
397
+ async start(controller) {
398
+ const heartbeat = setInterval(() => {
399
+ try {
400
+ controller.enqueue(heartbeatBytes);
401
+ } catch {
402
+ clearInterval(heartbeat);
403
+ }
404
+ }, SSE_HEARTBEAT_INTERVAL_MS);
405
+ try {
406
+ for await (const event of events) {
407
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}
408
+
409
+ `));
410
+ }
411
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
412
+ controller.close();
413
+ } catch (err) {
414
+ const errorEvent = (0, import_core2.createInternalErrorEvent)(
415
+ err instanceof Error ? err.message : "Unknown error"
416
+ );
417
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(errorEvent)}
418
+
419
+ `));
420
+ controller.close();
421
+ } finally {
422
+ clearInterval(heartbeat);
423
+ }
424
+ }
425
+ });
426
+ }
427
+ var AgentSession = class {
428
+ sessionId;
429
+ config;
430
+ toolHandlers;
431
+ resourceMap;
432
+ additionalToolSchemas;
433
+ additionalToolSchemasSent = false;
434
+ socketAbortController = null;
435
+ onToolResults;
436
+ rejectClientToolCalls;
437
+ constructor(sessionConfig) {
438
+ this.sessionId = sessionConfig.sessionId;
439
+ this.config = sessionConfig.config;
440
+ this.toolHandlers = sessionConfig.tools ?? {};
441
+ this.additionalToolSchemas = sessionConfig.additionalToolSchemas;
442
+ this.onToolResults = sessionConfig.onToolResults;
443
+ this.rejectClientToolCalls = sessionConfig.rejectClientToolCalls ?? false;
444
+ this.resourceMap = /* @__PURE__ */ new Map();
445
+ for (const resource of sessionConfig.resources ?? []) {
446
+ this.resourceMap.set(resource.name, resource);
447
+ }
448
+ }
449
+ /**
450
+ * Execute a session request and stream the response.
451
+ *
452
+ * This is the unified method that handles both triggers and continuations.
453
+ * Use this when you want to pass through requests from the client directly.
454
+ *
455
+ * @param request - The request (check `request.type` for the kind)
456
+ * @param options - Optional configuration including abort signal
457
+ *
458
+ * @example HTTP route (simple passthrough)
459
+ * ```typescript
460
+ * const events = session.execute(body, { signal: request.signal });
461
+ * return new Response(toSSEStream(events));
462
+ * ```
463
+ *
464
+ * @example WebSocket handler
465
+ * ```typescript
466
+ * socket.on('message', (data) => {
467
+ * const events = session.execute(data);
468
+ * for await (const event of events) {
469
+ * socket.send(JSON.stringify(event));
470
+ * }
471
+ * });
472
+ * ```
473
+ */
474
+ async *execute(request, options) {
475
+ if (request.type === "continue") {
476
+ yield* this.executeStream(
477
+ { executionId: request.executionId, toolResults: request.toolResults },
478
+ options?.signal
479
+ );
480
+ } else {
481
+ yield* this.executeStream(
482
+ {
483
+ triggerName: request.triggerName,
484
+ input: request.input,
485
+ rollbackAfterMessageId: request.rollbackAfterMessageId
486
+ },
487
+ options?.signal
488
+ );
489
+ }
490
+ }
491
+ getSessionId() {
492
+ return this.sessionId;
493
+ }
494
+ /**
495
+ * Handle a WebSocket protocol message (trigger, continue, or stop).
496
+ * Manages abort controller lifecycle internally.
497
+ *
498
+ * @example
499
+ * ```typescript
500
+ * conn.on('data', (raw) => {
501
+ * session.handleSocketMessage(JSON.parse(raw), {
502
+ * onEvent: (event) => conn.write(JSON.stringify(event)),
503
+ * onFinish: async () => {
504
+ * // Fetch messages and persist to your database for restoration
505
+ * },
506
+ * });
507
+ * });
508
+ * ```
509
+ */
510
+ async handleSocketMessage(message, handlers) {
511
+ if (message.type === "stop") {
512
+ this.socketAbortController?.abort();
513
+ return;
514
+ }
515
+ this.socketAbortController?.abort();
516
+ this.socketAbortController = new AbortController();
517
+ const localController = this.socketAbortController;
518
+ try {
519
+ const events = this.execute(message, { signal: localController.signal });
520
+ for await (const event of events) {
521
+ if (localController.signal.aborted) break;
522
+ handlers.onEvent(event);
523
+ }
524
+ if (!localController.signal.aborted && handlers.onFinish) {
525
+ await handlers.onFinish();
526
+ }
527
+ } catch (err) {
528
+ if (!localController.signal.aborted) {
529
+ const errorEvent = (0, import_core2.createInternalErrorEvent)(
530
+ err instanceof Error ? err.message : "Unknown error"
531
+ );
532
+ handlers.onEvent(errorEvent);
533
+ }
534
+ }
535
+ }
536
+ async *executeStream(payload, signal) {
537
+ yield* executeStream(
538
+ {
539
+ config: this.config,
540
+ toolHandlers: this.toolHandlers,
541
+ url: `${this.config.baseUrl}/api/agent-sessions/${this.sessionId}/trigger`,
542
+ buildBody: ({ executionId, toolResults }) => {
543
+ const body = {};
544
+ if (payload.triggerName !== void 0) body.triggerName = payload.triggerName;
545
+ if (payload.input !== void 0) body.input = payload.input;
546
+ if (payload.rollbackAfterMessageId !== void 0)
547
+ body.rollbackAfterMessageId = payload.rollbackAfterMessageId;
548
+ if (executionId !== void 0) body.executionId = executionId;
549
+ if (toolResults !== void 0) body.toolResults = toolResults;
550
+ if (!this.additionalToolSchemasSent && (this.additionalToolSchemas?.length ?? 0) > 0) {
551
+ body.additionalToolSchemas = this.additionalToolSchemas;
552
+ this.additionalToolSchemasSent = true;
553
+ }
554
+ return body;
555
+ },
556
+ onResourceUpdate: (name, value) => this.handleResourceUpdate(name, value),
557
+ onToolResults: this.onToolResults,
558
+ rejectClientToolCalls: this.rejectClientToolCalls,
559
+ errorContext: "Failed to trigger"
560
+ },
561
+ { executionId: payload.executionId, toolResults: payload.toolResults },
562
+ signal
563
+ );
564
+ }
565
+ handleResourceUpdate(name, value) {
566
+ const resource = this.resourceMap.get(name);
567
+ if (resource) {
568
+ void resource.onUpdate(value);
569
+ }
570
+ }
571
+ };
572
+
573
+ // src/agent-sessions.ts
574
+ var createSessionResponseSchema = import_zod3.z.object({
575
+ sessionId: import_zod3.z.string()
576
+ });
577
+ var sessionStateSchema = import_zod3.z.object({
578
+ id: import_zod3.z.string(),
579
+ agentId: import_zod3.z.string(),
580
+ input: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown()),
581
+ variables: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown()),
582
+ resources: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown()),
583
+ messages: import_zod3.z.array(import_core3.chatMessageSchema),
584
+ status: import_zod3.z.literal("active").optional(),
585
+ createdAt: import_zod3.z.string(),
586
+ updatedAt: import_zod3.z.string()
587
+ });
588
+ var uiSessionResponseSchema = import_zod3.z.object({
589
+ sessionId: import_zod3.z.string(),
590
+ agentId: import_zod3.z.string(),
591
+ messages: import_zod3.z.array(import_core3.uiMessageSchema),
592
+ status: import_zod3.z.literal("active").optional()
593
+ });
594
+ var expiredSessionResponseSchema = import_zod3.z.object({
595
+ sessionId: import_zod3.z.string(),
596
+ agentId: import_zod3.z.string(),
597
+ status: import_zod3.z.literal("expired"),
598
+ createdAt: import_zod3.z.string()
599
+ });
600
+ var restoreSessionResponseSchema = import_zod3.z.object({
601
+ sessionId: import_zod3.z.string(),
602
+ restored: import_zod3.z.boolean()
603
+ });
604
+ var clearSessionResponseSchema = import_zod3.z.object({
605
+ sessionId: import_zod3.z.string(),
606
+ cleared: import_zod3.z.boolean()
607
+ });
608
+ var AgentSessionsApi = class extends BaseApiClient {
609
+ /** Create a new session for an agent */
610
+ async create(agentId, input) {
611
+ const response = await this.httpPost(
612
+ "/api/agent-sessions",
613
+ { agentId, input },
614
+ createSessionResponseSchema
615
+ );
616
+ return response.sessionId;
617
+ }
618
+ /**
619
+ * Get full session state (for internal/debug use)
620
+ * Note: Contains all messages including hidden content
621
+ *
622
+ * Returns SessionState for active sessions, ExpiredSessionState for expired sessions.
623
+ * Check `status` field to determine which type was returned.
624
+ */
625
+ async get(sessionId) {
626
+ const response = await fetch(`${this.config.baseUrl}/api/agent-sessions/${sessionId}`, {
627
+ method: "GET",
628
+ headers: this.config.getHeaders()
629
+ });
630
+ if (!response.ok) {
631
+ await throwApiError(response, "Request failed");
632
+ }
633
+ const data = await response.json();
634
+ const expiredResult = expiredSessionResponseSchema.safeParse(data);
635
+ if (expiredResult.success) {
636
+ return expiredResult.data;
637
+ }
638
+ return sessionStateSchema.parse(data);
639
+ }
640
+ /**
641
+ * Get UI-ready session messages (for client display)
642
+ * Returns only visible messages with hidden content filtered out.
643
+ *
644
+ * For expired sessions, returns status: 'expired' without messages.
645
+ * Use restore() to restore from stored messages before continuing.
646
+ */
647
+ async getMessages(sessionId) {
648
+ const response = await fetch(
649
+ `${this.config.baseUrl}/api/agent-sessions/${sessionId}?format=ui`,
650
+ {
651
+ method: "GET",
652
+ headers: this.config.getHeaders()
653
+ }
654
+ );
655
+ if (!response.ok) {
656
+ await throwApiError(response, "Request failed");
657
+ }
658
+ const data = await response.json();
659
+ const expiredResult = expiredSessionResponseSchema.safeParse(data);
660
+ if (expiredResult.success) {
661
+ return expiredResult.data;
662
+ }
663
+ return uiSessionResponseSchema.parse(data);
664
+ }
665
+ /**
666
+ * Restore an expired session from stored messages.
667
+ *
668
+ * Use this to restore a session after its state has expired.
669
+ * The consumer should have stored the UIMessage[] array from previous interactions.
670
+ *
671
+ * @param sessionId - The session ID to restore
672
+ * @param messages - Previously stored UIMessage[] array
673
+ * @param input - Optional session input for system prompt interpolation (same as create)
674
+ * @returns { sessionId, restored: true } if restored, { sessionId, restored: false } if already active
675
+ */
676
+ async restore(sessionId, messages, input) {
677
+ return await this.httpPost(
678
+ `/api/agent-sessions/${sessionId}/restore`,
679
+ { messages, input },
680
+ restoreSessionResponseSchema
681
+ );
682
+ }
683
+ /**
684
+ * Clear session state from the server.
685
+ * The session will transition to 'expired' status and can be restored with restore().
686
+ * Idempotent: succeeds even if state was already cleared/expired.
687
+ */
688
+ async clear(sessionId) {
689
+ return await this.httpDelete(`/api/agent-sessions/${sessionId}`, clearSessionResponseSchema);
690
+ }
691
+ /** Attach to an existing session for triggering events */
692
+ attach(sessionId, options = {}) {
693
+ const computerHandlers = options.computer?.toolHandlers() ?? {};
694
+ const mergedTools = { ...computerHandlers, ...options.tools };
695
+ return new AgentSession({
696
+ sessionId,
697
+ config: this.config,
698
+ tools: mergedTools,
699
+ resources: options.resources,
700
+ additionalToolSchemas: options.computer?.toolSchemas(),
701
+ onToolResults: options.onToolResults,
702
+ rejectClientToolCalls: options.rejectClientToolCalls
703
+ });
704
+ }
705
+ };
706
+
707
+ // src/files.ts
708
+ var import_zod4 = require("zod");
709
+ var fileUploadRequestSchema = import_zod4.z.object({
710
+ filename: import_zod4.z.string().min(1).max(255),
711
+ mediaType: import_zod4.z.string().min(1),
712
+ size: import_zod4.z.number().int().positive()
713
+ });
714
+ var fileUploadInfoSchema = import_zod4.z.object({
715
+ /** File ID to reference in messages */
716
+ id: import_zod4.z.string(),
717
+ /** Presigned PUT URL for uploading to S3 */
718
+ uploadUrl: import_zod4.z.url(),
719
+ /** Presigned GET URL for downloading after upload */
720
+ downloadUrl: import_zod4.z.url()
721
+ });
722
+ var uploadUrlsResponseSchema = import_zod4.z.object({
723
+ files: import_zod4.z.array(fileUploadInfoSchema)
724
+ });
725
+ var FilesApi = class extends BaseApiClient {
726
+ /**
727
+ * Get presigned URLs for uploading files to a session.
728
+ *
729
+ * Returns upload URLs (PUT) and download URLs (GET) for each file.
730
+ * Upload URLs expire in 15 minutes, download URLs match session TTL (24 hours).
731
+ *
732
+ * @param sessionId - The session ID to associate files with
733
+ * @param files - Array of file metadata (filename, mediaType, size)
734
+ * @returns Upload info with presigned URLs for each file
735
+ *
736
+ * @throws ApiError if session doesn't exist or validation fails
737
+ *
738
+ * @example
739
+ * ```typescript
740
+ * const { files } = await client.files.getUploadUrls(sessionId, [
741
+ * { filename: 'photo.jpg', mediaType: 'image/jpeg', size: 102400 },
742
+ * { filename: 'doc.pdf', mediaType: 'application/pdf', size: 204800 },
743
+ * ]);
744
+ *
745
+ * // files[0].id - Use in FileReference
746
+ * // files[0].uploadUrl - PUT to this URL
747
+ * // files[0].downloadUrl - Use as FileReference.url
748
+ * ```
749
+ */
750
+ async getUploadUrls(sessionId, files) {
751
+ return await this.httpPost(
752
+ "/api/files/upload-urls",
753
+ { sessionId, files },
754
+ uploadUrlsResponseSchema
755
+ );
756
+ }
757
+ };
758
+
759
+ // src/worker-error.ts
760
+ var WorkerError = class extends Error {
761
+ constructor(message, sessionId) {
762
+ super(message);
763
+ this.sessionId = sessionId;
764
+ this.name = "WorkerError";
765
+ }
766
+ };
767
+
768
+ // src/workers.ts
769
+ var WorkersApi = class extends BaseApiClient {
770
+ /**
771
+ * Execute a worker agent and stream the response.
772
+ *
773
+ * Worker agents execute steps sequentially and return an output value.
774
+ * Unlike interactive sessions, workers don't maintain persistent state.
775
+ *
776
+ * The execution handles the tool continuation pattern automatically:
777
+ * - Server tools (with handlers provided) are executed automatically
778
+ * - Client tools (without handlers) emit a client-tool-request event
779
+ *
780
+ * @param agentId - The worker agent ID
781
+ * @param input - Input values for the worker
782
+ * @param options - Optional configuration including tools and abort signal
783
+ * @returns An async generator of stream events
784
+ *
785
+ * @example Basic execution
786
+ * ```typescript
787
+ * const events = client.workers.execute(agentId, { TOPIC: 'AI safety' });
788
+ * for await (const event of events) {
789
+ * if (event.type === 'worker-start') {
790
+ * console.log(`Worker ${event.workerSlug} started (${event.workerId})`);
791
+ * }
792
+ * if (event.type === 'worker-result') {
793
+ * if (event.error) {
794
+ * console.error('Worker failed:', event.error);
795
+ * } else {
796
+ * console.log('Output:', event.output);
797
+ * }
798
+ * }
799
+ * }
800
+ * ```
801
+ *
802
+ * @example With tool handlers
803
+ * ```typescript
804
+ * const events = client.workers.execute(agentId, { TOPIC: 'AI safety' }, {
805
+ * tools: {
806
+ * 'web-search': async (args) => {
807
+ * return await searchWeb(args.query);
808
+ * },
809
+ * },
810
+ * });
811
+ * ```
812
+ */
813
+ async *execute(agentId, input, options = {}) {
814
+ yield* executeStream(
815
+ {
816
+ config: this.config,
817
+ toolHandlers: options.tools ?? {},
818
+ url: `${this.config.baseUrl}/api/agents/${agentId}/execute`,
819
+ buildBody: ({ executionId, toolResults }) => !executionId ? { type: "start", input } : { type: "continue", executionId, toolResults },
820
+ errorContext: "Failed to execute worker"
821
+ },
822
+ {},
823
+ options.signal
824
+ );
825
+ }
826
+ /**
827
+ * Execute a worker agent and return the final output.
828
+ *
829
+ * Non-streaming equivalent of `execute()` — runs the worker to completion
830
+ * and returns the output value directly. Use this when you don't need to
831
+ * observe intermediate streaming events.
832
+ *
833
+ * @param agentId - The worker agent ID
834
+ * @param input - Input values for the worker
835
+ * @param options - Optional configuration including tools and abort signal
836
+ * @returns The worker output and session ID
837
+ * @throws {WorkerError} If the worker fails or completes without output
838
+ *
839
+ * @example Basic usage
840
+ * ```typescript
841
+ * const { output, sessionId } = await client.workers.generate(agentId, {
842
+ * TOPIC: 'AI safety',
843
+ * });
844
+ * console.log(output);
845
+ * console.log(`Debug: ${client.baseUrl}/platform/sessions/${sessionId}`);
846
+ * ```
847
+ *
848
+ * @example With timeout
849
+ * ```typescript
850
+ * const { output } = await client.workers.generate(agentId, input, {
851
+ * signal: AbortSignal.timeout(120_000),
852
+ * });
853
+ * ```
854
+ */
855
+ async generate(agentId, input, options = {}) {
856
+ let sessionId;
857
+ for await (const event of this.execute(agentId, input, options)) {
858
+ if (event.type === "start" && event.executionId) {
859
+ sessionId = event.executionId;
860
+ } else if (event.type === "error") {
861
+ throw new WorkerError(event.message, sessionId);
862
+ } else if (event.type === "worker-result") {
863
+ if (event.error) {
864
+ throw new WorkerError(event.error, sessionId ?? event.workerId);
865
+ }
866
+ return {
867
+ output: event.output,
868
+ sessionId: sessionId ?? event.workerId
869
+ };
870
+ }
871
+ }
872
+ throw new WorkerError("Worker completed without producing a result", sessionId);
873
+ }
874
+ /**
875
+ * Continue a worker execution after client-side tool handling.
876
+ *
877
+ * Use this when your worker has tools without server-side handlers.
878
+ * The execution returns a client-tool-request event with an executionId.
879
+ * Execute the tools client-side, then call this method to continue.
880
+ *
881
+ * @param agentId - The worker agent ID
882
+ * @param executionId - The execution ID from the client-tool-request event
883
+ * @param toolResults - Results from client-side tool execution
884
+ * @param options - Optional configuration including tools and abort signal
885
+ * @returns An async generator of stream events
886
+ *
887
+ * @example
888
+ * ```typescript
889
+ * // Start execution
890
+ * for await (const event of client.workers.execute(agentId, input)) {
891
+ * if (event.type === 'client-tool-request') {
892
+ * // Execute tools client-side
893
+ * const results = await executeToolsClientSide(event.toolCalls);
894
+ * // Continue execution
895
+ * for await (const ev of client.workers.continue(agentId, event.executionId, results)) {
896
+ * // Handle remaining events
897
+ * }
898
+ * }
899
+ * }
900
+ * ```
901
+ */
902
+ async *continue(agentId, executionId, toolResults, options = {}) {
903
+ yield* executeStream(
904
+ {
905
+ config: this.config,
906
+ toolHandlers: options.tools ?? {},
907
+ url: `${this.config.baseUrl}/api/agents/${agentId}/execute`,
908
+ buildBody: ({ executionId: execId, toolResults: results }) => ({
909
+ type: "continue",
910
+ executionId: execId ?? executionId,
911
+ toolResults: results ?? toolResults
912
+ }),
913
+ errorContext: "Failed to continue worker"
914
+ },
915
+ { executionId, toolResults },
916
+ options.signal
917
+ );
918
+ }
919
+ };
920
+
921
+ // src/client.ts
922
+ var OctavusClient = class {
923
+ agents;
924
+ agentSessions;
925
+ files;
926
+ workers;
927
+ baseUrl;
928
+ apiKey;
929
+ traceModelRequests;
930
+ constructor(config) {
931
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
932
+ this.apiKey = config.apiKey;
933
+ this.traceModelRequests = config.traceModelRequests ?? false;
934
+ const apiConfig = {
935
+ baseUrl: this.baseUrl,
936
+ getHeaders: () => this.getHeaders()
937
+ };
938
+ this.agents = new AgentsApi(apiConfig);
939
+ this.agentSessions = new AgentSessionsApi(apiConfig);
940
+ this.files = new FilesApi(apiConfig);
941
+ this.workers = new WorkersApi(apiConfig);
942
+ }
943
+ getHeaders() {
944
+ const headers = {
945
+ "Content-Type": "application/json"
946
+ };
947
+ if (this.apiKey) {
948
+ headers.Authorization = `Bearer ${this.apiKey}`;
949
+ }
950
+ if (this.traceModelRequests) {
951
+ headers["X-Octavus-Trace"] = "true";
952
+ }
953
+ return headers;
954
+ }
955
+ };
956
+
957
+ // src/resource.ts
958
+ var Resource = class {
959
+ };
960
+
961
+ // src/normalize-images.ts
962
+ var import_core4 = require("@octavus/core");
963
+ var IMAGE_EXTENSIONS = {
964
+ "image/png": "png",
965
+ "image/jpeg": "jpg",
966
+ "image/webp": "webp",
967
+ "image/gif": "gif",
968
+ "image/svg+xml": "svg"
969
+ };
970
+ function getExtensionFromMediaType(mediaType) {
971
+ return IMAGE_EXTENSIONS[mediaType] ?? "png";
972
+ }
973
+ function isImagePart(part) {
974
+ return typeof part === "object" && part !== null && part.type === "image" && typeof part.data === "string";
975
+ }
976
+ function hasImageParts(value) {
977
+ return Array.isArray(value) && value.some(isImagePart);
978
+ }
979
+ function base64ToArrayBuffer(base64) {
980
+ const binary = atob(base64);
981
+ const buffer = new ArrayBuffer(binary.length);
982
+ const view = new Uint8Array(buffer);
983
+ for (let i = 0; i < binary.length; i++) {
984
+ view[i] = binary.charCodeAt(i);
985
+ }
986
+ return buffer;
987
+ }
988
+ async function normalizeToolResultImages(toolResults, filesApi, sessionId) {
989
+ for (const toolResult of toolResults) {
990
+ if (toolResult.outputVariable) continue;
991
+ if (!hasImageParts(toolResult.result)) continue;
992
+ const parts = toolResult.result;
993
+ const files = toolResult.files ? [...toolResult.files] : [];
994
+ const imageIndices = [];
995
+ const imageBuffers = [];
996
+ const uploadRequests = [];
997
+ for (let i = 0; i < parts.length; i++) {
998
+ const part = parts[i];
999
+ if (!isImagePart(part)) continue;
1000
+ const buffer = base64ToArrayBuffer(part.data);
1001
+ const mimeType = part.mimeType || "image/png";
1002
+ imageIndices.push(i);
1003
+ imageBuffers.push(buffer);
1004
+ uploadRequests.push({
1005
+ filename: `image-${(0, import_core4.generateId)()}.${getExtensionFromMediaType(mimeType)}`,
1006
+ mediaType: mimeType,
1007
+ size: buffer.byteLength
1008
+ });
1009
+ }
1010
+ if (uploadRequests.length === 0) continue;
1011
+ let uploadInfos;
1012
+ try {
1013
+ const response = await filesApi.getUploadUrls(sessionId, uploadRequests);
1014
+ uploadInfos = response.files;
1015
+ } catch {
1016
+ continue;
1017
+ }
1018
+ const uploadResults = await Promise.allSettled(
1019
+ uploadInfos.map(
1020
+ (info, i) => fetch(info.uploadUrl, {
1021
+ method: "PUT",
1022
+ body: imageBuffers[i],
1023
+ headers: { "Content-Type": uploadRequests[i].mediaType }
1024
+ })
1025
+ )
1026
+ );
1027
+ const summaryParts = [];
1028
+ let imageIdx = 0;
1029
+ for (let i = 0; i < parts.length; i++) {
1030
+ if (imageIdx < imageIndices.length && imageIndices[imageIdx] === i) {
1031
+ const uploadResult = uploadResults[imageIdx];
1032
+ const info = uploadInfos[imageIdx];
1033
+ const request = uploadRequests[imageIdx];
1034
+ const buf = imageBuffers[imageIdx];
1035
+ if (uploadResult.status === "fulfilled" && uploadResult.value.ok) {
1036
+ files.push({
1037
+ id: info.id,
1038
+ mediaType: request.mediaType,
1039
+ url: info.downloadUrl,
1040
+ filename: request.filename,
1041
+ size: buf.byteLength
1042
+ });
1043
+ summaryParts.push({
1044
+ type: "image",
1045
+ mediaType: request.mediaType,
1046
+ size: buf.byteLength,
1047
+ url: info.downloadUrl
1048
+ });
1049
+ } else {
1050
+ summaryParts.push(parts[i]);
1051
+ }
1052
+ imageIdx += 1;
1053
+ } else {
1054
+ summaryParts.push(parts[i]);
1055
+ }
1056
+ }
1057
+ toolResult.files = files;
1058
+ toolResult.result = summaryParts;
1059
+ }
1060
+ }
1061
+
1062
+ // src/index.ts
1063
+ var import_core5 = require("@octavus/core");
1064
+ // Annotate the CommonJS export names for ESM import in node:
1065
+ 0 && (module.exports = {
1066
+ AgentSession,
1067
+ AgentSessionsApi,
1068
+ AgentsApi,
1069
+ ApiError,
1070
+ AppError,
1071
+ ConflictError,
1072
+ FilesApi,
1073
+ ForbiddenError,
1074
+ MAIN_THREAD,
1075
+ NotFoundError,
1076
+ OCTAVUS_SKILL_TOOLS,
1077
+ OctavusClient,
1078
+ OctavusError,
1079
+ Resource,
1080
+ ValidationError,
1081
+ WorkerError,
1082
+ WorkersApi,
1083
+ createApiErrorEvent,
1084
+ createErrorEvent,
1085
+ createInternalErrorEvent,
1086
+ errorToStreamEvent,
1087
+ generateId,
1088
+ getSkillSlugFromToolCall,
1089
+ isAbortError,
1090
+ isAuthenticationError,
1091
+ isFileReference,
1092
+ isFileReferenceArray,
1093
+ isMainThread,
1094
+ isOctavusSkillTool,
1095
+ isOtherThread,
1096
+ isProviderError,
1097
+ isRateLimitError,
1098
+ isRetryableError,
1099
+ isToolError,
1100
+ isValidationError,
1101
+ normalizeToolResultImages,
1102
+ resolveThread,
1103
+ safeParseStreamEvent,
1104
+ safeParseUIMessage,
1105
+ safeParseUIMessages,
1106
+ threadForPart,
1107
+ toSSEStream
1108
+ });
1109
+ //# sourceMappingURL=index.cjs.map