@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.
@@ -0,0 +1,552 @@
1
+ import { ZodType, z } from 'zod';
2
+ import { ToolHandlers, ToolSchema, ToolResult, StreamEvent, ChatMessage, UIMessage, ToolProvider } from '@octavus/core';
3
+ export * from '@octavus/core';
4
+ export { AppError, ConflictError, ForbiddenError, MAIN_THREAD, NotFoundError, OCTAVUS_SKILL_TOOLS, OctavusError, ValidationError, createApiErrorEvent, createErrorEvent, createInternalErrorEvent, errorToStreamEvent, generateId, getSkillSlugFromToolCall, isAbortError, isAuthenticationError, isFileReference, isFileReferenceArray, isMainThread, isOctavusSkillTool, isOtherThread, isProviderError, isRateLimitError, isRetryableError, isToolError, isValidationError, resolveThread, safeParseStreamEvent, safeParseUIMessage, safeParseUIMessages, threadForPart } from '@octavus/core';
5
+
6
+ /**
7
+ * Error thrown when API request fails
8
+ */
9
+ declare class ApiError extends Error {
10
+ status: number;
11
+ code?: string | undefined;
12
+ constructor(message: string, status: number, code?: string | undefined);
13
+ }
14
+
15
+ interface ApiClientConfig {
16
+ baseUrl: string;
17
+ getHeaders: () => Record<string, string>;
18
+ }
19
+ /** Base class for API clients with shared HTTP utilities */
20
+ declare abstract class BaseApiClient {
21
+ protected readonly config: ApiClientConfig;
22
+ constructor(config: ApiClientConfig);
23
+ protected httpGet<T>(path: string, schema: ZodType<T>): Promise<T>;
24
+ protected httpPost<T>(path: string, body: unknown, schema: ZodType<T>): Promise<T>;
25
+ protected httpPatch<T>(path: string, body: unknown, schema: ZodType<T>): Promise<T>;
26
+ protected httpDelete<T>(path: string, schema: ZodType<T>): Promise<T>;
27
+ }
28
+
29
+ /** Agent format - interactive (chat) or worker (background task) */
30
+ type AgentFormat = 'interactive' | 'worker';
31
+ /** Agent settings */
32
+ interface AgentSettings {
33
+ slug: string;
34
+ name: string;
35
+ description?: string;
36
+ format: AgentFormat;
37
+ }
38
+ /** Agent prompt */
39
+ interface AgentPrompt {
40
+ name: string;
41
+ content: string;
42
+ }
43
+ /**
44
+ * Agent summary returned from list endpoint
45
+ */
46
+ interface Agent {
47
+ /** Agent slug (human-readable identifier within project) */
48
+ slug: string;
49
+ /** Agent ID - use this for API calls */
50
+ id: string;
51
+ name: string;
52
+ description: string | null;
53
+ format: AgentFormat;
54
+ createdAt: string;
55
+ updatedAt: string;
56
+ projectId: string;
57
+ }
58
+ /**
59
+ * Full agent definition returned from get endpoint
60
+ */
61
+ interface AgentDefinition {
62
+ settings: AgentSettings;
63
+ protocol: string;
64
+ prompts: AgentPrompt[];
65
+ /** Agent ID - use this for API calls like createSession */
66
+ id: string;
67
+ }
68
+
69
+ /**
70
+ * API for listing and retrieving agent definitions.
71
+ *
72
+ * Note: Agent management (create, update, sync) should be done via the @octavus/cli package.
73
+ * This API uses agent IDs only - use CLI for slug-based operations.
74
+ */
75
+ declare class AgentsApi extends BaseApiClient {
76
+ /** List all agents in the project */
77
+ list(): Promise<Agent[]>;
78
+ /** Get a single agent by ID */
79
+ get(agentId: string): Promise<AgentDefinition>;
80
+ }
81
+
82
+ /**
83
+ * Base class for agent-managed resources.
84
+ * Extend this class to define how each resource should be persisted when the agent updates it.
85
+ */
86
+ declare abstract class Resource {
87
+ /** The resource name as defined in the agent protocol */
88
+ abstract readonly name: string;
89
+ /** Called when the agent updates this resource */
90
+ abstract onUpdate(value: unknown): Promise<void> | void;
91
+ }
92
+
93
+ /** Start a new trigger execution */
94
+ interface TriggerRequest {
95
+ type: 'trigger';
96
+ triggerName: string;
97
+ input?: Record<string, unknown>;
98
+ /** ID of the last message to keep. Messages after this are removed before execution. `null` = truncate all. */
99
+ rollbackAfterMessageId?: string | null;
100
+ }
101
+ /** Continue execution after client-side tool handling */
102
+ interface ContinueRequest {
103
+ type: 'continue';
104
+ executionId: string;
105
+ toolResults: ToolResult[];
106
+ }
107
+ /** All request types supported by the session */
108
+ type SessionRequest = TriggerRequest | ContinueRequest;
109
+ /** Stop message to abort in-flight requests */
110
+ interface StopMessage {
111
+ type: 'stop';
112
+ }
113
+ /** All socket protocol messages (trigger, continue, stop) */
114
+ type SocketMessage = TriggerRequest | ContinueRequest | StopMessage;
115
+ /** Handlers for socket message streaming */
116
+ interface SocketMessageHandlers {
117
+ /** Called for each stream event */
118
+ onEvent: (event: StreamEvent) => void;
119
+ /** Called after streaming completes (not called if aborted) */
120
+ onFinish?: () => void | Promise<void>;
121
+ }
122
+ /**
123
+ * Converts an async iterable of stream events to an SSE-formatted ReadableStream.
124
+ * Use this when you need to return an SSE response (e.g., HTTP endpoints).
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const events = session.trigger('user-message', input);
129
+ * return new Response(toSSEStream(events), {
130
+ * headers: { 'Content-Type': 'text/event-stream' },
131
+ * });
132
+ * ```
133
+ */
134
+ declare function toSSEStream(events: AsyncIterable<StreamEvent>): ReadableStream<Uint8Array>;
135
+ interface SessionConfig {
136
+ sessionId: string;
137
+ config: ApiClientConfig;
138
+ tools?: ToolHandlers;
139
+ resources?: Resource[];
140
+ /** Tool schemas to send to the platform on the first trigger (device MCP tools, etc.) */
141
+ additionalToolSchemas?: ToolSchema[];
142
+ /** Called after server-side tools execute, before yielding events or continuing. Use to normalize tool results (e.g., upload base64 images). */
143
+ onToolResults?: (results: ToolResult[]) => Promise<void>;
144
+ /** When true, unhandled tool calls return errors instead of being emitted as client-tool-request events. */
145
+ rejectClientToolCalls?: boolean;
146
+ }
147
+ /**
148
+ * Options for trigger execution.
149
+ */
150
+ interface TriggerOptions {
151
+ /** Abort signal to cancel the trigger execution */
152
+ signal?: AbortSignal;
153
+ }
154
+ /** Handles streaming and tool continuation for agent sessions */
155
+ declare class AgentSession {
156
+ private sessionId;
157
+ private config;
158
+ private toolHandlers;
159
+ private resourceMap;
160
+ private additionalToolSchemas;
161
+ private additionalToolSchemasSent;
162
+ private socketAbortController;
163
+ private onToolResults?;
164
+ private rejectClientToolCalls;
165
+ constructor(sessionConfig: SessionConfig);
166
+ /**
167
+ * Execute a session request and stream the response.
168
+ *
169
+ * This is the unified method that handles both triggers and continuations.
170
+ * Use this when you want to pass through requests from the client directly.
171
+ *
172
+ * @param request - The request (check `request.type` for the kind)
173
+ * @param options - Optional configuration including abort signal
174
+ *
175
+ * @example HTTP route (simple passthrough)
176
+ * ```typescript
177
+ * const events = session.execute(body, { signal: request.signal });
178
+ * return new Response(toSSEStream(events));
179
+ * ```
180
+ *
181
+ * @example WebSocket handler
182
+ * ```typescript
183
+ * socket.on('message', (data) => {
184
+ * const events = session.execute(data);
185
+ * for await (const event of events) {
186
+ * socket.send(JSON.stringify(event));
187
+ * }
188
+ * });
189
+ * ```
190
+ */
191
+ execute(request: SessionRequest, options?: TriggerOptions): AsyncGenerator<StreamEvent>;
192
+ getSessionId(): string;
193
+ /**
194
+ * Handle a WebSocket protocol message (trigger, continue, or stop).
195
+ * Manages abort controller lifecycle internally.
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * conn.on('data', (raw) => {
200
+ * session.handleSocketMessage(JSON.parse(raw), {
201
+ * onEvent: (event) => conn.write(JSON.stringify(event)),
202
+ * onFinish: async () => {
203
+ * // Fetch messages and persist to your database for restoration
204
+ * },
205
+ * });
206
+ * });
207
+ * ```
208
+ */
209
+ handleSocketMessage(message: SocketMessage, handlers: SocketMessageHandlers): Promise<void>;
210
+ private executeStream;
211
+ private handleResourceUpdate;
212
+ }
213
+
214
+ /** Session status indicating whether it's active or expired */
215
+ type SessionStatus = 'active' | 'expired';
216
+ interface SessionState {
217
+ id: string;
218
+ agentId: string;
219
+ input: Record<string, unknown>;
220
+ variables: Record<string, unknown>;
221
+ resources: Record<string, unknown>;
222
+ messages: ChatMessage[];
223
+ status?: 'active';
224
+ createdAt: string;
225
+ updatedAt: string;
226
+ }
227
+ interface UISessionState {
228
+ sessionId: string;
229
+ agentId: string;
230
+ messages: UIMessage[];
231
+ status?: 'active';
232
+ }
233
+ interface ExpiredSessionState {
234
+ sessionId: string;
235
+ agentId: string;
236
+ status: 'expired';
237
+ createdAt: string;
238
+ }
239
+ interface RestoreSessionResult {
240
+ sessionId: string;
241
+ /** True if session was restored from messages, false if already active */
242
+ restored: boolean;
243
+ }
244
+ interface ClearSessionResult {
245
+ sessionId: string;
246
+ cleared: boolean;
247
+ }
248
+ interface SessionAttachOptions {
249
+ tools?: ToolHandlers;
250
+ resources?: Resource[];
251
+ /** Computer capability provider (browser, filesystem, shell via MCP). */
252
+ computer?: ToolProvider;
253
+ /** Called after server-side tools execute, before yielding events or continuing. Use to normalize tool results (e.g., upload base64 images). */
254
+ onToolResults?: (results: ToolResult[]) => Promise<void>;
255
+ /** When true, unhandled tool calls return errors instead of being emitted as client-tool-request events. */
256
+ rejectClientToolCalls?: boolean;
257
+ }
258
+ /** API for managing agent sessions */
259
+ declare class AgentSessionsApi extends BaseApiClient {
260
+ /** Create a new session for an agent */
261
+ create(agentId: string, input?: Record<string, unknown>): Promise<string>;
262
+ /**
263
+ * Get full session state (for internal/debug use)
264
+ * Note: Contains all messages including hidden content
265
+ *
266
+ * Returns SessionState for active sessions, ExpiredSessionState for expired sessions.
267
+ * Check `status` field to determine which type was returned.
268
+ */
269
+ get(sessionId: string): Promise<SessionState | ExpiredSessionState>;
270
+ /**
271
+ * Get UI-ready session messages (for client display)
272
+ * Returns only visible messages with hidden content filtered out.
273
+ *
274
+ * For expired sessions, returns status: 'expired' without messages.
275
+ * Use restore() to restore from stored messages before continuing.
276
+ */
277
+ getMessages(sessionId: string): Promise<UISessionState | ExpiredSessionState>;
278
+ /**
279
+ * Restore an expired session from stored messages.
280
+ *
281
+ * Use this to restore a session after its state has expired.
282
+ * The consumer should have stored the UIMessage[] array from previous interactions.
283
+ *
284
+ * @param sessionId - The session ID to restore
285
+ * @param messages - Previously stored UIMessage[] array
286
+ * @param input - Optional session input for system prompt interpolation (same as create)
287
+ * @returns { sessionId, restored: true } if restored, { sessionId, restored: false } if already active
288
+ */
289
+ restore(sessionId: string, messages: UIMessage[], input?: Record<string, unknown>): Promise<RestoreSessionResult>;
290
+ /**
291
+ * Clear session state from the server.
292
+ * The session will transition to 'expired' status and can be restored with restore().
293
+ * Idempotent: succeeds even if state was already cleared/expired.
294
+ */
295
+ clear(sessionId: string): Promise<ClearSessionResult>;
296
+ /** Attach to an existing session for triggering events */
297
+ attach(sessionId: string, options?: SessionAttachOptions): AgentSession;
298
+ }
299
+
300
+ /**
301
+ * Schema for a single file upload request
302
+ */
303
+ declare const fileUploadRequestSchema: z.ZodObject<{
304
+ filename: z.ZodString;
305
+ mediaType: z.ZodString;
306
+ size: z.ZodNumber;
307
+ }, z.core.$strip>;
308
+ /**
309
+ * Schema for a single file upload response
310
+ */
311
+ declare const fileUploadInfoSchema: z.ZodObject<{
312
+ id: z.ZodString;
313
+ uploadUrl: z.ZodURL;
314
+ downloadUrl: z.ZodURL;
315
+ }, z.core.$strip>;
316
+ /**
317
+ * Schema for the upload URLs response
318
+ */
319
+ declare const uploadUrlsResponseSchema: z.ZodObject<{
320
+ files: z.ZodArray<z.ZodObject<{
321
+ id: z.ZodString;
322
+ uploadUrl: z.ZodURL;
323
+ downloadUrl: z.ZodURL;
324
+ }, z.core.$strip>>;
325
+ }, z.core.$strip>;
326
+ type FileUploadRequest = z.infer<typeof fileUploadRequestSchema>;
327
+ type FileUploadInfo = z.infer<typeof fileUploadInfoSchema>;
328
+ type UploadUrlsResponse = z.infer<typeof uploadUrlsResponseSchema>;
329
+ /**
330
+ * API for file operations.
331
+ *
332
+ * Provides methods to generate presigned URLs for file uploads.
333
+ * Files are uploaded directly to S3, not through the platform.
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * // Get upload URLs
338
+ * const { files } = await client.files.getUploadUrls(sessionId, [
339
+ * { filename: 'image.png', mediaType: 'image/png', size: 12345 }
340
+ * ]);
341
+ *
342
+ * // Upload directly to S3
343
+ * await fetch(files[0].uploadUrl, {
344
+ * method: 'PUT',
345
+ * body: imageFile,
346
+ * headers: { 'Content-Type': 'image/png' }
347
+ * });
348
+ *
349
+ * // Use downloadUrl as FileReference in trigger input
350
+ * ```
351
+ */
352
+ declare class FilesApi extends BaseApiClient {
353
+ /**
354
+ * Get presigned URLs for uploading files to a session.
355
+ *
356
+ * Returns upload URLs (PUT) and download URLs (GET) for each file.
357
+ * Upload URLs expire in 15 minutes, download URLs match session TTL (24 hours).
358
+ *
359
+ * @param sessionId - The session ID to associate files with
360
+ * @param files - Array of file metadata (filename, mediaType, size)
361
+ * @returns Upload info with presigned URLs for each file
362
+ *
363
+ * @throws ApiError if session doesn't exist or validation fails
364
+ *
365
+ * @example
366
+ * ```typescript
367
+ * const { files } = await client.files.getUploadUrls(sessionId, [
368
+ * { filename: 'photo.jpg', mediaType: 'image/jpeg', size: 102400 },
369
+ * { filename: 'doc.pdf', mediaType: 'application/pdf', size: 204800 },
370
+ * ]);
371
+ *
372
+ * // files[0].id - Use in FileReference
373
+ * // files[0].uploadUrl - PUT to this URL
374
+ * // files[0].downloadUrl - Use as FileReference.url
375
+ * ```
376
+ */
377
+ getUploadUrls(sessionId: string, files: FileUploadRequest[]): Promise<UploadUrlsResponse>;
378
+ }
379
+
380
+ /** Start a new worker execution */
381
+ interface WorkerStartRequest {
382
+ type: 'start';
383
+ input: Record<string, unknown>;
384
+ }
385
+ /** Continue execution after client-side tool handling */
386
+ interface WorkerContinueRequest {
387
+ type: 'continue';
388
+ executionId: string;
389
+ toolResults: ToolResult[];
390
+ }
391
+ /** All request types supported by workers */
392
+ type WorkerRequest = WorkerStartRequest | WorkerContinueRequest;
393
+ /** Options for worker execution */
394
+ interface WorkerExecuteOptions {
395
+ /** Tool handlers for server-side tool execution */
396
+ tools?: ToolHandlers;
397
+ /** Abort signal to cancel the execution */
398
+ signal?: AbortSignal;
399
+ }
400
+ /** Result from a non-streaming worker execution via `generate()` */
401
+ interface WorkerGenerateResult {
402
+ /** The worker's output value */
403
+ output: unknown;
404
+ /** Session ID for the worker execution (usable for debugging/session URLs) */
405
+ sessionId: string;
406
+ }
407
+ /** API for executing worker agents */
408
+ declare class WorkersApi extends BaseApiClient {
409
+ /**
410
+ * Execute a worker agent and stream the response.
411
+ *
412
+ * Worker agents execute steps sequentially and return an output value.
413
+ * Unlike interactive sessions, workers don't maintain persistent state.
414
+ *
415
+ * The execution handles the tool continuation pattern automatically:
416
+ * - Server tools (with handlers provided) are executed automatically
417
+ * - Client tools (without handlers) emit a client-tool-request event
418
+ *
419
+ * @param agentId - The worker agent ID
420
+ * @param input - Input values for the worker
421
+ * @param options - Optional configuration including tools and abort signal
422
+ * @returns An async generator of stream events
423
+ *
424
+ * @example Basic execution
425
+ * ```typescript
426
+ * const events = client.workers.execute(agentId, { TOPIC: 'AI safety' });
427
+ * for await (const event of events) {
428
+ * if (event.type === 'worker-start') {
429
+ * console.log(`Worker ${event.workerSlug} started (${event.workerId})`);
430
+ * }
431
+ * if (event.type === 'worker-result') {
432
+ * if (event.error) {
433
+ * console.error('Worker failed:', event.error);
434
+ * } else {
435
+ * console.log('Output:', event.output);
436
+ * }
437
+ * }
438
+ * }
439
+ * ```
440
+ *
441
+ * @example With tool handlers
442
+ * ```typescript
443
+ * const events = client.workers.execute(agentId, { TOPIC: 'AI safety' }, {
444
+ * tools: {
445
+ * 'web-search': async (args) => {
446
+ * return await searchWeb(args.query);
447
+ * },
448
+ * },
449
+ * });
450
+ * ```
451
+ */
452
+ execute(agentId: string, input: Record<string, unknown>, options?: WorkerExecuteOptions): AsyncGenerator<StreamEvent>;
453
+ /**
454
+ * Execute a worker agent and return the final output.
455
+ *
456
+ * Non-streaming equivalent of `execute()` — runs the worker to completion
457
+ * and returns the output value directly. Use this when you don't need to
458
+ * observe intermediate streaming events.
459
+ *
460
+ * @param agentId - The worker agent ID
461
+ * @param input - Input values for the worker
462
+ * @param options - Optional configuration including tools and abort signal
463
+ * @returns The worker output and session ID
464
+ * @throws {WorkerError} If the worker fails or completes without output
465
+ *
466
+ * @example Basic usage
467
+ * ```typescript
468
+ * const { output, sessionId } = await client.workers.generate(agentId, {
469
+ * TOPIC: 'AI safety',
470
+ * });
471
+ * console.log(output);
472
+ * console.log(`Debug: ${client.baseUrl}/platform/sessions/${sessionId}`);
473
+ * ```
474
+ *
475
+ * @example With timeout
476
+ * ```typescript
477
+ * const { output } = await client.workers.generate(agentId, input, {
478
+ * signal: AbortSignal.timeout(120_000),
479
+ * });
480
+ * ```
481
+ */
482
+ generate(agentId: string, input: Record<string, unknown>, options?: WorkerExecuteOptions): Promise<WorkerGenerateResult>;
483
+ /**
484
+ * Continue a worker execution after client-side tool handling.
485
+ *
486
+ * Use this when your worker has tools without server-side handlers.
487
+ * The execution returns a client-tool-request event with an executionId.
488
+ * Execute the tools client-side, then call this method to continue.
489
+ *
490
+ * @param agentId - The worker agent ID
491
+ * @param executionId - The execution ID from the client-tool-request event
492
+ * @param toolResults - Results from client-side tool execution
493
+ * @param options - Optional configuration including tools and abort signal
494
+ * @returns An async generator of stream events
495
+ *
496
+ * @example
497
+ * ```typescript
498
+ * // Start execution
499
+ * for await (const event of client.workers.execute(agentId, input)) {
500
+ * if (event.type === 'client-tool-request') {
501
+ * // Execute tools client-side
502
+ * const results = await executeToolsClientSide(event.toolCalls);
503
+ * // Continue execution
504
+ * for await (const ev of client.workers.continue(agentId, event.executionId, results)) {
505
+ * // Handle remaining events
506
+ * }
507
+ * }
508
+ * }
509
+ * ```
510
+ */
511
+ continue(agentId: string, executionId: string, toolResults: ToolResult[], options?: WorkerExecuteOptions): AsyncGenerator<StreamEvent>;
512
+ }
513
+
514
+ interface OctavusClientConfig {
515
+ baseUrl: string;
516
+ apiKey?: string;
517
+ /** Enable model request tracing to capture full payloads sent to providers (default: false) */
518
+ traceModelRequests?: boolean;
519
+ }
520
+ /** Client for interacting with the Octavus platform API */
521
+ declare class OctavusClient {
522
+ readonly agents: AgentsApi;
523
+ readonly agentSessions: AgentSessionsApi;
524
+ readonly files: FilesApi;
525
+ readonly workers: WorkersApi;
526
+ readonly baseUrl: string;
527
+ private readonly apiKey?;
528
+ private readonly traceModelRequests;
529
+ constructor(config: OctavusClientConfig);
530
+ getHeaders(): Record<string, string>;
531
+ }
532
+
533
+ /** Error thrown when a worker execution fails */
534
+ declare class WorkerError extends Error {
535
+ /** Session ID if the worker started before failing (for debugging URLs) */
536
+ readonly sessionId?: string | undefined;
537
+ constructor(message: string,
538
+ /** Session ID if the worker started before failing (for debugging URLs) */
539
+ sessionId?: string | undefined);
540
+ }
541
+
542
+ /**
543
+ * Extract base64 image blobs from tool results, upload them to S3 via
544
+ * presigned URLs, and replace inline data with compact metadata +
545
+ * FileReference entries.
546
+ *
547
+ * Runs at the server-sdk streaming layer so images are uploaded before
548
+ * tool results travel over the network (WebSocket, HTTP continuation).
549
+ */
550
+ declare function normalizeToolResultImages(toolResults: ToolResult[], filesApi: FilesApi, sessionId: string): Promise<void>;
551
+
552
+ export { type Agent, type AgentDefinition, type AgentFormat, type AgentPrompt, AgentSession, AgentSessionsApi, type AgentSettings, AgentsApi, ApiError, type ClearSessionResult, type ContinueRequest, type ExpiredSessionState, type FileUploadInfo, type FileUploadRequest, FilesApi, OctavusClient, type OctavusClientConfig, Resource, type RestoreSessionResult, type SessionAttachOptions, type SessionConfig, type SessionRequest, type SessionState, type SessionStatus, type SocketMessage, type SocketMessageHandlers, type StopMessage, type TriggerOptions, type TriggerRequest, type UISessionState, type UploadUrlsResponse, type WorkerContinueRequest, WorkerError, type WorkerExecuteOptions, type WorkerGenerateResult, type WorkerRequest, type WorkerStartRequest, WorkersApi, normalizeToolResultImages, toSSEStream };
package/dist/index.d.ts CHANGED
@@ -141,6 +141,8 @@ interface SessionConfig {
141
141
  additionalToolSchemas?: ToolSchema[];
142
142
  /** Called after server-side tools execute, before yielding events or continuing. Use to normalize tool results (e.g., upload base64 images). */
143
143
  onToolResults?: (results: ToolResult[]) => Promise<void>;
144
+ /** When true, unhandled tool calls return errors instead of being emitted as client-tool-request events. */
145
+ rejectClientToolCalls?: boolean;
144
146
  }
145
147
  /**
146
148
  * Options for trigger execution.
@@ -159,6 +161,7 @@ declare class AgentSession {
159
161
  private additionalToolSchemasSent;
160
162
  private socketAbortController;
161
163
  private onToolResults?;
164
+ private rejectClientToolCalls;
162
165
  constructor(sessionConfig: SessionConfig);
163
166
  /**
164
167
  * Execute a session request and stream the response.
@@ -249,6 +252,8 @@ interface SessionAttachOptions {
249
252
  computer?: ToolProvider;
250
253
  /** Called after server-side tools execute, before yielding events or continuing. Use to normalize tool results (e.g., upload base64 images). */
251
254
  onToolResults?: (results: ToolResult[]) => Promise<void>;
255
+ /** When true, unhandled tool calls return errors instead of being emitted as client-tool-request events. */
256
+ rejectClientToolCalls?: boolean;
252
257
  }
253
258
  /** API for managing agent sessions */
254
259
  declare class AgentSessionsApi extends BaseApiClient {
package/dist/index.js CHANGED
@@ -294,18 +294,34 @@ async function* executeStream(config, payload, signal) {
294
294
  }
295
295
  }
296
296
  if (clientTools.length > 0) {
297
- if (!executionId) {
298
- yield createInternalErrorEvent("Missing executionId for client-tool-request");
299
- return;
297
+ if (config.rejectClientToolCalls) {
298
+ const rejectedResults = clientTools.map((tc) => ({
299
+ toolCallId: tc.toolCallId,
300
+ toolName: tc.toolName,
301
+ error: `Tool "${tc.toolName}" is not available. No handler is registered for this tool.`,
302
+ outputVariable: tc.outputVariable,
303
+ blockIndex: tc.blockIndex,
304
+ thread: tc.thread,
305
+ workerId: tc.workerId
306
+ }));
307
+ for (const tr of rejectedResults) {
308
+ yield { type: "tool-output-error", toolCallId: tr.toolCallId, error: tr.error };
309
+ }
310
+ toolResults = [...serverResults, ...rejectedResults];
311
+ } else {
312
+ if (!executionId) {
313
+ yield createInternalErrorEvent("Missing executionId for client-tool-request");
314
+ return;
315
+ }
316
+ yield {
317
+ type: "client-tool-request",
318
+ executionId,
319
+ toolCalls: clientTools,
320
+ serverToolResults: serverResults.length > 0 ? serverResults : void 0
321
+ };
322
+ yield { type: "finish", finishReason: "client-tool-calls", executionId };
323
+ continueLoop = false;
300
324
  }
301
- yield {
302
- type: "client-tool-request",
303
- executionId,
304
- toolCalls: clientTools,
305
- serverToolResults: serverResults.length > 0 ? serverResults : void 0
306
- };
307
- yield { type: "finish", finishReason: "client-tool-calls", executionId };
308
- continueLoop = false;
309
325
  } else {
310
326
  toolResults = serverResults;
311
327
  }
@@ -360,12 +376,14 @@ var AgentSession = class {
360
376
  additionalToolSchemasSent = false;
361
377
  socketAbortController = null;
362
378
  onToolResults;
379
+ rejectClientToolCalls;
363
380
  constructor(sessionConfig) {
364
381
  this.sessionId = sessionConfig.sessionId;
365
382
  this.config = sessionConfig.config;
366
383
  this.toolHandlers = sessionConfig.tools ?? {};
367
384
  this.additionalToolSchemas = sessionConfig.additionalToolSchemas;
368
385
  this.onToolResults = sessionConfig.onToolResults;
386
+ this.rejectClientToolCalls = sessionConfig.rejectClientToolCalls ?? false;
369
387
  this.resourceMap = /* @__PURE__ */ new Map();
370
388
  for (const resource of sessionConfig.resources ?? []) {
371
389
  this.resourceMap.set(resource.name, resource);
@@ -480,6 +498,7 @@ var AgentSession = class {
480
498
  },
481
499
  onResourceUpdate: (name, value) => this.handleResourceUpdate(name, value),
482
500
  onToolResults: this.onToolResults,
501
+ rejectClientToolCalls: this.rejectClientToolCalls,
483
502
  errorContext: "Failed to trigger"
484
503
  },
485
504
  { executionId: payload.executionId, toolResults: payload.toolResults },
@@ -622,7 +641,8 @@ var AgentSessionsApi = class extends BaseApiClient {
622
641
  tools: mergedTools,
623
642
  resources: options.resources,
624
643
  additionalToolSchemas: options.computer?.toolSchemas(),
625
- onToolResults: options.onToolResults
644
+ onToolResults: options.onToolResults,
645
+ rejectClientToolCalls: options.rejectClientToolCalls
626
646
  });
627
647
  }
628
648
  };