@agentuity/core 2.0.11 → 2.0.13

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.
Files changed (101) hide show
  1. package/dist/services/api.d.ts +1 -1
  2. package/dist/services/api.d.ts.map +1 -1
  3. package/dist/services/api.js +4 -6
  4. package/dist/services/api.js.map +1 -1
  5. package/dist/services/coder/agents.d.ts +6 -4
  6. package/dist/services/coder/agents.d.ts.map +1 -1
  7. package/dist/services/coder/api-reference.d.ts.map +1 -1
  8. package/dist/services/coder/api-reference.js +78 -10
  9. package/dist/services/coder/api-reference.js.map +1 -1
  10. package/dist/services/coder/client.d.ts +17 -1
  11. package/dist/services/coder/client.d.ts.map +1 -1
  12. package/dist/services/coder/client.js +30 -2
  13. package/dist/services/coder/client.js.map +1 -1
  14. package/dist/services/coder/index.d.ts +2 -2
  15. package/dist/services/coder/index.d.ts.map +1 -1
  16. package/dist/services/coder/index.js +1 -1
  17. package/dist/services/coder/index.js.map +1 -1
  18. package/dist/services/coder/protocol.d.ts +385 -15
  19. package/dist/services/coder/protocol.d.ts.map +1 -1
  20. package/dist/services/coder/protocol.js +148 -2
  21. package/dist/services/coder/protocol.js.map +1 -1
  22. package/dist/services/coder/sessions.d.ts +24 -2
  23. package/dist/services/coder/sessions.d.ts.map +1 -1
  24. package/dist/services/coder/sessions.js +10 -1
  25. package/dist/services/coder/sessions.js.map +1 -1
  26. package/dist/services/coder/sse.d.ts +2 -2
  27. package/dist/services/coder/sse.d.ts.map +1 -1
  28. package/dist/services/coder/sse.js +290 -178
  29. package/dist/services/coder/sse.js.map +1 -1
  30. package/dist/services/coder/types.d.ts +680 -42
  31. package/dist/services/coder/types.d.ts.map +1 -1
  32. package/dist/services/coder/types.js +284 -40
  33. package/dist/services/coder/types.js.map +1 -1
  34. package/dist/services/coder/websocket.d.ts +13 -1
  35. package/dist/services/coder/websocket.d.ts.map +1 -1
  36. package/dist/services/coder/websocket.js +91 -19
  37. package/dist/services/coder/websocket.js.map +1 -1
  38. package/dist/services/coder/workspaces.d.ts +11 -1
  39. package/dist/services/coder/workspaces.d.ts.map +1 -1
  40. package/dist/services/coder/workspaces.js +34 -1
  41. package/dist/services/coder/workspaces.js.map +1 -1
  42. package/dist/services/sandbox/api-reference.js +8 -8
  43. package/dist/services/sandbox/api-reference.js.map +1 -1
  44. package/dist/services/sandbox/client.d.ts +3 -2
  45. package/dist/services/sandbox/client.d.ts.map +1 -1
  46. package/dist/services/sandbox/client.js.map +1 -1
  47. package/dist/services/sandbox/create.d.ts +5 -0
  48. package/dist/services/sandbox/create.d.ts.map +1 -1
  49. package/dist/services/sandbox/create.js +8 -0
  50. package/dist/services/sandbox/create.js.map +1 -1
  51. package/dist/services/sandbox/get.d.ts +8 -4
  52. package/dist/services/sandbox/get.d.ts.map +1 -1
  53. package/dist/services/sandbox/get.js +28 -3
  54. package/dist/services/sandbox/get.js.map +1 -1
  55. package/dist/services/sandbox/getStatus.d.ts +2 -0
  56. package/dist/services/sandbox/getStatus.d.ts.map +1 -1
  57. package/dist/services/sandbox/getStatus.js +17 -1
  58. package/dist/services/sandbox/getStatus.js.map +1 -1
  59. package/dist/services/sandbox/index.d.ts +1 -1
  60. package/dist/services/sandbox/index.d.ts.map +1 -1
  61. package/dist/services/sandbox/list.d.ts +3 -0
  62. package/dist/services/sandbox/list.d.ts.map +1 -1
  63. package/dist/services/sandbox/list.js +5 -0
  64. package/dist/services/sandbox/list.js.map +1 -1
  65. package/dist/services/sandbox/pause.d.ts +17 -1
  66. package/dist/services/sandbox/pause.d.ts.map +1 -1
  67. package/dist/services/sandbox/pause.js +21 -3
  68. package/dist/services/sandbox/pause.js.map +1 -1
  69. package/dist/services/sandbox/run.d.ts +3 -2
  70. package/dist/services/sandbox/run.d.ts.map +1 -1
  71. package/dist/services/sandbox/run.js +145 -85
  72. package/dist/services/sandbox/run.js.map +1 -1
  73. package/dist/services/sandbox/types.d.ts +11 -4
  74. package/dist/services/sandbox/types.d.ts.map +1 -1
  75. package/dist/services/sandbox/types.js +12 -0
  76. package/dist/services/sandbox/types.js.map +1 -1
  77. package/dist/services/stream/namespaces.d.ts +2 -2
  78. package/dist/services/stream/namespaces.js +2 -2
  79. package/dist/services/stream/namespaces.js.map +1 -1
  80. package/package.json +2 -2
  81. package/src/services/api.ts +6 -7
  82. package/src/services/coder/api-reference.ts +79 -9
  83. package/src/services/coder/client.ts +46 -0
  84. package/src/services/coder/index.ts +3 -0
  85. package/src/services/coder/protocol.ts +166 -2
  86. package/src/services/coder/sessions.ts +26 -0
  87. package/src/services/coder/sse.ts +343 -184
  88. package/src/services/coder/types.ts +350 -44
  89. package/src/services/coder/websocket.ts +120 -21
  90. package/src/services/coder/workspaces.ts +74 -0
  91. package/src/services/sandbox/api-reference.ts +8 -8
  92. package/src/services/sandbox/client.ts +4 -4
  93. package/src/services/sandbox/create.ts +10 -0
  94. package/src/services/sandbox/get.ts +32 -3
  95. package/src/services/sandbox/getStatus.ts +20 -1
  96. package/src/services/sandbox/index.ts +1 -1
  97. package/src/services/sandbox/list.ts +5 -0
  98. package/src/services/sandbox/pause.ts +38 -4
  99. package/src/services/sandbox/run.ts +202 -108
  100. package/src/services/sandbox/types.ts +17 -2
  101. package/src/services/stream/namespaces.ts +2 -2
@@ -70,7 +70,7 @@ import type {
70
70
  ConnectionParams,
71
71
  ServerMessage,
72
72
  } from './protocol.ts';
73
- import { CoderHubInitMessageSchema } from './protocol.ts';
73
+ import { CoderHubInitMessageSchema, parseServerMessage } from './protocol.ts';
74
74
  import { normalizeCoderUrl } from './util.ts';
75
75
 
76
76
  /**
@@ -118,6 +118,11 @@ export const CoderHubWebSocketOptionsSchema = z.object({
118
118
  task: z.string().optional().describe('Initial task for driver mode'),
119
119
  /** Human-readable session label */
120
120
  label: z.string().optional().describe('Session label'),
121
+ /** Observer event filters to request during the initial connection. */
122
+ subscribe: z
123
+ .array(z.string())
124
+ .optional()
125
+ .describe('Observer event filters to request during connection setup'),
121
126
  /** Client origin (web, desktop, tui, sdk) */
122
127
  origin: z.enum(['web', 'desktop', 'tui', 'sdk']).optional().describe('Client origin'),
123
128
  /** Driver mode: 'rpc' for RPC bridge driver */
@@ -198,7 +203,13 @@ export const CoderHubWebSocketError = StructuredError('CoderHubWebSocketError')<
198
203
  | 'response_timeout'
199
204
  | 'invalid_response';
200
205
  sessionId?: string;
206
+ serverCode?: string;
207
+ serverMessage?: string;
208
+ serverMessageType?: 'connection_rejected' | 'protocol_error';
209
+ closeCode?: number;
210
+ closeReason?: string;
201
211
  }>();
212
+ export type CoderHubWebSocketErrorInstance = InstanceType<typeof CoderHubWebSocketError>;
202
213
 
203
214
  interface PendingRequest {
204
215
  resolve: (response: CoderHubResponse) => void;
@@ -270,6 +281,7 @@ export class CoderHubWebSocketClient {
270
281
  parentSessionId: string;
271
282
  task: string;
272
283
  label: string;
284
+ subscribe: string[];
273
285
  origin: 'web' | 'desktop' | 'tui' | 'sdk';
274
286
  driverMode: 'rpc' | undefined;
275
287
  driverInstanceId: string;
@@ -317,6 +329,7 @@ export class CoderHubWebSocketClient {
317
329
  parentSessionId: options.parentSessionId ?? '',
318
330
  task: options.task ?? '',
319
331
  label: options.label ?? '',
332
+ subscribe: options.subscribe ?? [],
320
333
  origin: options.origin ?? 'sdk',
321
334
  driverMode: options.driverMode,
322
335
  driverInstanceId: options.driverInstanceId ?? '',
@@ -522,6 +535,57 @@ export class CoderHubWebSocketClient {
522
535
  return `${Date.now()}-${++this.#messageId}`;
523
536
  }
524
537
 
538
+ #buildHandshakeError(input: {
539
+ code: 'auth_failed' | 'connection_error';
540
+ message: string;
541
+ serverCode?: string;
542
+ serverMessage?: string;
543
+ serverMessageType?: 'connection_rejected' | 'protocol_error';
544
+ closeCode?: number;
545
+ closeReason?: string;
546
+ }): CoderHubWebSocketErrorInstance {
547
+ return new CoderHubWebSocketError({
548
+ code: input.code,
549
+ message: input.message,
550
+ sessionId: this.sessionId,
551
+ serverCode: input.serverCode,
552
+ serverMessage: input.serverMessage,
553
+ serverMessageType: input.serverMessageType,
554
+ closeCode: input.closeCode,
555
+ closeReason: input.closeReason,
556
+ });
557
+ }
558
+
559
+ #markReady(input?: {
560
+ initMessage?: CoderHubInitMessage;
561
+ firstMessage?: ServerMessage;
562
+ sendBootstrapReady?: boolean;
563
+ }): void {
564
+ this.#authenticated = true;
565
+ this.#initMessage = input?.initMessage ?? null;
566
+ this.#sessionId =
567
+ input?.initMessage?.sessionId ??
568
+ (input?.firstMessage && 'sessionId' in input.firstMessage
569
+ ? input.firstMessage.sessionId
570
+ : undefined) ??
571
+ this.#options.sessionId ??
572
+ null;
573
+ this.#reconnectAttempts = 0;
574
+ this.#setState('connected');
575
+ this.#startHeartbeat();
576
+ if (input?.initMessage) {
577
+ this.#options.onInit(input.initMessage);
578
+ }
579
+ if (input?.sendBootstrapReady && this.#ws?.readyState === WebSocket.OPEN) {
580
+ this.#ws.send(JSON.stringify({ type: 'bootstrap_ready' }));
581
+ }
582
+ this.#flushMessageQueue();
583
+ this.#options.onOpen();
584
+ if (input?.firstMessage) {
585
+ this.#options.onMessage(input.firstMessage);
586
+ }
587
+ }
588
+
525
589
  #setState(state: CoderHubWebSocketState): void {
526
590
  if (this.#state !== state) {
527
591
  this.#state = state;
@@ -589,6 +653,8 @@ export class CoderHubWebSocketClient {
589
653
  parent: this.#options.parentSessionId || undefined,
590
654
  task: this.#options.task || undefined,
591
655
  label: this.#options.label || undefined,
656
+ subscribe:
657
+ this.#options.subscribe.length > 0 ? this.#options.subscribe.join(',') : undefined,
592
658
  orgId: this.#options.orgId || undefined,
593
659
  origin: this.#options.origin || undefined,
594
660
  driverMode: this.#options.driverMode || undefined,
@@ -601,6 +667,9 @@ export class CoderHubWebSocketClient {
601
667
  params.set(key, String(value));
602
668
  }
603
669
  }
670
+ if (this.#options.apiKey) {
671
+ params.set('api_key', this.#options.apiKey);
672
+ }
604
673
 
605
674
  const queryString = params.toString();
606
675
  return queryString ? `${wsUrl}?${queryString}` : wsUrl;
@@ -642,12 +711,16 @@ export class CoderHubWebSocketClient {
642
711
  ws.onopen = () => {
643
712
  if (ws !== this.#ws) return;
644
713
  this.#setState('authenticating');
645
- ws.send(
646
- JSON.stringify({
647
- authorization: this.#options.apiKey,
648
- org_id: this.#options.orgId,
649
- })
650
- );
714
+ if (this.#options.apiKey || this.#options.orgId) {
715
+ const bootstrapPayload: { authorization?: string; org_id?: string } = {};
716
+ if (this.#options.apiKey) {
717
+ bootstrapPayload.authorization = this.#options.apiKey;
718
+ }
719
+ if (this.#options.orgId) {
720
+ bootstrapPayload.org_id = this.#options.orgId;
721
+ }
722
+ ws.send(JSON.stringify(bootstrapPayload));
723
+ }
651
724
  };
652
725
 
653
726
  ws.onmessage = (event: MessageEvent) => {
@@ -685,10 +758,12 @@ export class CoderHubWebSocketClient {
685
758
  if (msg.type === 'connection_rejected' || msg.type === 'protocol_error') {
686
759
  this.#setState('closed');
687
760
  this.#options.onError(
688
- new CoderHubWebSocketError({
689
- message: `Connection rejected: ${msg.message ?? msg.code ?? 'Unknown error'}`,
761
+ this.#buildHandshakeError({
690
762
  code: 'auth_failed',
691
- sessionId: this.sessionId,
763
+ message: `Connection rejected: ${msg.message ?? msg.code ?? 'Unknown error'}`,
764
+ serverCode: msg.code,
765
+ serverMessage: msg.message,
766
+ serverMessageType: msg.type,
692
767
  })
693
768
  );
694
769
  this.#intentionallyClosed = true;
@@ -700,15 +775,18 @@ export class CoderHubWebSocketClient {
700
775
  const initResult = CoderHubInitMessageSchema.safeParse(parsed);
701
776
  if (initResult.success) {
702
777
  const initMsg = initResult.data;
703
- this.#authenticated = true;
704
- this.#initMessage = initMsg;
705
- this.#sessionId = initMsg.sessionId ?? this.#options.sessionId ?? null;
706
- this.#reconnectAttempts = 0;
707
- this.#setState('connected');
708
- this.#startHeartbeat();
709
- this.#flushMessageQueue();
710
- this.#options.onInit(initMsg);
711
- this.#options.onOpen();
778
+ this.#markReady({
779
+ initMessage: initMsg,
780
+ sendBootstrapReady: this.#options.role === 'controller',
781
+ });
782
+ return;
783
+ }
784
+
785
+ if (this.#options.role === 'observer') {
786
+ const firstObserverMessage = parseServerMessage(parsed);
787
+ if (firstObserverMessage) {
788
+ this.#markReady({ firstMessage: firstObserverMessage });
789
+ }
712
790
  }
713
791
  return;
714
792
  }
@@ -759,14 +837,31 @@ export class CoderHubWebSocketClient {
759
837
  this.#clearTimers();
760
838
  this.#setState('closed');
761
839
 
840
+ const wasAuthenticated = this.#authenticated;
841
+ const hadTerminalError = this.#intentionallyClosed;
842
+ const terminalClose = isTerminalCloseCode(event.code);
843
+
762
844
  // Clear auth state for clean reconnect
763
845
  this.#authenticated = false;
764
846
  this.#initMessage = null;
765
847
 
766
- if (isTerminalCloseCode(event.code)) {
848
+ if (terminalClose) {
767
849
  this.#intentionallyClosed = true;
768
850
  }
769
851
 
852
+ if (!wasAuthenticated && terminalClose && !hadTerminalError) {
853
+ this.#options.onError(
854
+ this.#buildHandshakeError({
855
+ code: 'connection_error',
856
+ message: `WebSocket closed before connection was ready (code ${event.code})${
857
+ event.reason ? `: ${event.reason}` : ''
858
+ }`,
859
+ closeCode: event.code,
860
+ closeReason: event.reason || undefined,
861
+ })
862
+ );
863
+ }
864
+
770
865
  this.#options.onClose(event.code, event.reason);
771
866
 
772
867
  if (!this.#intentionallyClosed) {
@@ -896,7 +991,11 @@ export async function* subscribeToCoderHub(
896
991
  onError: (error) => {
897
992
  if (
898
993
  error instanceof CoderHubWebSocketError &&
899
- (error.code === 'max_reconnects_exceeded' || error.code === 'auth_failed')
994
+ (error.code === 'max_reconnects_exceeded' ||
995
+ error.code === 'auth_failed' ||
996
+ (error.code === 'connection_error' &&
997
+ typeof error.closeCode === 'number' &&
998
+ isTerminalCloseCode(error.closeCode)))
900
999
  ) {
901
1000
  terminalError = error;
902
1001
  done = true;
@@ -2,9 +2,13 @@ import { z } from 'zod/v4';
2
2
  import { type APIClient } from '../api.ts';
3
3
  import {
4
4
  CoderCreateWorkspaceRequestSchema,
5
+ CoderUpdateWorkspaceRequestSchema,
6
+ CoderWorkspaceDependencyValidationResponseSchema,
5
7
  CoderWorkspaceDetailSchema,
6
8
  CoderWorkspaceListResponseSchema,
7
9
  type CoderCreateWorkspaceRequest,
10
+ type CoderUpdateWorkspaceRequest,
11
+ type CoderWorkspaceDependencyValidationResponse,
8
12
  type CoderWorkspaceDetail,
9
13
  type CoderWorkspaceListResponse,
10
14
  } from './types.ts';
@@ -25,6 +29,34 @@ const WorkspaceCreateResponseSchema = z
25
29
  .passthrough()
26
30
  .describe('Wrapped workspace create response from coder hub');
27
31
 
32
+ const WorkspaceUpdateResponseSchema = z
33
+ .object({
34
+ workspace: CoderWorkspaceDetailSchema.describe(
35
+ 'Updated workspace payload returned by coder hub'
36
+ ),
37
+ })
38
+ .passthrough()
39
+ .describe('Wrapped workspace update response from coder hub');
40
+
41
+ const WorkspaceSnapshotRefreshResponseSchema = z
42
+ .object({
43
+ workspace: CoderWorkspaceDetailSchema.describe(
44
+ 'Workspace payload returned after refreshing its snapshot'
45
+ ),
46
+ })
47
+ .passthrough()
48
+ .describe('Wrapped workspace snapshot refresh response from coder hub');
49
+
50
+ const WorkspaceDependencyValidationWrappedResponseSchema = z
51
+ .object({
52
+ success: z.boolean().describe('Validation request success indicator'),
53
+ data: CoderWorkspaceDependencyValidationResponseSchema.describe(
54
+ 'Dependency validation result'
55
+ ),
56
+ })
57
+ .passthrough()
58
+ .describe('Wrapped workspace dependency validation response from coder hub');
59
+
28
60
  const OkResponseSchema = z
29
61
  .object({
30
62
  ok: z.boolean().describe('Operation success indicator'),
@@ -68,6 +100,48 @@ export async function coderCreateWorkspace(
68
100
  return resp.workspace;
69
101
  }
70
102
 
103
+ export async function coderUpdateWorkspace(
104
+ client: APIClient,
105
+ params: { workspaceId: string; body: CoderUpdateWorkspaceRequest }
106
+ ): Promise<CoderWorkspaceDetail> {
107
+ const path = `/hub/workspaces/${encodeURIComponent(params.workspaceId)}`;
108
+ const resp = await client.patch<
109
+ z.infer<typeof WorkspaceUpdateResponseSchema>,
110
+ CoderUpdateWorkspaceRequest
111
+ >(path, params.body, WorkspaceUpdateResponseSchema, CoderUpdateWorkspaceRequestSchema);
112
+
113
+ return resp.workspace;
114
+ }
115
+
116
+ export async function coderRefreshWorkspaceSnapshot(
117
+ client: APIClient,
118
+ params: { workspaceId: string }
119
+ ): Promise<CoderWorkspaceDetail> {
120
+ const path = `/hub/workspaces/${encodeURIComponent(params.workspaceId)}/snapshot/refresh`;
121
+ const resp = await client.post<z.infer<typeof WorkspaceSnapshotRefreshResponseSchema>>(
122
+ path,
123
+ undefined,
124
+ WorkspaceSnapshotRefreshResponseSchema
125
+ );
126
+
127
+ return resp.workspace;
128
+ }
129
+
130
+ export async function coderValidateWorkspaceDependencies(
131
+ client: APIClient,
132
+ params: { dependencies: string[] }
133
+ ): Promise<CoderWorkspaceDependencyValidationResponse> {
134
+ const resp = await client.post<
135
+ z.infer<typeof WorkspaceDependencyValidationWrappedResponseSchema>
136
+ >(
137
+ '/hub/workspaces/dependencies/validate',
138
+ { dependencies: params.dependencies },
139
+ WorkspaceDependencyValidationWrappedResponseSchema
140
+ );
141
+
142
+ return resp.data;
143
+ }
144
+
71
145
  export async function coderDeleteWorkspace(
72
146
  client: APIClient,
73
147
  params: { workspaceId: string }
@@ -613,7 +613,7 @@ const service: Service = {
613
613
  ],
614
614
  requestBody: {
615
615
  description: 'Snapshot creation payload.',
616
- fields: { schema: SnapshotCreateOptionsSchema },
616
+ fields: { schema: SnapshotCreateOptionsSchema.omit({ orgId: true }) },
617
617
  },
618
618
  responseDescription: 'Returns the created snapshot.',
619
619
  statuses: [
@@ -887,15 +887,15 @@ const service: Service = {
887
887
  exampleHeaders: { 'Content-Type': 'application/gzip' },
888
888
  exampleBody: '<binary gzip data>',
889
889
  },
890
- // ── Disk Checkpoints ──────────────────────────────────────────────
890
+ // ── Sandbox Checkpoints ───────────────────────────────────────────
891
891
  {
892
892
  id: 'create-checkpoint',
893
893
  title: 'Create Checkpoint',
894
- sectionTitle: 'Disk Checkpoints',
894
+ sectionTitle: 'Sandbox Checkpoints',
895
895
  method: 'POST',
896
896
  path: '/sandbox/{sandboxId}/checkpoint',
897
897
  description:
898
- 'Create a named checkpoint of the sandbox filesystem. Checkpoint names must be unique — creating a checkpoint with a name that already exists returns 409 Conflict. Disk checkpoints are persisted across pause/resume cycles.',
898
+ 'Create a named checkpoint of the sandbox filesystem. Checkpoint names must be unique — creating a checkpoint with a name that already exists returns 409 Conflict. Checkpoints are persisted across pause/resume cycles.',
899
899
  pathParams: [
900
900
  { name: 'sandboxId', type: 'string', description: 'Sandbox ID', required: true },
901
901
  ],
@@ -923,11 +923,11 @@ const service: Service = {
923
923
  {
924
924
  id: 'list-checkpoints',
925
925
  title: 'List Checkpoints',
926
- sectionTitle: 'Disk Checkpoints',
926
+ sectionTitle: 'Sandbox Checkpoints',
927
927
  method: 'GET',
928
928
  path: '/sandbox/checkpoints/{sandboxId}',
929
929
  description:
930
- 'List checkpoints for a specific sandbox. Disk checkpoints are persisted across pause/resume cycles.',
930
+ 'List checkpoints for a specific sandbox. Checkpoints are persisted across pause/resume cycles.',
931
931
  pathParams: [
932
932
  { name: 'sandboxId', type: 'string', description: 'Sandbox ID', required: true },
933
933
  ],
@@ -947,7 +947,7 @@ const service: Service = {
947
947
  {
948
948
  id: 'restore-checkpoint',
949
949
  title: 'Restore Checkpoint',
950
- sectionTitle: 'Disk Checkpoints',
950
+ sectionTitle: 'Sandbox Checkpoints',
951
951
  method: 'POST',
952
952
  path: '/sandbox/{sandboxId}/checkpoint/{checkpointId}/restore',
953
953
  description: 'Restore the sandbox filesystem to a checkpoint state.',
@@ -971,7 +971,7 @@ const service: Service = {
971
971
  {
972
972
  id: 'delete-checkpoint',
973
973
  title: 'Delete Checkpoint',
974
- sectionTitle: 'Disk Checkpoints',
974
+ sectionTitle: 'Sandbox Checkpoints',
975
975
  method: 'DELETE',
976
976
  path: '/sandbox/{sandboxId}/checkpoint/{checkpointId}',
977
977
  description: 'Delete a checkpoint.',
@@ -34,7 +34,7 @@ import {
34
34
  sandboxRmDir,
35
35
  sandboxSetEnv,
36
36
  } from './files.ts';
37
- import { sandboxPause } from './pause.ts';
37
+ import { sandboxPause, type SandboxPauseResult } from './pause.ts';
38
38
  import { sandboxResume } from './resume.ts';
39
39
  import { sandboxRun } from './run.ts';
40
40
  import {
@@ -281,7 +281,7 @@ export interface SandboxInstance {
281
281
  /**
282
282
  * Pause the sandbox, creating a checkpoint of its current state
283
283
  */
284
- pause(): Promise<void>;
284
+ pause(): Promise<SandboxPauseResult>;
285
285
 
286
286
  /**
287
287
  * Resume the sandbox from a paused or evacuated state
@@ -399,7 +399,7 @@ function createSandboxInstanceMethods(
399
399
  return sandboxGet(client, { sandboxId, orgId });
400
400
  },
401
401
 
402
- async pause(): Promise<void> {
402
+ async pause(): Promise<SandboxPauseResult> {
403
403
  return sandboxPause(client, { sandboxId, orgId });
404
404
  },
405
405
 
@@ -725,7 +725,7 @@ export class SandboxClient {
725
725
  *
726
726
  * @param sandboxId - The sandbox ID to pause
727
727
  */
728
- async pause(sandboxId: string): Promise<void> {
728
+ async pause(sandboxId: string): Promise<SandboxPauseResult> {
729
729
  return sandboxPause(this.#client, { sandboxId, orgId: this.#orgId });
730
730
  }
731
731
 
@@ -49,6 +49,12 @@ export const SandboxCreateRequestSchema = z
49
49
  .object({
50
50
  idle: z.string().optional().describe('Idle timeout duration (e.g., "5m", "1h")'),
51
51
  execution: z.string().optional().describe('Maximum execution time (e.g., "30m", "2h")'),
52
+ paused: z
53
+ .string()
54
+ .optional()
55
+ .describe(
56
+ 'Maximum time sandbox can remain paused before termination (e.g., "24h", "0s" for infinite)'
57
+ ),
52
58
  })
53
59
  .optional()
54
60
  .describe('Timeout settings for the sandbox'),
@@ -139,6 +145,10 @@ export const SandboxCreateDataSchema = z
139
145
  'failed',
140
146
  ])
141
147
  .describe('Current status of the sandbox'),
148
+ executionId: z
149
+ .string()
150
+ .optional()
151
+ .describe('Initial execution identifier for oneshot sandbox creation'),
142
152
  url: z
143
153
  .string()
144
154
  .optional()
@@ -156,7 +156,7 @@ export const SandboxInfoDataSchema = z
156
156
  user: SandboxUserInfoSchema.optional().describe('User who created the sandbox'),
157
157
  agent: SandboxAgentInfoSchema.optional().describe('Agent associated with the sandbox'),
158
158
  project: SandboxProjectInfoSchema.optional().describe('Project associated with the sandbox'),
159
- org: SandboxOrgInfoSchema.describe('Organization associated with the sandbox'),
159
+ org: SandboxOrgInfoSchema.nullish().describe('Organization associated with the sandbox'),
160
160
  timeout: z
161
161
  .object({
162
162
  idle: z.string().optional().describe('Idle timeout duration (e.g., "5m", "1h").'),
@@ -164,6 +164,10 @@ export const SandboxInfoDataSchema = z
164
164
  .string()
165
165
  .optional()
166
166
  .describe('Execution timeout duration (e.g., "30m", "2h").'),
167
+ paused: z
168
+ .string()
169
+ .optional()
170
+ .describe('Paused timeout duration (e.g., "24h", "0s" for infinite).'),
167
171
  })
168
172
  .optional()
169
173
  .describe('Timeout configuration for the sandbox.'),
@@ -189,6 +193,16 @@ export const SandboxGetParamsSchema = z.object({
189
193
  .boolean()
190
194
  .optional()
191
195
  .describe('Whether deleted sandboxes should be included in lookup'),
196
+ waitForStatus: z
197
+ .union([z.string(), z.array(z.string())])
198
+ .optional()
199
+ .describe('Optional desired status or statuses to wait for before responding'),
200
+ waitMs: z
201
+ .number()
202
+ .int()
203
+ .nonnegative()
204
+ .optional()
205
+ .describe('Maximum time in milliseconds to wait for the desired status'),
192
206
  });
193
207
 
194
208
  export type SandboxGetParams = z.infer<typeof SandboxGetParamsSchema>;
@@ -205,7 +219,7 @@ export async function sandboxGet(
205
219
  client: APIClient,
206
220
  params: SandboxGetParams
207
221
  ): Promise<SandboxInfo> {
208
- const { sandboxId, orgId, includeDeleted } = params;
222
+ const { sandboxId, orgId, includeDeleted, waitForStatus, waitMs } = params;
209
223
  const queryParams = new URLSearchParams();
210
224
  if (orgId) {
211
225
  queryParams.set('orgId', orgId);
@@ -213,6 +227,15 @@ export async function sandboxGet(
213
227
  if (includeDeleted) {
214
228
  queryParams.set('includeDeleted', 'true');
215
229
  }
230
+ if (waitForStatus) {
231
+ queryParams.set(
232
+ 'waitForStatus',
233
+ Array.isArray(waitForStatus) ? waitForStatus.join(',') : waitForStatus
234
+ );
235
+ }
236
+ if (waitMs != null) {
237
+ queryParams.set('waitMs', String(waitMs));
238
+ }
216
239
  const queryString = queryParams.toString();
217
240
  const url = `/sandbox/${encodeURIComponent(sandboxId)}${queryString ? `?${queryString}` : ''}`;
218
241
 
@@ -222,6 +245,12 @@ export async function sandboxGet(
222
245
  );
223
246
 
224
247
  if (resp.success) {
248
+ // Newly created sandboxes can be served from the server's pending cache before
249
+ // the backing row and related org hydration are fully available. That response
250
+ // serializes `org: null`, so normalize it to a placeholder object instead of
251
+ // failing response validation for an otherwise valid sandbox status poll.
252
+ const org = resp.data.org ?? { id: orgId ?? '', name: '' };
253
+
225
254
  return {
226
255
  sandboxId: resp.data.sandboxId,
227
256
  identifier: resp.data.identifier,
@@ -252,7 +281,7 @@ export async function sandboxGet(
252
281
  user: resp.data.user,
253
282
  agent: resp.data.agent,
254
283
  project: resp.data.project,
255
- org: resp.data.org,
284
+ org,
256
285
  timeout: resp.data.timeout,
257
286
  command: resp.data.command,
258
287
  };
@@ -13,6 +13,16 @@ const SandboxStatusResponseSchema = APIResponseSchema(SandboxStatusDataSchema);
13
13
  export const SandboxGetStatusParamsSchema = z.object({
14
14
  sandboxId: z.string().describe('Sandbox ID to retrieve status for'),
15
15
  orgId: z.string().optional().describe('Optional org id for CLI auth context'),
16
+ waitForStatus: z
17
+ .union([z.string(), z.array(z.string())])
18
+ .optional()
19
+ .describe('Optional desired status or statuses to wait for before responding'),
20
+ waitMs: z
21
+ .number()
22
+ .int()
23
+ .nonnegative()
24
+ .optional()
25
+ .describe('Maximum time in milliseconds to wait for the desired status'),
16
26
  });
17
27
 
18
28
  export type SandboxGetStatusParams = z.infer<typeof SandboxGetStatusParamsSchema>;
@@ -26,11 +36,20 @@ export async function sandboxGetStatus(
26
36
  client: APIClient,
27
37
  params: SandboxGetStatusParams
28
38
  ): Promise<SandboxStatusResult> {
29
- const { sandboxId, orgId } = params;
39
+ const { sandboxId, orgId, waitForStatus, waitMs } = params;
30
40
  const queryParams = new URLSearchParams();
31
41
  if (orgId) {
32
42
  queryParams.set('orgId', orgId);
33
43
  }
44
+ if (waitForStatus) {
45
+ queryParams.set(
46
+ 'waitForStatus',
47
+ Array.isArray(waitForStatus) ? waitForStatus.join(',') : waitForStatus
48
+ );
49
+ }
50
+ if (waitMs != null) {
51
+ queryParams.set('waitMs', String(waitMs));
52
+ }
34
53
  const queryString = queryParams.toString();
35
54
  const url = `/sandbox/status/${sandboxId}${queryString ? `?${queryString}` : ''}`;
36
55
 
@@ -52,7 +52,7 @@ export {
52
52
  diskCheckpointRestore,
53
53
  diskCheckpointDelete,
54
54
  } from './disk-checkpoint.ts';
55
- export type { SandboxPauseParams } from './pause.ts';
55
+ export type { SandboxPauseParams, SandboxPauseResult } from './pause.ts';
56
56
  export { PauseResponseSchema, SandboxPauseParamsSchema, sandboxPause } from './pause.ts';
57
57
  export type { SandboxResumeParams } from './resume.ts';
58
58
  export { ResumeResponseSchema, SandboxResumeParamsSchema, sandboxResume } from './resume.ts';
@@ -112,6 +112,10 @@ export const SandboxInfoSchema = z
112
112
  .string()
113
113
  .optional()
114
114
  .describe('Execution timeout duration (e.g., "30m", "2h").'),
115
+ paused: z
116
+ .string()
117
+ .optional()
118
+ .describe('Paused timeout duration (e.g., "24h", "0s" for infinite).'),
115
119
  })
116
120
  .optional()
117
121
  .describe('Timeout configuration for the sandbox.'),
@@ -225,6 +229,7 @@ export async function sandboxList(
225
229
  networkPort: s.networkPort,
226
230
  url: s.url,
227
231
  org: s.org,
232
+ timeout: s.timeout,
228
233
  })),
229
234
  total: resp.data.total,
230
235
  };
@@ -1,8 +1,21 @@
1
1
  import { z } from 'zod';
2
- import { type APIClient, APIResponseSchemaNoData } from '../api.ts';
2
+ import { type APIClient } from '../api.ts';
3
3
  import { throwSandboxError } from './util.ts';
4
4
 
5
- export const PauseResponseSchema = APIResponseSchemaNoData();
5
+ export const PauseResponseSchema = z.discriminatedUnion('success', [
6
+ z.object({
7
+ success: z.literal<false>(false),
8
+ message: z.string(),
9
+ code: z.string().optional(),
10
+ }),
11
+ z.object({
12
+ success: z.literal<true>(true),
13
+ sandboxId: z.string(),
14
+ status: z.string(),
15
+ checkpointId: z.string().optional(),
16
+ terminatesAt: z.string().optional(),
17
+ }),
18
+ ]);
6
19
 
7
20
  export const SandboxPauseParamsSchema = z.object({
8
21
  sandboxId: z.string().describe('Sandbox ID to pause'),
@@ -11,14 +24,30 @@ export const SandboxPauseParamsSchema = z.object({
11
24
 
12
25
  export type SandboxPauseParams = z.infer<typeof SandboxPauseParamsSchema>;
13
26
 
27
+ /** Result returned from pausing a sandbox */
28
+ export interface SandboxPauseResult {
29
+ /** The sandbox ID that was paused */
30
+ sandboxId: string;
31
+ /** New status (typically "suspended") */
32
+ status: string;
33
+ /** Checkpoint ID created during pause */
34
+ checkpointId?: string;
35
+ /** ISO 8601 timestamp when sandbox will auto-terminate if not resumed (omitted if no paused timeout) */
36
+ terminatesAt?: string;
37
+ }
38
+
14
39
  /**
15
40
  * Pauses a running sandbox, creating a checkpoint of its current state.
16
41
  *
17
42
  * @param client - The API client to use for the request
18
43
  * @param params - Parameters including the sandbox ID to pause
44
+ * @returns Pause result including terminatesAt if a paused timeout is configured
19
45
  * @throws {SandboxResponseError} If the sandbox is not found or pause fails
20
46
  */
21
- export async function sandboxPause(client: APIClient, params: SandboxPauseParams): Promise<void> {
47
+ export async function sandboxPause(
48
+ client: APIClient,
49
+ params: SandboxPauseParams
50
+ ): Promise<SandboxPauseResult> {
22
51
  const { sandboxId, orgId } = params;
23
52
  const queryParams = new URLSearchParams();
24
53
  if (orgId) {
@@ -34,7 +63,12 @@ export async function sandboxPause(client: APIClient, params: SandboxPauseParams
34
63
  );
35
64
 
36
65
  if (resp.success) {
37
- return;
66
+ return {
67
+ sandboxId: resp.sandboxId,
68
+ status: resp.status,
69
+ checkpointId: resp.checkpointId,
70
+ terminatesAt: resp.terminatesAt,
71
+ };
38
72
  }
39
73
 
40
74
  throwSandboxError(resp, { sandboxId });