@opengeni/sdk 0.11.0 → 0.13.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/src/client.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { OpenGeniApiError } from "./errors";
2
- import { streamSessionEvents, type SessionEventStreamTransport, type StreamSessionEventsOptions } from "./stream";
2
+ import {
3
+ streamSessionEvents,
4
+ type SessionEventStreamTransport,
5
+ type StreamSessionEventsOptions,
6
+ } from "./stream";
3
7
  import type {
4
8
  AccessContext,
5
9
  AddWorkspaceMemberRequest,
@@ -23,11 +27,13 @@ import type {
23
27
  ClientSessionEventInput,
24
28
  CompactSessionContextResult,
25
29
  CompleteFileUploadResponse,
30
+ ConnectionMetadata,
26
31
  CreateApiKeyRequest,
27
32
  CreateApiKeyResponse,
28
33
  CreateCapabilityCatalogItemRequest,
29
34
  CreateCheckoutRequest,
30
35
  CreateCheckoutResponse,
36
+ CreateConnectionRequest,
31
37
  CreateDocumentBaseRequest,
32
38
  CreateFileUploadRequest,
33
39
  CreateFileUploadResponse,
@@ -36,7 +42,8 @@ import type {
36
42
  CreateKnowledgeMemoryRequest,
37
43
  CreateScheduledTaskRequest,
38
44
  CreateSessionRequest,
39
- CreateWorkspaceEnvironmentRequest,
45
+ CreateVariableSetRequest,
46
+ CreateRigRequest,
40
47
  CreateWorkspaceRequest,
41
48
  // Enrollment UX (design 11): the click-Grant approve-page lookup/deny + headless
42
49
  // enroll-token mint.
@@ -62,7 +69,6 @@ import type {
62
69
  ListPacksResponse,
63
70
  // Bring-your-own-compute: the Machines dashboard + per-machine metrics (M10).
64
71
  MachinesResponse,
65
- MachineView,
66
72
  MetricSample,
67
73
  MachineMetricsSeriesResponse,
68
74
  // Bring-your-own-compute: the user-authenticated active-sandbox swap (M7).
@@ -76,8 +82,16 @@ import type {
76
82
  ScheduledTask,
77
83
  ScheduledTaskRun,
78
84
  Session,
85
+ SessionListResponse,
86
+ UpdateSessionPinRequest,
79
87
  SessionEvent,
80
88
  SessionGoal,
89
+ SessionLineageResponse,
90
+ SessionMcpCredentialUpdateInput,
91
+ SessionQueueSnapshot,
92
+ SessionQueueMutationResponse,
93
+ SessionControlResponse,
94
+ WorkspaceInferenceControlResponse,
81
95
  SessionTurn,
82
96
  // Stream surfacing (Phase 5): capability negotiation + viewer lifecycle + config.
83
97
  SessionCapabilities,
@@ -108,6 +122,9 @@ import type {
108
122
  GitLogResponse,
109
123
  GitShowRequest,
110
124
  GitShowResponse,
125
+ // Workbench v2 turn-end capture reads (M2, dossier §10.3).
126
+ GetWorkspaceCaptureResponse,
127
+ GetWorkspaceCaptureFileResponse,
111
128
  TerminalExecRequest,
112
129
  TerminalExecResponse,
113
130
  PtyOpenRequest,
@@ -116,20 +133,33 @@ import type {
116
133
  PtyResizeRequest,
117
134
  PtyCloseRequest,
118
135
  ToolRef,
136
+ UpdateConnectionRequest,
119
137
  UpdateKnowledgeMemoryRequest,
120
138
  UpdateScheduledTaskRequest,
121
139
  UpdateSessionGoalRequest,
122
140
  UpdateSessionRequest,
123
- UpdateSessionTurnRequest,
124
- UpdateWorkspaceEnvironmentRequest,
141
+ UpdateVariableSetRequest,
142
+ UpdateRigRequest,
125
143
  UpdateWorkspaceMemberRequest,
126
144
  UpdateWorkspaceRequest,
145
+ UpdateWorkspaceSettingsRequest,
146
+ SetWorkspaceDefaultRigRequest,
127
147
  UploadFileInput,
128
- WorkspaceEnvironment,
129
- WorkspaceEnvironmentVariableMetadata,
148
+ VariableSet,
149
+ VariableSetVariableMetadata,
150
+ Rig,
151
+ RigVersion,
152
+ RigChange,
153
+ ProposeRigChangeRequest,
130
154
  WorkspaceMember,
155
+ WorkspaceMemorySearchRequest,
156
+ WorkspaceMemorySearchResponse,
131
157
  WorkspaceRegisteredPack,
132
158
  Workspace,
159
+ ListConnectionsResponse,
160
+ ConnectionResponse,
161
+ OAuthStartRequest,
162
+ OAuthStartResponse,
133
163
  } from "./types";
134
164
 
135
165
  export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
@@ -152,19 +182,16 @@ export type SendMessageInput = {
152
182
  model?: string;
153
183
  reasoningEffort?: ReasoningEffort;
154
184
  clientEventId?: string;
185
+ expectedControlGeneration?: number;
186
+ expectedWorkspaceInferenceGeneration?: number;
187
+ mcpCredentialUpdates?: SessionMcpCredentialUpdateInput[];
155
188
  };
156
189
 
157
190
  export type SteerMessageResult = {
158
191
  /** The accepted `user.message` event. */
159
192
  accepted: SessionEvent;
160
- /**
161
- * The turn created for the message, when it could be located — usually
162
- * still queued, but already claimed (running/requires_action or even
163
- * finished) when the worker picked it up mid-call.
164
- */
165
- turn: SessionTurn | null;
166
- /** True when the running turn was interrupted to make way for the message. */
167
- interrupted: boolean;
193
+ /** The exact turn created for this message in the same server transaction. */
194
+ turn: SessionTurn;
168
195
  };
169
196
 
170
197
  /**
@@ -180,34 +207,145 @@ export class OpenGeniClient {
180
207
  constructor(options: OpenGeniClientOptions) {
181
208
  this.baseUrl = options.baseUrl.replace(/\/+$/, "");
182
209
  this.options = options;
183
- // Bind lazily so environments that polyfill fetch after module load work.
210
+ // Bind lazily so variable sets that polyfill fetch after module load work.
184
211
  this.fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
185
212
  }
186
213
 
187
214
  // --- Session lifecycle ---------------------------------------------------
188
215
 
189
216
  async createSession(workspaceId: string, request: CreateSessionRequest): Promise<Session> {
190
- return await this.requestJson<Session>("POST", `/v1/workspaces/${workspaceId}/sessions`, request);
217
+ return await this.requestJson<Session>(
218
+ "POST",
219
+ `/v1/workspaces/${workspaceId}/sessions`,
220
+ request,
221
+ );
191
222
  }
192
223
 
193
224
  async getSession(workspaceId: string, sessionId: string): Promise<Session> {
194
- return await this.requestJson<Session>("GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}`);
225
+ return await this.requestJson<Session>(
226
+ "GET",
227
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}`,
228
+ );
195
229
  }
196
230
 
197
- async updateSession(workspaceId: string, sessionId: string, request: UpdateSessionRequest): Promise<Session> {
198
- return await this.requestJson<Session>("PATCH", `/v1/workspaces/${workspaceId}/sessions/${sessionId}`, request);
231
+ async updateSession(
232
+ workspaceId: string,
233
+ sessionId: string,
234
+ request: UpdateSessionRequest,
235
+ ): Promise<Session> {
236
+ return await this.requestJson<Session>(
237
+ "PATCH",
238
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}`,
239
+ request,
240
+ );
199
241
  }
200
242
 
201
- async listSessions(workspaceId: string, options: { limit?: number } = {}): Promise<Session[]> {
202
- return await this.requestJson<Session[]>("GET", `/v1/workspaces/${workspaceId}/sessions`, undefined, {
203
- ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
204
- });
243
+ async listSessions(
244
+ workspaceId: string,
245
+ options: {
246
+ limit?: number;
247
+ parentSessionId?: string | null;
248
+ search?: string;
249
+ } = {},
250
+ ): Promise<Session[]> {
251
+ return await this.requestJson<Session[]>(
252
+ "GET",
253
+ `/v1/workspaces/${workspaceId}/sessions`,
254
+ undefined,
255
+ {
256
+ ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
257
+ ...(options.search?.trim() ? { search: options.search.trim() } : {}),
258
+ ...(Object.prototype.hasOwnProperty.call(options, "parentSessionId") &&
259
+ options.parentSessionId !== undefined
260
+ ? {
261
+ parentSessionId:
262
+ options.parentSessionId === null ? "null" : String(options.parentSessionId),
263
+ }
264
+ : {}),
265
+ },
266
+ );
205
267
  }
206
268
 
207
- async listTurns(workspaceId: string, sessionId: string, options: { limit?: number } = {}): Promise<SessionTurn[]> {
208
- return await this.requestJson<SessionTurn[]>("GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns`, undefined, {
209
- ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
210
- });
269
+ /** Pin-aware ordinary-session page with a stable keyset cursor. */
270
+ async listSessionPage(
271
+ workspaceId: string,
272
+ options: {
273
+ limit?: number;
274
+ parentSessionId?: string | null;
275
+ cursor?: string;
276
+ search?: string;
277
+ } = {},
278
+ ): Promise<SessionListResponse> {
279
+ const response = await this.requestJson<SessionListResponse | Session[]>(
280
+ "GET",
281
+ `/v1/workspaces/${workspaceId}/sessions`,
282
+ undefined,
283
+ {
284
+ view: "page",
285
+ ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
286
+ ...(options.cursor !== undefined ? { cursor: options.cursor } : {}),
287
+ ...(options.search?.trim() ? { search: options.search.trim() } : {}),
288
+ ...(Object.prototype.hasOwnProperty.call(options, "parentSessionId") &&
289
+ options.parentSessionId !== undefined
290
+ ? {
291
+ parentSessionId:
292
+ options.parentSessionId === null ? "null" : String(options.parentSessionId),
293
+ }
294
+ : {}),
295
+ },
296
+ );
297
+ if (Array.isArray(response)) {
298
+ // Rolling/same-major compatibility: an older API ignores `view=page` and
299
+ // returns the historical array. That is an honest one-page projection;
300
+ // never pretend it honored a cursor supplied directly by a caller.
301
+ if (options.cursor) {
302
+ throw new Error("The connected OpenGeni API does not support stable session-page cursors");
303
+ }
304
+ // Older APIs ignore unknown query parameters. Treating their unfiltered
305
+ // array as a successful search would be worse than an explicit rolling-
306
+ // upgrade error (and client-side filtering cannot recover matches beyond
307
+ // the old endpoint's bounded first page).
308
+ if (options.search?.trim()) {
309
+ throw new Error("The connected OpenGeni API does not support session search");
310
+ }
311
+ return { pinned: [], sessions: response, nextCursor: null };
312
+ }
313
+ return response;
314
+ }
315
+
316
+ /** Set this authenticated member's personal workspace pin for a session. */
317
+ async updateSessionPin(
318
+ workspaceId: string,
319
+ sessionId: string,
320
+ request: UpdateSessionPinRequest,
321
+ ): Promise<Session> {
322
+ return await this.requestJson<Session>(
323
+ "PUT",
324
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/pin`,
325
+ request,
326
+ );
327
+ }
328
+
329
+ async getSessionLineage(workspaceId: string, sessionId: string): Promise<SessionLineageResponse> {
330
+ return await this.requestJson<SessionLineageResponse>(
331
+ "GET",
332
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`,
333
+ );
334
+ }
335
+
336
+ async listTurns(
337
+ workspaceId: string,
338
+ sessionId: string,
339
+ options: { limit?: number } = {},
340
+ ): Promise<SessionTurn[]> {
341
+ return await this.requestJson<SessionTurn[]>(
342
+ "GET",
343
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns`,
344
+ undefined,
345
+ {
346
+ ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
347
+ },
348
+ );
211
349
  }
212
350
 
213
351
  // --- Bring-your-own-compute: Machines dashboard + metrics (M10) ------------
@@ -218,10 +356,18 @@ export class OpenGeniClient {
218
356
  * sharedSessionCount. Pass `sessionId` for an in-session view, which adds the
219
357
  * session's synthetic Modal group box + the active-sandbox pointer.
220
358
  */
221
- async listMachines(workspaceId: string, options: { sessionId?: string } = {}): Promise<MachinesResponse> {
222
- return await this.requestJson<MachinesResponse>("GET", `/v1/workspaces/${workspaceId}/machines`, undefined, {
223
- ...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
224
- });
359
+ async listMachines(
360
+ workspaceId: string,
361
+ options: { sessionId?: string } = {},
362
+ ): Promise<MachinesResponse> {
363
+ return await this.requestJson<MachinesResponse>(
364
+ "GET",
365
+ `/v1/workspaces/${workspaceId}/machines`,
366
+ undefined,
367
+ {
368
+ ...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
369
+ },
370
+ );
225
371
  }
226
372
 
227
373
  /**
@@ -254,7 +400,11 @@ export class OpenGeniClient {
254
400
  * the request.
255
401
  */
256
402
  async lookupDeviceEnrollment(userCode: string): Promise<DeviceEnrollmentLookupResponse> {
257
- return await this.requestJson<DeviceEnrollmentLookupResponse>("POST", "/v1/enrollments/device/lookup", { userCode });
403
+ return await this.requestJson<DeviceEnrollmentLookupResponse>(
404
+ "POST",
405
+ "/v1/enrollments/device/lookup",
406
+ { userCode },
407
+ );
258
408
  }
259
409
 
260
410
  /**
@@ -324,14 +474,25 @@ export class OpenGeniClient {
324
474
 
325
475
  // --- Scheduled tasks -------------------------------------------------------
326
476
 
327
- async listScheduledTasks(workspaceId: string, options: { limit?: number } = {}): Promise<ScheduledTask[]> {
328
- return await this.requestJson<ScheduledTask[]>("GET", `/v1/workspaces/${workspaceId}/scheduled-tasks`, undefined, {
329
- ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
330
- });
477
+ async listScheduledTasks(
478
+ workspaceId: string,
479
+ options: { limit?: number } = {},
480
+ ): Promise<ScheduledTask[]> {
481
+ return await this.requestJson<ScheduledTask[]>(
482
+ "GET",
483
+ `/v1/workspaces/${workspaceId}/scheduled-tasks`,
484
+ undefined,
485
+ {
486
+ ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
487
+ },
488
+ );
331
489
  }
332
490
 
333
491
  async getScheduledTask(workspaceId: string, taskId: string): Promise<ScheduledTask> {
334
- return await this.requestJson<ScheduledTask>("GET", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`);
492
+ return await this.requestJson<ScheduledTask>(
493
+ "GET",
494
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`,
495
+ );
335
496
  }
336
497
 
337
498
  // --- Events: replay, send, stream ----------------------------------------
@@ -347,20 +508,37 @@ export class OpenGeniClient {
347
508
  sessionId: string,
348
509
  options: { after?: number; before?: number; limit?: number; compact?: boolean } = {},
349
510
  ): Promise<SessionEvent[]> {
350
- return await this.requestJson<SessionEvent[]>("GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, undefined, {
351
- ...(options.after !== undefined ? { after: String(options.after) } : {}),
352
- ...(options.before !== undefined ? { before: String(options.before) } : {}),
353
- ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
354
- ...(options.compact ? { compact: "1" } : {}),
355
- });
511
+ return await this.requestJson<SessionEvent[]>(
512
+ "GET",
513
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`,
514
+ undefined,
515
+ {
516
+ ...(options.after !== undefined ? { after: String(options.after) } : {}),
517
+ ...(options.before !== undefined ? { before: String(options.before) } : {}),
518
+ ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
519
+ ...(options.compact ? { compact: "1" } : {}),
520
+ },
521
+ );
356
522
  }
357
523
 
358
524
  /** POST a user/control event to the session. Returns the accepted event. */
359
- async sendEvent(workspaceId: string, sessionId: string, event: ClientSessionEventInput): Promise<SessionEvent> {
360
- return await this.requestJson<SessionEvent>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, event);
525
+ async sendEvent(
526
+ workspaceId: string,
527
+ sessionId: string,
528
+ event: ClientSessionEventInput,
529
+ ): Promise<SessionEvent> {
530
+ return await this.requestJson<SessionEvent>(
531
+ "POST",
532
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`,
533
+ event,
534
+ );
361
535
  }
362
536
 
363
- async sendMessage(workspaceId: string, sessionId: string, message: string | SendMessageInput): Promise<SessionEvent> {
537
+ async sendMessage(
538
+ workspaceId: string,
539
+ sessionId: string,
540
+ message: string | SendMessageInput,
541
+ ): Promise<SessionEvent> {
364
542
  const input = typeof message === "string" ? { text: message } : message;
365
543
  const { clientEventId, ...payload } = input;
366
544
  return await this.sendEvent(workspaceId, sessionId, {
@@ -370,22 +548,28 @@ export class OpenGeniClient {
370
548
  });
371
549
  }
372
550
 
373
- async interrupt(
551
+ async pauseSession(
374
552
  workspaceId: string,
375
553
  sessionId: string,
376
554
  options: { reason?: string; clientEventId?: string } = {},
377
555
  ): Promise<SessionEvent> {
378
- return await this.sendEvent(workspaceId, sessionId, {
379
- type: "user.interrupt",
380
- ...(options.clientEventId !== undefined ? { clientEventId: options.clientEventId } : {}),
381
- payload: options.reason !== undefined ? { reason: options.reason } : {},
382
- });
556
+ return (
557
+ await this.controlSession(workspaceId, sessionId, {
558
+ mode: "pause",
559
+ ...options,
560
+ })
561
+ ).event;
383
562
  }
384
563
 
385
564
  async sendApprovalDecision(
386
565
  workspaceId: string,
387
566
  sessionId: string,
388
- decision: { approvalId: string; decision: "approve" | "reject"; message?: string; clientEventId?: string },
567
+ decision: {
568
+ approvalId: string;
569
+ decision: "approve" | "reject";
570
+ message?: string;
571
+ clientEventId?: string;
572
+ },
389
573
  ): Promise<SessionEvent> {
390
574
  const { clientEventId, ...payload } = decision;
391
575
  return await this.sendEvent(workspaceId, sessionId, {
@@ -411,8 +595,13 @@ export class OpenGeniClient {
411
595
  /** The transport `streamEvents` runs on; useful for custom streaming layers. */
412
596
  eventStreamTransport(workspaceId: string, sessionId: string): SessionEventStreamTransport {
413
597
  return {
414
- openStream: async (after, signal) => await this.openEventStream(workspaceId, sessionId, { after, ...(signal ? { signal } : {}) }),
415
- listEvents: async (after, limit) => await this.listEvents(workspaceId, sessionId, { after, limit }),
598
+ openStream: async (after, signal) =>
599
+ await this.openEventStream(workspaceId, sessionId, {
600
+ after,
601
+ ...(signal ? { signal } : {}),
602
+ }),
603
+ listEvents: async (after, limit) =>
604
+ await this.listEvents(workspaceId, sessionId, { after, limit }),
416
605
  };
417
606
  }
418
607
 
@@ -441,34 +630,77 @@ export class OpenGeniClient {
441
630
 
442
631
  // --- Turn queue ------------------------------------------------------------
443
632
 
444
- /** Edit a still-queued turn (prompt, model, resources, tools, ...). */
445
- async updateQueuedTurn(
633
+ async getQueue(workspaceId: string, sessionId: string): Promise<SessionQueueSnapshot> {
634
+ return await this.requestJson<SessionQueueSnapshot>(
635
+ "GET",
636
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`,
637
+ );
638
+ }
639
+
640
+ async cancelQueueItem(
446
641
  workspaceId: string,
447
642
  sessionId: string,
448
643
  turnId: string,
449
- update: UpdateSessionTurnRequest,
450
- ): Promise<SessionTurn> {
451
- return await this.requestJson<SessionTurn>(
452
- "PATCH",
453
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns/${turnId}`,
454
- update,
644
+ request: { expectedQueueVersion: number; expectedItemVersion: number; reason?: string },
645
+ ): Promise<SessionQueueMutationResponse> {
646
+ return await this.requestJson<SessionQueueMutationResponse>(
647
+ "POST",
648
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/cancel`,
649
+ request,
455
650
  );
456
651
  }
457
652
 
458
- /**
459
- * Reorder the queued turns. `turnIds` must all reference queued turns; the
460
- * server assigns positions in the given order and returns the queue.
461
- */
462
- async reorderQueuedTurns(workspaceId: string, sessionId: string, turnIds: string[]): Promise<SessionTurn[]> {
463
- return await this.requestJson<SessionTurn[]>(
653
+ async controlSession(
654
+ workspaceId: string,
655
+ sessionId: string,
656
+ request: {
657
+ mode: "pause" | "resume";
658
+ reason?: string;
659
+ clientEventId?: string;
660
+ expectedControlState?: "active" | "paused";
661
+ expectedControlGeneration?: number;
662
+ expectedWorkspaceInferenceGeneration?: number;
663
+ },
664
+ ): Promise<SessionControlResponse> {
665
+ return await this.requestJson<SessionControlResponse>(
666
+ "POST",
667
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/control`,
668
+ request,
669
+ );
670
+ }
671
+
672
+ async resumeSession(
673
+ workspaceId: string,
674
+ sessionId: string,
675
+ options: { reason?: string; clientEventId?: string } = {},
676
+ ): Promise<SessionControlResponse> {
677
+ return await this.controlSession(workspaceId, sessionId, { mode: "resume", ...options });
678
+ }
679
+
680
+ async setWorkspaceInferenceState(
681
+ workspaceId: string,
682
+ request: {
683
+ state: "active" | "paused";
684
+ reason: string;
685
+ clientEventId: string;
686
+ expectedState: "active" | "paused";
687
+ expectedGeneration: number;
688
+ exceptSessionIds?: string[];
689
+ },
690
+ ): Promise<WorkspaceInferenceControlResponse> {
691
+ return await this.requestJson<WorkspaceInferenceControlResponse>(
464
692
  "POST",
465
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns/reorder`,
466
- { turnIds },
693
+ `/v1/workspaces/${workspaceId}/inference-control`,
694
+ request,
467
695
  );
468
696
  }
469
697
 
470
698
  /** Cancel a queued turn before it is claimed. Returns the cancelled turn. */
471
- async deleteQueuedTurn(workspaceId: string, sessionId: string, turnId: string): Promise<SessionTurn> {
699
+ async deleteQueuedTurn(
700
+ workspaceId: string,
701
+ sessionId: string,
702
+ turnId: string,
703
+ ): Promise<SessionTurn> {
472
704
  return await this.requestJson<SessionTurn>(
473
705
  "DELETE",
474
706
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns/${turnId}`,
@@ -476,87 +708,54 @@ export class OpenGeniClient {
476
708
  }
477
709
 
478
710
  /**
479
- * Steer: deliver a message *now* instead of behind the queue. Sends the
480
- * message, promotes its queued turn to the front, and interrupts the
481
- * running turn so the session picks the steer turn up next. On a session
482
- * that is not running this degrades gracefully to a plain queued message.
483
- *
484
- * The steer turn is located by `triggerEventId` across ALL turns (retried
485
- * briefly in case the server is still materializing it) — not just the
486
- * queued ones, because the worker can claim the steer turn before it is
487
- * ever observed queued, and a claimed steer turn means the message is
488
- * already being delivered: interrupting then would cancel the very message
489
- * being steered. If the turn cannot be found while other turns are queued,
490
- * the interrupt is also skipped — stopping the running turn would otherwise
491
- * promote someone else's queued work over this message — and the call
492
- * degrades to a plain queued send (`interrupted: false`).
711
+ * Steer: atomically put this prompt at the head and supersede the current
712
+ * inference. The client performs one request and renders server order.
493
713
  */
494
714
  async steerMessage(
495
715
  workspaceId: string,
496
716
  sessionId: string,
497
717
  message: string | SendMessageInput,
498
718
  ): Promise<SteerMessageResult> {
499
- const accepted = await this.sendMessage(workspaceId, sessionId, message);
500
- let steerTurn: SessionTurn | null = null;
501
- let queued: SessionTurn[] = [];
502
- for (let attempt = 0; attempt < 4; attempt += 1) {
503
- if (attempt > 0) {
504
- await delay(150 * attempt);
505
- }
506
- const turns = await this.listTurns(workspaceId, sessionId);
507
- queued = turns
508
- .filter((turn) => turn.status === "queued")
509
- .sort((a, b) => a.position - b.position || a.createdAt.localeCompare(b.createdAt));
510
- // Match against every turn, whatever its status: a steer turn that is
511
- // already running/requires_action (or even finished) was claimed before
512
- // this listing — that is delivery, not grounds for an interrupt.
513
- steerTurn = turns.find((turn) => turn.triggerEventId === accepted.id) ?? null;
514
- if (steerTurn) {
515
- break;
516
- }
517
- }
518
- const steerTurnQueued = steerTurn?.status === "queued";
519
- if (steerTurn && steerTurnQueued && queued.length > 1) {
520
- const front = steerTurn;
521
- await this.reorderQueuedTurns(workspaceId, sessionId, [
522
- front.id,
523
- ...queued.filter((turn) => turn.id !== front.id).map((turn) => turn.id),
524
- ]);
525
- }
526
- // Interrupting is only safe when the next claim is provably this message:
527
- // either the steer turn sits queued (now at the front), or no turn
528
- // materialized yet AND nothing else is queued. A steer turn observed in
529
- // any non-queued state was already claimed — skip the interrupt.
530
- const canDeliverNext = steerTurnQueued || (steerTurn === null && queued.length === 0);
531
- const session = await this.getSession(workspaceId, sessionId);
532
- // If the previously running turn already finished and the session claimed
533
- // the steer turn itself, interrupting now would cancel the very message
534
- // being steered. `activeTurnId` is the claim check; the residual window
535
- // between this read and the interrupt landing is accepted (an interrupt
536
- // can never be atomic with a status read over HTTP).
537
- const steerTurnAlreadyActive = steerTurn !== null && session.activeTurnId === steerTurn.id;
538
- const interrupted = canDeliverNext
539
- && !steerTurnAlreadyActive
540
- && (session.status === "running" || session.status === "requires_action");
541
- if (interrupted) {
542
- await this.interrupt(workspaceId, sessionId, { reason: "steer" });
543
- }
544
- return { accepted, turn: steerTurn, interrupted };
719
+ const input = typeof message === "string" ? { text: message } : message;
720
+ return await this.requestJson<SteerMessageResult>(
721
+ "POST",
722
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/steer`,
723
+ input,
724
+ );
545
725
  }
546
726
 
547
727
  // --- Goals -------------------------------------------------------------------
548
728
 
549
729
  /** The session's goal. 404s when the session never had one. */
550
730
  async getGoal(workspaceId: string, sessionId: string): Promise<SessionGoal> {
551
- return await this.requestJson<SessionGoal>("GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`);
731
+ return await this.requestJson<SessionGoal>(
732
+ "GET",
733
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`,
734
+ );
735
+ }
736
+
737
+ async updateGoal(
738
+ workspaceId: string,
739
+ sessionId: string,
740
+ request: UpdateSessionGoalRequest,
741
+ ): Promise<SessionGoal> {
742
+ return await this.requestJson<SessionGoal>(
743
+ "PATCH",
744
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`,
745
+ request,
746
+ );
552
747
  }
553
748
 
554
- async updateGoal(workspaceId: string, sessionId: string, request: UpdateSessionGoalRequest): Promise<SessionGoal> {
555
- return await this.requestJson<SessionGoal>("PATCH", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`, request);
749
+ async deleteGoal(workspaceId: string, sessionId: string): Promise<void> {
750
+ await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`);
556
751
  }
557
752
 
558
753
  /** Pause the goal loop: the session stops self-continuing until resumed. */
559
- async pauseGoal(workspaceId: string, sessionId: string, options: { rationale?: string } = {}): Promise<SessionGoal> {
754
+ async pauseGoal(
755
+ workspaceId: string,
756
+ sessionId: string,
757
+ options: { rationale?: string } = {},
758
+ ): Promise<SessionGoal> {
560
759
  return await this.updateGoal(workspaceId, sessionId, {
561
760
  status: "paused",
562
761
  ...(options.rationale !== undefined ? { rationale: options.rationale } : {}),
@@ -578,17 +777,23 @@ export class OpenGeniClient {
578
777
  * context — the destructive intent is explicit on the wire.
579
778
  */
580
779
  async clearSessionContext(workspaceId: string, sessionId: string): Promise<void> {
581
- await this.requestVoid("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/context/clear`, { confirm: true });
780
+ await this.requestVoid(
781
+ "POST",
782
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/context/clear`,
783
+ { confirm: true },
784
+ );
582
785
  }
583
786
 
584
- /**
585
- * Trigger conversation compaction now. On the client-managed (Azure) path this
586
- * queues a forced compaction the worker honors before the next turn
587
- * (`status:"queued"`); on a server-managed provider or when compaction is off
588
- * it is a no-op (`status:"noop"`) with an explanatory message.
589
- */
590
- async compactSessionContext(workspaceId: string, sessionId: string): Promise<CompactSessionContextResult> {
591
- return await this.requestJson<CompactSessionContextResult>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/context/compact`, {});
787
+ /** Request one durable portable compaction at the next safe model boundary. */
788
+ async compactSessionContext(
789
+ workspaceId: string,
790
+ sessionId: string,
791
+ ): Promise<CompactSessionContextResult> {
792
+ return await this.requestJson<CompactSessionContextResult>(
793
+ "POST",
794
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/context/compact`,
795
+ {},
796
+ );
592
797
  }
593
798
 
594
799
  // --- Channel-A structured services (P4.4) ------------------------------------
@@ -597,79 +802,232 @@ export class OpenGeniClient {
597
802
  // notifications + the PTY output stream arrive on the existing event SSE.
598
803
 
599
804
  /** FileSystem: list a directory tree (feeds the Pierre file tree). */
600
- async fsList(workspaceId: string, sessionId: string, request: FsListRequest = {}): Promise<FsListResponse> {
601
- return await this.requestJson<FsListResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list`, request);
805
+ async fsList(
806
+ workspaceId: string,
807
+ sessionId: string,
808
+ request: FsListRequest = {},
809
+ ): Promise<FsListResponse> {
810
+ return await this.requestJson<FsListResponse>(
811
+ "POST",
812
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list`,
813
+ request,
814
+ );
602
815
  }
603
816
 
604
817
  /** FileSystem: read a file (text or base64; binary-safe, size-capped). */
605
- async fsRead(workspaceId: string, sessionId: string, request: FsReadRequest): Promise<FsReadResponse> {
606
- return await this.requestJson<FsReadResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/read`, request);
818
+ async fsRead(
819
+ workspaceId: string,
820
+ sessionId: string,
821
+ request: FsReadRequest,
822
+ ): Promise<FsReadResponse> {
823
+ return await this.requestJson<FsReadResponse>(
824
+ "POST",
825
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/read`,
826
+ request,
827
+ );
607
828
  }
608
829
 
609
830
  /** FileSystem: write a file (last-writer-wins; emits fs.changed). */
610
- async fsWrite(workspaceId: string, sessionId: string, request: FsWriteRequest): Promise<FsWriteResponse> {
611
- return await this.requestJson<FsWriteResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/write`, request);
831
+ async fsWrite(
832
+ workspaceId: string,
833
+ sessionId: string,
834
+ request: FsWriteRequest,
835
+ ): Promise<FsWriteResponse> {
836
+ return await this.requestJson<FsWriteResponse>(
837
+ "POST",
838
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/write`,
839
+ request,
840
+ );
612
841
  }
613
842
 
614
843
  /** FileSystem: delete a path (emits fs.changed). */
615
- async fsDelete(workspaceId: string, sessionId: string, request: FsDeleteRequest): Promise<FsDeleteResponse> {
616
- return await this.requestJson<FsDeleteResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/delete`, request);
844
+ async fsDelete(
845
+ workspaceId: string,
846
+ sessionId: string,
847
+ request: FsDeleteRequest,
848
+ ): Promise<FsDeleteResponse> {
849
+ return await this.requestJson<FsDeleteResponse>(
850
+ "POST",
851
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/delete`,
852
+ request,
853
+ );
617
854
  }
618
855
 
619
856
  /** FileSystem: move/rename a path (emits fs.changed; 409 if destination exists and overwrite is false). */
620
- async fsMove(workspaceId: string, sessionId: string, request: FsMoveRequest): Promise<FsMoveResponse> {
621
- return await this.requestJson<FsMoveResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/move`, request);
857
+ async fsMove(
858
+ workspaceId: string,
859
+ sessionId: string,
860
+ request: FsMoveRequest,
861
+ ): Promise<FsMoveResponse> {
862
+ return await this.requestJson<FsMoveResponse>(
863
+ "POST",
864
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/move`,
865
+ request,
866
+ );
622
867
  }
623
868
 
624
869
  /** FileSystem: create a directory (emits fs.changed; recursive defaults to true). */
625
- async fsMkdir(workspaceId: string, sessionId: string, request: FsMkdirRequest): Promise<FsMkdirResponse> {
626
- return await this.requestJson<FsMkdirResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/mkdir`, request);
870
+ async fsMkdir(
871
+ workspaceId: string,
872
+ sessionId: string,
873
+ request: FsMkdirRequest,
874
+ ): Promise<FsMkdirResponse> {
875
+ return await this.requestJson<FsMkdirResponse>(
876
+ "POST",
877
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/mkdir`,
878
+ request,
879
+ );
627
880
  }
628
881
 
629
882
  /** Git: working-tree/index status (the Pierre file-status feed). */
630
- async gitStatus(workspaceId: string, sessionId: string, request: GitStatusRequest = {}): Promise<GitStatusResponse> {
631
- return await this.requestJson<GitStatusResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/status`, request);
883
+ async gitStatus(
884
+ workspaceId: string,
885
+ sessionId: string,
886
+ request: GitStatusRequest = {},
887
+ ): Promise<GitStatusResponse> {
888
+ return await this.requestJson<GitStatusResponse>(
889
+ "POST",
890
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/status`,
891
+ request,
892
+ );
632
893
  }
633
894
 
634
895
  /** Git: structured diff hunks (the Pierre diff feed). */
635
- async gitDiff(workspaceId: string, sessionId: string, request: GitDiffRequest = {}): Promise<GitDiffResponse> {
636
- return await this.requestJson<GitDiffResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/diff`, request);
896
+ async gitDiff(
897
+ workspaceId: string,
898
+ sessionId: string,
899
+ request: GitDiffRequest = {},
900
+ ): Promise<GitDiffResponse> {
901
+ return await this.requestJson<GitDiffResponse>(
902
+ "POST",
903
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/diff`,
904
+ request,
905
+ );
637
906
  }
638
907
 
639
908
  /** Git: commit log. */
640
- async gitLog(workspaceId: string, sessionId: string, request: GitLogRequest = {}): Promise<GitLogResponse> {
641
- return await this.requestJson<GitLogResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/log`, request);
909
+ async gitLog(
910
+ workspaceId: string,
911
+ sessionId: string,
912
+ request: GitLogRequest = {},
913
+ ): Promise<GitLogResponse> {
914
+ return await this.requestJson<GitLogResponse>(
915
+ "POST",
916
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/log`,
917
+ request,
918
+ );
642
919
  }
643
920
 
644
921
  /** Git: show a commit (diff vs first parent) or fetch a raw blob at a ref. */
645
- async gitShow(workspaceId: string, sessionId: string, request: GitShowRequest): Promise<GitShowResponse> {
646
- return await this.requestJson<GitShowResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/show`, request);
922
+ async gitShow(
923
+ workspaceId: string,
924
+ sessionId: string,
925
+ request: GitShowRequest,
926
+ ): Promise<GitShowResponse> {
927
+ return await this.requestJson<GitShowResponse>(
928
+ "POST",
929
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/show`,
930
+ request,
931
+ );
932
+ }
933
+
934
+ /** Workspace capture: the latest turn-end snapshot of the session's workspace
935
+ * (tree + per-repo diff + file after-image refs), served from durable storage
936
+ * WITHOUT warming a machine — the workbench cold-paint source. Returns
937
+ * `{available:false}` when no capture exists yet (fall back to the live path). */
938
+ async getWorkspaceCapture(
939
+ workspaceId: string,
940
+ sessionId: string,
941
+ ): Promise<GetWorkspaceCaptureResponse> {
942
+ return await this.requestJson<GetWorkspaceCaptureResponse>(
943
+ "GET",
944
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture`,
945
+ );
946
+ }
947
+
948
+ /** Workspace capture: a single file's after-image from the capture (revision
949
+ * pins a specific one; omitted → latest). Content is inline for small files,
950
+ * else a short-TTL signed URL; a tooLarge file returns metadata only. */
951
+ async getWorkspaceCaptureFile(
952
+ workspaceId: string,
953
+ sessionId: string,
954
+ path: string,
955
+ revision?: number,
956
+ ): Promise<GetWorkspaceCaptureFileResponse> {
957
+ const query: Record<string, string> = { path };
958
+ if (revision !== undefined) query.revision = String(revision);
959
+ return await this.requestJson<GetWorkspaceCaptureFileResponse>(
960
+ "GET",
961
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture/file`,
962
+ undefined,
963
+ query,
964
+ );
647
965
  }
648
966
 
649
967
  /** Terminal: run a bounded command, returning buffered stdout/stderr inline. */
650
- async terminalExec(workspaceId: string, sessionId: string, request: TerminalExecRequest): Promise<TerminalExecResponse> {
651
- return await this.requestJson<TerminalExecResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/exec`, request);
968
+ async terminalExec(
969
+ workspaceId: string,
970
+ sessionId: string,
971
+ request: TerminalExecRequest,
972
+ ): Promise<TerminalExecResponse> {
973
+ return await this.requestJson<TerminalExecResponse>(
974
+ "POST",
975
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/exec`,
976
+ request,
977
+ );
652
978
  }
653
979
 
654
980
  /** Terminal: open an interactive PTY. Output streams on the event SSE as
655
981
  * terminal.pty.output.delta; drive it with terminalPtyWrite. */
656
- async terminalPtyOpen(workspaceId: string, sessionId: string, request: PtyOpenRequest = {}): Promise<PtyOpenResponse> {
657
- return await this.requestJson<PtyOpenResponse>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty`, request);
982
+ async terminalPtyOpen(
983
+ workspaceId: string,
984
+ sessionId: string,
985
+ request: PtyOpenRequest = {},
986
+ ): Promise<PtyOpenResponse> {
987
+ return await this.requestJson<PtyOpenResponse>(
988
+ "POST",
989
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty`,
990
+ request,
991
+ );
658
992
  }
659
993
 
660
994
  /** Terminal: send stdin to an open PTY (output rides A1). */
661
- async terminalPtyWrite(workspaceId: string, sessionId: string, request: PtyWriteRequest): Promise<void> {
662
- await this.requestVoid("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/write`, request);
995
+ async terminalPtyWrite(
996
+ workspaceId: string,
997
+ sessionId: string,
998
+ request: PtyWriteRequest,
999
+ ): Promise<void> {
1000
+ await this.requestVoid(
1001
+ "POST",
1002
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/write`,
1003
+ request,
1004
+ );
663
1005
  }
664
1006
 
665
1007
  /** Terminal: resize an open PTY. */
666
- async terminalPtyResize(workspaceId: string, sessionId: string, request: PtyResizeRequest): Promise<void> {
667
- await this.requestVoid("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/resize`, request);
1008
+ async terminalPtyResize(
1009
+ workspaceId: string,
1010
+ sessionId: string,
1011
+ request: PtyResizeRequest,
1012
+ ): Promise<void> {
1013
+ await this.requestVoid(
1014
+ "POST",
1015
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/resize`,
1016
+ request,
1017
+ );
668
1018
  }
669
1019
 
670
1020
  /** Terminal: close an open PTY (idempotent). */
671
- async terminalPtyClose(workspaceId: string, sessionId: string, request: PtyCloseRequest): Promise<void> {
672
- await this.requestVoid("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/close`, request);
1021
+ async terminalPtyClose(
1022
+ workspaceId: string,
1023
+ sessionId: string,
1024
+ request: PtyCloseRequest,
1025
+ ): Promise<void> {
1026
+ await this.requestVoid(
1027
+ "POST",
1028
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/close`,
1029
+ request,
1030
+ );
673
1031
  }
674
1032
 
675
1033
  // --- Stream surfacing: capability negotiation + viewer lifecycle (Phase 5) ---
@@ -684,19 +1042,29 @@ export class OpenGeniClient {
684
1042
  * liveness the client polls on while `cold`/`warming`. The desktop URL/token
685
1043
  * are minted in-process only when the box is warm AND the principal has
686
1044
  * acknowledged the un-redacted plane. */
687
- async getStreamCapabilities(workspaceId: string, sessionId: string): Promise<SessionCapabilities> {
1045
+ async getStreamCapabilities(
1046
+ workspaceId: string,
1047
+ sessionId: string,
1048
+ ): Promise<SessionCapabilities> {
688
1049
  return await this.requestJson<SessionCapabilities>(
689
- "GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`);
1050
+ "GET",
1051
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`,
1052
+ );
690
1053
  }
691
1054
 
692
1055
  /** Record the calling principal's acknowledgment of the un-redacted desktop
693
1056
  * pixel plane (and, when the box is shared, the shared-exposure disclosure).
694
1057
  * The desktop viewer-attach path returns 409 until this is recorded. */
695
1058
  async acknowledgeStream(
696
- workspaceId: string, sessionId: string, request: AcknowledgeStreamRequest = {},
1059
+ workspaceId: string,
1060
+ sessionId: string,
1061
+ request: AcknowledgeStreamRequest = {},
697
1062
  ): Promise<AcknowledgeStreamResponse> {
698
1063
  return await this.requestJson<AcknowledgeStreamResponse>(
699
- "POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities/acknowledge`, request);
1064
+ "POST",
1065
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities/acknowledge`,
1066
+ request,
1067
+ );
700
1068
  }
701
1069
 
702
1070
  /** Attach a viewer holder (refcounted liveness — keeps the box warm while
@@ -708,25 +1076,39 @@ export class OpenGeniClient {
708
1076
  * (`desktop` omitted/false) warms the box + mints the pty-ws terminal cell with
709
1077
  * NO consent gate. An omitted `viewerId` mints a fresh one. */
710
1078
  async attachViewer(
711
- workspaceId: string, sessionId: string, request: AttachViewerRequest = {},
1079
+ workspaceId: string,
1080
+ sessionId: string,
1081
+ request: AttachViewerRequest = {},
712
1082
  ): Promise<AttachViewerResponse> {
713
1083
  return await this.requestJson<AttachViewerResponse>(
714
- "POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers`, request);
1084
+ "POST",
1085
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers`,
1086
+ request,
1087
+ );
715
1088
  }
716
1089
 
717
1090
  /** Heartbeat a viewer holder (Channel-A app-level liveness). A closed laptop
718
1091
  * stops sending these → the reaper drops the holder within ~90s. Echoes
719
1092
  * `leaseEpoch` so a superseded epoch is rejected (`alive:false` → re-attach). */
720
1093
  async heartbeatViewer(
721
- workspaceId: string, sessionId: string, viewerId: string, request: ViewerHeartbeatRequest,
1094
+ workspaceId: string,
1095
+ sessionId: string,
1096
+ viewerId: string,
1097
+ request: ViewerHeartbeatRequest,
722
1098
  ): Promise<ViewerHeartbeatResponse> {
723
1099
  return await this.requestJson<ViewerHeartbeatResponse>(
724
- "POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers/${viewerId}/heartbeat`, request);
1100
+ "POST",
1101
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers/${viewerId}/heartbeat`,
1102
+ request,
1103
+ );
725
1104
  }
726
1105
 
727
1106
  /** Detach a viewer (delete this holder; idempotent delete-my-row). */
728
1107
  async detachViewer(workspaceId: string, sessionId: string, viewerId: string): Promise<void> {
729
- await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers/${viewerId}`);
1108
+ await this.requestVoid(
1109
+ "DELETE",
1110
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers/${viewerId}`,
1111
+ );
730
1112
  }
731
1113
 
732
1114
  // --- Access + workspaces -----------------------------------------------------
@@ -759,155 +1141,392 @@ export class OpenGeniClient {
759
1141
  return await this.requestJson<Workspace>("GET", `/v1/workspaces/${workspaceId}`);
760
1142
  }
761
1143
 
762
- async updateWorkspace(workspaceId: string, request: UpdateWorkspaceRequest): Promise<Workspace> {
763
- return await this.requestJson<Workspace>("PATCH", `/v1/workspaces/${workspaceId}`, request);
1144
+ async updateWorkspace(workspaceId: string, request: UpdateWorkspaceRequest): Promise<Workspace> {
1145
+ return await this.requestJson<Workspace>("PATCH", `/v1/workspaces/${workspaceId}`, request);
1146
+ }
1147
+
1148
+ /**
1149
+ * Delete a workspace and everything in it. Refused (409) for the account's
1150
+ * only workspace and while it still has a running session. Irreversible.
1151
+ */
1152
+ async deleteWorkspace(workspaceId: string): Promise<void> {
1153
+ await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}`);
1154
+ }
1155
+
1156
+ // --- Members ("People with access") -------------------------------------------
1157
+
1158
+ /** The workspace's members (user + api_key subjects). */
1159
+ async listWorkspaceMembers(workspaceId: string): Promise<WorkspaceMember[]> {
1160
+ const response = await this.requestJson<ListWorkspaceMembersResponse>(
1161
+ "GET",
1162
+ `/v1/workspaces/${workspaceId}/members`,
1163
+ );
1164
+ return response.members;
1165
+ }
1166
+
1167
+ /**
1168
+ * Add an already-registered user by email. 404s when no user with that email
1169
+ * exists (email invites for unknown users are deferred).
1170
+ */
1171
+ async addWorkspaceMember(
1172
+ workspaceId: string,
1173
+ request: AddWorkspaceMemberRequest,
1174
+ ): Promise<WorkspaceMember> {
1175
+ return await this.requestJson<WorkspaceMember>(
1176
+ "POST",
1177
+ `/v1/workspaces/${workspaceId}/members`,
1178
+ request,
1179
+ );
1180
+ }
1181
+
1182
+ async updateWorkspaceMember(
1183
+ workspaceId: string,
1184
+ subjectId: string,
1185
+ request: UpdateWorkspaceMemberRequest,
1186
+ ): Promise<WorkspaceMember> {
1187
+ return await this.requestJson<WorkspaceMember>(
1188
+ "PATCH",
1189
+ `/v1/workspaces/${workspaceId}/members/${encodeURIComponent(subjectId)}`,
1190
+ request,
1191
+ );
1192
+ }
1193
+
1194
+ /**
1195
+ * Remove a member. Refused (409) for your own membership and for the last
1196
+ * member who can still manage the workspace.
1197
+ */
1198
+ async removeWorkspaceMember(workspaceId: string, subjectId: string): Promise<void> {
1199
+ await this.requestVoid(
1200
+ "DELETE",
1201
+ `/v1/workspaces/${workspaceId}/members/${encodeURIComponent(subjectId)}`,
1202
+ );
1203
+ }
1204
+
1205
+ // --- Scheduled tasks (write + runs) -------------------------------------------
1206
+
1207
+ async createScheduledTask(
1208
+ workspaceId: string,
1209
+ request: CreateScheduledTaskRequest,
1210
+ ): Promise<ScheduledTask> {
1211
+ return await this.requestJson<ScheduledTask>(
1212
+ "POST",
1213
+ `/v1/workspaces/${workspaceId}/scheduled-tasks`,
1214
+ request,
1215
+ );
1216
+ }
1217
+
1218
+ async updateScheduledTask(
1219
+ workspaceId: string,
1220
+ taskId: string,
1221
+ request: UpdateScheduledTaskRequest,
1222
+ ): Promise<ScheduledTask> {
1223
+ return await this.requestJson<ScheduledTask>(
1224
+ "PATCH",
1225
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`,
1226
+ request,
1227
+ );
1228
+ }
1229
+
1230
+ async pauseScheduledTask(workspaceId: string, taskId: string): Promise<ScheduledTask> {
1231
+ return await this.requestJson<ScheduledTask>(
1232
+ "POST",
1233
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/pause`,
1234
+ );
1235
+ }
1236
+
1237
+ async resumeScheduledTask(workspaceId: string, taskId: string): Promise<ScheduledTask> {
1238
+ return await this.requestJson<ScheduledTask>(
1239
+ "POST",
1240
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/resume`,
1241
+ );
1242
+ }
1243
+
1244
+ /**
1245
+ * Fire the task immediately (manual trigger), independent of its schedule.
1246
+ * Pass a stable `triggerId` to make a retried trigger idempotent — the same
1247
+ * token charges once and starts one run. Omit it and each call is distinct.
1248
+ */
1249
+ async triggerScheduledTask(
1250
+ workspaceId: string,
1251
+ taskId: string,
1252
+ options: { triggerId?: string } = {},
1253
+ ): Promise<ScheduledTask> {
1254
+ return await this.requestJson<ScheduledTask>(
1255
+ "POST",
1256
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/trigger`,
1257
+ options.triggerId ? { triggerId: options.triggerId } : undefined,
1258
+ );
1259
+ }
1260
+
1261
+ async deleteScheduledTask(workspaceId: string, taskId: string): Promise<void> {
1262
+ await this.requestJson<unknown>(
1263
+ "DELETE",
1264
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`,
1265
+ );
1266
+ }
1267
+
1268
+ async listScheduledTaskRuns(
1269
+ workspaceId: string,
1270
+ taskId: string,
1271
+ options: { limit?: number } = {},
1272
+ ): Promise<ScheduledTaskRun[]> {
1273
+ return await this.requestJson<ScheduledTaskRun[]>(
1274
+ "GET",
1275
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/runs`,
1276
+ undefined,
1277
+ { ...(options.limit !== undefined ? { limit: String(options.limit) } : {}) },
1278
+ );
1279
+ }
1280
+
1281
+ // --- VariableSets --------------------------------------------------------------
1282
+ // Variable values are write-only: reads return name/version metadata only.
1283
+
1284
+ async listVariableSets(workspaceId: string): Promise<VariableSet[]> {
1285
+ return await this.requestJson<VariableSet[]>(
1286
+ "GET",
1287
+ `/v1/workspaces/${workspaceId}/variable-sets`,
1288
+ );
1289
+ }
1290
+
1291
+ async createVariableSet(
1292
+ workspaceId: string,
1293
+ request: CreateVariableSetRequest,
1294
+ ): Promise<VariableSet> {
1295
+ return await this.requestJson<VariableSet>(
1296
+ "POST",
1297
+ `/v1/workspaces/${workspaceId}/variable-sets`,
1298
+ request,
1299
+ );
1300
+ }
1301
+
1302
+ async getVariableSet(workspaceId: string, variableSetId: string): Promise<VariableSet> {
1303
+ return await this.requestJson<VariableSet>(
1304
+ "GET",
1305
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`,
1306
+ );
1307
+ }
1308
+
1309
+ async updateVariableSet(
1310
+ workspaceId: string,
1311
+ variableSetId: string,
1312
+ request: UpdateVariableSetRequest,
1313
+ ): Promise<VariableSet> {
1314
+ return await this.requestJson<VariableSet>(
1315
+ "PATCH",
1316
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`,
1317
+ request,
1318
+ );
1319
+ }
1320
+
1321
+ async deleteVariableSet(workspaceId: string, variableSetId: string): Promise<void> {
1322
+ await this.requestJson<unknown>(
1323
+ "DELETE",
1324
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`,
1325
+ );
1326
+ }
1327
+
1328
+ /** Create or rotate a variable. The value never comes back on any read. */
1329
+ async setVariableSetVariable(
1330
+ workspaceId: string,
1331
+ variableSetId: string,
1332
+ name: string,
1333
+ value: string,
1334
+ ): Promise<VariableSetVariableMetadata> {
1335
+ return await this.requestJson<VariableSetVariableMetadata>(
1336
+ "PUT",
1337
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}/variables/${encodeURIComponent(name)}`,
1338
+ { value },
1339
+ );
764
1340
  }
765
1341
 
766
- /**
767
- * Delete a workspace and everything in it. Refused (409) for the account's
768
- * only workspace and while it still has a running session. Irreversible.
769
- */
770
- async deleteWorkspace(workspaceId: string): Promise<void> {
771
- await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}`);
1342
+ async deleteVariableSetVariable(
1343
+ workspaceId: string,
1344
+ variableSetId: string,
1345
+ name: string,
1346
+ ): Promise<void> {
1347
+ await this.requestJson<unknown>(
1348
+ "DELETE",
1349
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}/variables/${encodeURIComponent(name)}`,
1350
+ );
772
1351
  }
773
1352
 
774
- // --- Members ("People with access") -------------------------------------------
1353
+ // --- Rigs ------------------------------------------------------------------
1354
+ // Workspace-scoped, versioned sandbox machine definitions. rigs:use gates read
1355
+ // + proposeRigChange; rigs:manage gates create / update / delete / activate.
775
1356
 
776
- /** The workspace's members (user + api_key subjects). */
777
- async listWorkspaceMembers(workspaceId: string): Promise<WorkspaceMember[]> {
778
- const response = await this.requestJson<ListWorkspaceMembersResponse>("GET", `/v1/workspaces/${workspaceId}/members`);
779
- return response.members;
1357
+ async listRigs(workspaceId: string): Promise<Rig[]> {
1358
+ return await this.requestJson<Rig[]>("GET", `/v1/workspaces/${workspaceId}/rigs`);
780
1359
  }
781
1360
 
782
- /**
783
- * Add an already-registered user by email. 404s when no user with that email
784
- * exists (email invites for unknown users are deferred).
785
- */
786
- async addWorkspaceMember(workspaceId: string, request: AddWorkspaceMemberRequest): Promise<WorkspaceMember> {
787
- return await this.requestJson<WorkspaceMember>("POST", `/v1/workspaces/${workspaceId}/members`, request);
1361
+ async createRig(workspaceId: string, request: CreateRigRequest): Promise<Rig> {
1362
+ return await this.requestJson<Rig>("POST", `/v1/workspaces/${workspaceId}/rigs`, request);
788
1363
  }
789
1364
 
790
- async updateWorkspaceMember(workspaceId: string, subjectId: string, request: UpdateWorkspaceMemberRequest): Promise<WorkspaceMember> {
791
- return await this.requestJson<WorkspaceMember>(
1365
+ async getRig(workspaceId: string, rigId: string): Promise<Rig> {
1366
+ return await this.requestJson<Rig>("GET", `/v1/workspaces/${workspaceId}/rigs/${rigId}`);
1367
+ }
1368
+
1369
+ async updateRig(workspaceId: string, rigId: string, request: UpdateRigRequest): Promise<Rig> {
1370
+ return await this.requestJson<Rig>(
792
1371
  "PATCH",
793
- `/v1/workspaces/${workspaceId}/members/${encodeURIComponent(subjectId)}`,
1372
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}`,
794
1373
  request,
795
1374
  );
796
1375
  }
797
1376
 
798
- /**
799
- * Remove a member. Refused (409) for your own membership and for the last
800
- * member who can still manage the workspace.
801
- */
802
- async removeWorkspaceMember(workspaceId: string, subjectId: string): Promise<void> {
803
- await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/members/${encodeURIComponent(subjectId)}`);
1377
+ async deleteRig(workspaceId: string, rigId: string): Promise<void> {
1378
+ await this.requestJson<unknown>("DELETE", `/v1/workspaces/${workspaceId}/rigs/${rigId}`);
804
1379
  }
805
1380
 
806
- // --- Scheduled tasks (write + runs) -------------------------------------------
1381
+ async listRigVersions(workspaceId: string, rigId: string): Promise<RigVersion[]> {
1382
+ return await this.requestJson<RigVersion[]>(
1383
+ "GET",
1384
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/versions`,
1385
+ );
1386
+ }
807
1387
 
808
- async createScheduledTask(workspaceId: string, request: CreateScheduledTaskRequest): Promise<ScheduledTask> {
809
- return await this.requestJson<ScheduledTask>("POST", `/v1/workspaces/${workspaceId}/scheduled-tasks`, request);
1388
+ /** Roll the active version to an existing one (rollback / promote-activate). */
1389
+ async activateRigVersion(
1390
+ workspaceId: string,
1391
+ rigId: string,
1392
+ versionId: string,
1393
+ ): Promise<RigVersion> {
1394
+ return await this.requestJson<RigVersion>(
1395
+ "POST",
1396
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/versions/${versionId}/activate`,
1397
+ );
810
1398
  }
811
1399
 
812
- async updateScheduledTask(workspaceId: string, taskId: string, request: UpdateScheduledTaskRequest): Promise<ScheduledTask> {
813
- return await this.requestJson<ScheduledTask>("PATCH", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`, request);
1400
+ async listRigChanges(workspaceId: string, rigId: string): Promise<RigChange[]> {
1401
+ return await this.requestJson<RigChange[]>(
1402
+ "GET",
1403
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes`,
1404
+ );
814
1405
  }
815
1406
 
816
- async pauseScheduledTask(workspaceId: string, taskId: string): Promise<ScheduledTask> {
817
- return await this.requestJson<ScheduledTask>("POST", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/pause`);
1407
+ /** Propose a change against the rig's active version (rigs:use). */
1408
+ async proposeRigChange(
1409
+ workspaceId: string,
1410
+ rigId: string,
1411
+ request: ProposeRigChangeRequest,
1412
+ ): Promise<RigChange> {
1413
+ return await this.requestJson<RigChange>(
1414
+ "POST",
1415
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes`,
1416
+ request,
1417
+ );
818
1418
  }
819
1419
 
820
- async resumeScheduledTask(workspaceId: string, taskId: string): Promise<ScheduledTask> {
821
- return await this.requestJson<ScheduledTask>("POST", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/resume`);
1420
+ async getRigChange(workspaceId: string, rigId: string, changeId: string): Promise<RigChange> {
1421
+ return await this.requestJson<RigChange>(
1422
+ "GET",
1423
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes/${changeId}`,
1424
+ );
822
1425
  }
823
1426
 
824
1427
  /**
825
- * Fire the task immediately (manual trigger), independent of its schedule.
826
- * Pass a stable `triggerId` to make a retried trigger idempotent — the same
827
- * token charges once and starts one run. Omit it and each call is distinct.
1428
+ * Re-run verification for a change (rigs:use). Verification is asynchronous:
1429
+ * this returns the change immediately with status `verifying`; poll
1430
+ * `getRigChange`/`listRigChanges` for the terminal outcome + logs.
828
1431
  */
829
- async triggerScheduledTask(workspaceId: string, taskId: string, options: { triggerId?: string } = {}): Promise<ScheduledTask> {
830
- return await this.requestJson<ScheduledTask>(
1432
+ async verifyRigChange(workspaceId: string, rigId: string, changeId: string): Promise<RigChange> {
1433
+ return await this.requestJson<RigChange>(
831
1434
  "POST",
832
- `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/trigger`,
833
- options.triggerId ? { triggerId: options.triggerId } : undefined,
1435
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes/${changeId}/verify`,
834
1436
  );
835
1437
  }
836
1438
 
837
- async deleteScheduledTask(workspaceId: string, taskId: string): Promise<void> {
838
- await this.requestJson<unknown>("DELETE", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`);
839
- }
840
-
841
- async listScheduledTaskRuns(
1439
+ /**
1440
+ * Promote a verified `definition_edit` change into a new active rig version
1441
+ * (rigs:manage). Only valid once the change's verification passed; returns the
1442
+ * newly minted version.
1443
+ */
1444
+ async promoteRigChange(
842
1445
  workspaceId: string,
843
- taskId: string,
844
- options: { limit?: number } = {},
845
- ): Promise<ScheduledTaskRun[]> {
846
- return await this.requestJson<ScheduledTaskRun[]>(
847
- "GET",
848
- `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/runs`,
849
- undefined,
850
- { ...(options.limit !== undefined ? { limit: String(options.limit) } : {}) },
1446
+ rigId: string,
1447
+ changeId: string,
1448
+ ): Promise<RigVersion> {
1449
+ return await this.requestJson<RigVersion>(
1450
+ "POST",
1451
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes/${changeId}/promote`,
851
1452
  );
852
1453
  }
853
1454
 
854
- // --- Environments --------------------------------------------------------------
855
- // Variable values are write-only: reads return name/version metadata only.
1455
+ /**
1456
+ * Re-run the active version's checks in a clean throwaway sandbox (rigs:use).
1457
+ * Asynchronous — returns the version id being verified; the outcome lands on
1458
+ * the version's audit trail.
1459
+ */
1460
+ async verifyRig(workspaceId: string, rigId: string): Promise<{ ok: boolean; versionId: string }> {
1461
+ return await this.requestJson<{ ok: boolean; versionId: string }>(
1462
+ "POST",
1463
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/verify`,
1464
+ );
1465
+ }
856
1466
 
857
- async listEnvironments(workspaceId: string): Promise<WorkspaceEnvironment[]> {
858
- return await this.requestJson<WorkspaceEnvironment[]>("GET", `/v1/workspaces/${workspaceId}/environments`);
1467
+ /** @deprecated use listVariableSets */
1468
+ async listEnvironments(workspaceId: string): Promise<VariableSet[]> {
1469
+ return await this.listVariableSets(workspaceId);
859
1470
  }
860
1471
 
861
- async createEnvironment(workspaceId: string, request: CreateWorkspaceEnvironmentRequest): Promise<WorkspaceEnvironment> {
862
- return await this.requestJson<WorkspaceEnvironment>("POST", `/v1/workspaces/${workspaceId}/environments`, request);
1472
+ /** @deprecated use createVariableSet */
1473
+ async createEnvironment(
1474
+ workspaceId: string,
1475
+ request: CreateVariableSetRequest,
1476
+ ): Promise<VariableSet> {
1477
+ return await this.createVariableSet(workspaceId, request);
863
1478
  }
864
1479
 
865
- async getEnvironment(workspaceId: string, environmentId: string): Promise<WorkspaceEnvironment> {
866
- return await this.requestJson<WorkspaceEnvironment>("GET", `/v1/workspaces/${workspaceId}/environments/${environmentId}`);
1480
+ /** @deprecated use getVariableSet */
1481
+ async getEnvironment(workspaceId: string, environmentId: string): Promise<VariableSet> {
1482
+ return await this.getVariableSet(workspaceId, environmentId);
867
1483
  }
868
1484
 
1485
+ /** @deprecated use updateVariableSet */
869
1486
  async updateEnvironment(
870
1487
  workspaceId: string,
871
1488
  environmentId: string,
872
- request: UpdateWorkspaceEnvironmentRequest,
873
- ): Promise<WorkspaceEnvironment> {
874
- return await this.requestJson<WorkspaceEnvironment>(
875
- "PATCH",
876
- `/v1/workspaces/${workspaceId}/environments/${environmentId}`,
877
- request,
878
- );
1489
+ request: UpdateVariableSetRequest,
1490
+ ): Promise<VariableSet> {
1491
+ return await this.updateVariableSet(workspaceId, environmentId, request);
879
1492
  }
880
1493
 
1494
+ /** @deprecated use deleteVariableSet */
881
1495
  async deleteEnvironment(workspaceId: string, environmentId: string): Promise<void> {
882
- await this.requestJson<unknown>("DELETE", `/v1/workspaces/${workspaceId}/environments/${environmentId}`);
1496
+ await this.deleteVariableSet(workspaceId, environmentId);
883
1497
  }
884
1498
 
885
- /** Create or rotate a variable. The value never comes back on any read. */
1499
+ /** @deprecated use setVariableSetVariable */
886
1500
  async setEnvironmentVariable(
887
1501
  workspaceId: string,
888
1502
  environmentId: string,
889
1503
  name: string,
890
1504
  value: string,
891
- ): Promise<WorkspaceEnvironmentVariableMetadata> {
892
- return await this.requestJson<WorkspaceEnvironmentVariableMetadata>(
893
- "PUT",
894
- `/v1/workspaces/${workspaceId}/environments/${environmentId}/variables/${encodeURIComponent(name)}`,
895
- { value },
896
- );
1505
+ ): Promise<VariableSetVariableMetadata> {
1506
+ return await this.setVariableSetVariable(workspaceId, environmentId, name, value);
897
1507
  }
898
1508
 
899
- async deleteEnvironmentVariable(workspaceId: string, environmentId: string, name: string): Promise<void> {
900
- await this.requestJson<unknown>(
901
- "DELETE",
902
- `/v1/workspaces/${workspaceId}/environments/${environmentId}/variables/${encodeURIComponent(name)}`,
903
- );
1509
+ /** @deprecated use deleteVariableSetVariable */
1510
+ async deleteEnvironmentVariable(
1511
+ workspaceId: string,
1512
+ environmentId: string,
1513
+ name: string,
1514
+ ): Promise<void> {
1515
+ await this.deleteVariableSetVariable(workspaceId, environmentId, name);
904
1516
  }
905
1517
 
906
1518
  // --- Files -----------------------------------------------------------------------
907
1519
 
908
1520
  /** Step 1 of the upload flow: returns the pre-signed PUT target. */
909
- async beginFileUpload(workspaceId: string, request: CreateFileUploadRequest): Promise<CreateFileUploadResponse> {
910
- return await this.requestJson<CreateFileUploadResponse>("POST", `/v1/workspaces/${workspaceId}/files/uploads`, request);
1521
+ async beginFileUpload(
1522
+ workspaceId: string,
1523
+ request: CreateFileUploadRequest,
1524
+ ): Promise<CreateFileUploadResponse> {
1525
+ return await this.requestJson<CreateFileUploadResponse>(
1526
+ "POST",
1527
+ `/v1/workspaces/${workspaceId}/files/uploads`,
1528
+ request,
1529
+ );
911
1530
  }
912
1531
 
913
1532
  /** Step 3 of the upload flow: server verifies the object and marks it ready. */
@@ -927,12 +1546,14 @@ export class OpenGeniClient {
927
1546
  async uploadFile(workspaceId: string, input: UploadFileInput): Promise<FileAsset> {
928
1547
  // Copy Uint8Array views into a Blob so byte offsets/shared buffers can't
929
1548
  // leak surrounding bytes into the PUT body.
930
- const body: Blob | ArrayBuffer | string = input.data instanceof Uint8Array
931
- ? new Blob([input.data.slice()])
932
- : input.data;
933
- const sizeBytes = typeof body === "string"
934
- ? new TextEncoder().encode(body).byteLength
935
- : body instanceof Blob ? body.size : body.byteLength;
1549
+ const body: Blob | ArrayBuffer | string =
1550
+ input.data instanceof Uint8Array ? new Blob([input.data.slice()]) : input.data;
1551
+ const sizeBytes =
1552
+ typeof body === "string"
1553
+ ? new TextEncoder().encode(body).byteLength
1554
+ : body instanceof Blob
1555
+ ? body.size
1556
+ : body.byteLength;
936
1557
  const upload = await this.beginFileUpload(workspaceId, {
937
1558
  filename: input.filename,
938
1559
  contentType: input.contentType,
@@ -957,39 +1578,76 @@ export class OpenGeniClient {
957
1578
  }
958
1579
 
959
1580
  async getFile(workspaceId: string, fileId: string): Promise<FileAsset> {
960
- return await this.requestJson<FileAsset>("GET", `/v1/workspaces/${workspaceId}/files/${fileId}`);
1581
+ return await this.requestJson<FileAsset>(
1582
+ "GET",
1583
+ `/v1/workspaces/${workspaceId}/files/${fileId}`,
1584
+ );
961
1585
  }
962
1586
 
963
1587
  /** Mint a short-lived signed download URL for a ready file. */
964
- async createFileDownloadUrl(workspaceId: string, fileId: string): Promise<FileDownloadUrlResponse> {
965
- return await this.requestJson<FileDownloadUrlResponse>("POST", `/v1/workspaces/${workspaceId}/files/${fileId}/download-url`);
1588
+ async createFileDownloadUrl(
1589
+ workspaceId: string,
1590
+ fileId: string,
1591
+ ): Promise<FileDownloadUrlResponse> {
1592
+ return await this.requestJson<FileDownloadUrlResponse>(
1593
+ "POST",
1594
+ `/v1/workspaces/${workspaceId}/files/${fileId}/download-url`,
1595
+ );
966
1596
  }
967
1597
 
968
1598
  // --- Documents ----------------------------------------------------------------------
969
1599
 
970
- async createDocumentBase(workspaceId: string, request: CreateDocumentBaseRequest): Promise<DocumentBase> {
971
- return await this.requestJson<DocumentBase>("POST", `/v1/workspaces/${workspaceId}/document-bases`, request);
1600
+ async createDocumentBase(
1601
+ workspaceId: string,
1602
+ request: CreateDocumentBaseRequest,
1603
+ ): Promise<DocumentBase> {
1604
+ return await this.requestJson<DocumentBase>(
1605
+ "POST",
1606
+ `/v1/workspaces/${workspaceId}/document-bases`,
1607
+ request,
1608
+ );
972
1609
  }
973
1610
 
974
1611
  async listDocumentBases(workspaceId: string): Promise<DocumentBase[]> {
975
- return await this.requestJson<DocumentBase[]>("GET", `/v1/workspaces/${workspaceId}/document-bases`);
1612
+ return await this.requestJson<DocumentBase[]>(
1613
+ "GET",
1614
+ `/v1/workspaces/${workspaceId}/document-bases`,
1615
+ );
976
1616
  }
977
1617
 
978
1618
  async getDocumentBase(workspaceId: string, baseId: string): Promise<DocumentBase> {
979
- return await this.requestJson<DocumentBase>("GET", `/v1/workspaces/${workspaceId}/document-bases/${baseId}`);
1619
+ return await this.requestJson<DocumentBase>(
1620
+ "GET",
1621
+ `/v1/workspaces/${workspaceId}/document-bases/${baseId}`,
1622
+ );
980
1623
  }
981
1624
 
982
1625
  /** Index an uploaded file into the base. The file must be `ready`. */
983
- async addDocument(workspaceId: string, baseId: string, request: AddDocumentRequest): Promise<Document> {
984
- return await this.requestJson<Document>("POST", `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`, request);
1626
+ async addDocument(
1627
+ workspaceId: string,
1628
+ baseId: string,
1629
+ request: AddDocumentRequest,
1630
+ ): Promise<Document> {
1631
+ return await this.requestJson<Document>(
1632
+ "POST",
1633
+ `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`,
1634
+ request,
1635
+ );
985
1636
  }
986
1637
 
987
1638
  async listDocuments(workspaceId: string, baseId: string): Promise<Document[]> {
988
- return await this.requestJson<Document[]>("GET", `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`);
1639
+ return await this.requestJson<Document[]>(
1640
+ "GET",
1641
+ `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`,
1642
+ );
989
1643
  }
990
1644
 
991
1645
  /** Retry indexing for a failed document. */
992
- async reindexDocument(workspaceId: string, baseId: string, documentId: string): Promise<Document> {
1646
+ async reindexDocument(
1647
+ workspaceId: string,
1648
+ baseId: string,
1649
+ documentId: string,
1650
+ ): Promise<Document> {
993
1651
  return await this.requestJson<Document>(
994
1652
  "POST",
995
1653
  `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents/${documentId}/reindex`,
@@ -1023,10 +1681,17 @@ export class OpenGeniClient {
1023
1681
  workspaceId: string,
1024
1682
  request: DocumentSearchRequest,
1025
1683
  ): Promise<DocumentSearchResponse> {
1026
- return await this.requestJson<DocumentSearchResponse>("POST", `/v1/workspaces/${workspaceId}/knowledge/search`, request);
1684
+ return await this.requestJson<DocumentSearchResponse>(
1685
+ "POST",
1686
+ `/v1/workspaces/${workspaceId}/knowledge/search`,
1687
+ request,
1688
+ );
1027
1689
  }
1028
1690
 
1029
- async listKnowledgeMemories(workspaceId: string, request: KnowledgeMemorySearchRequest = {}): Promise<KnowledgeMemory[]> {
1691
+ async listKnowledgeMemories(
1692
+ workspaceId: string,
1693
+ request: KnowledgeMemorySearchRequest = {},
1694
+ ): Promise<KnowledgeMemory[]> {
1030
1695
  const params = new URLSearchParams();
1031
1696
  if (request.query) params.set("query", request.query);
1032
1697
  if (request.status) params.set("status", request.status);
@@ -1034,19 +1699,75 @@ export class OpenGeniClient {
1034
1699
  if (request.scope) params.set("scope", request.scope);
1035
1700
  if (request.limit) params.set("limit", String(request.limit));
1036
1701
  const query = params.toString();
1037
- return await this.requestJson<KnowledgeMemory[]>("GET", `/v1/workspaces/${workspaceId}/knowledge/memories${query ? `?${query}` : ""}`);
1702
+ return await this.requestJson<KnowledgeMemory[]>(
1703
+ "GET",
1704
+ `/v1/workspaces/${workspaceId}/knowledge/memories${query ? `?${query}` : ""}`,
1705
+ );
1038
1706
  }
1039
1707
 
1040
1708
  async getKnowledgeMemory(workspaceId: string, memoryId: string): Promise<KnowledgeMemory> {
1041
- return await this.requestJson<KnowledgeMemory>("GET", `/v1/workspaces/${workspaceId}/knowledge/memories/${memoryId}`);
1709
+ return await this.requestJson<KnowledgeMemory>(
1710
+ "GET",
1711
+ `/v1/workspaces/${workspaceId}/knowledge/memories/${memoryId}`,
1712
+ );
1713
+ }
1714
+
1715
+ async createKnowledgeMemory(
1716
+ workspaceId: string,
1717
+ request: CreateKnowledgeMemoryRequest,
1718
+ ): Promise<KnowledgeMemory> {
1719
+ return await this.requestJson<KnowledgeMemory>(
1720
+ "POST",
1721
+ `/v1/workspaces/${workspaceId}/knowledge/memories`,
1722
+ request,
1723
+ );
1724
+ }
1725
+
1726
+ async updateKnowledgeMemory(
1727
+ workspaceId: string,
1728
+ memoryId: string,
1729
+ request: UpdateKnowledgeMemoryRequest,
1730
+ ): Promise<KnowledgeMemory> {
1731
+ return await this.requestJson<KnowledgeMemory>(
1732
+ "PATCH",
1733
+ `/v1/workspaces/${workspaceId}/knowledge/memories/${memoryId}`,
1734
+ request,
1735
+ );
1736
+ }
1737
+
1738
+ /** Hybrid (semantic + keyword) search over the workspace's agent-visible memory. */
1739
+ async searchWorkspaceMemories(
1740
+ workspaceId: string,
1741
+ request: WorkspaceMemorySearchRequest,
1742
+ ): Promise<WorkspaceMemorySearchResponse> {
1743
+ return await this.requestJson<WorkspaceMemorySearchResponse>(
1744
+ "POST",
1745
+ `/v1/workspaces/${workspaceId}/knowledge/memories/search`,
1746
+ request,
1747
+ );
1042
1748
  }
1043
1749
 
1044
- async createKnowledgeMemory(workspaceId: string, request: CreateKnowledgeMemoryRequest): Promise<KnowledgeMemory> {
1045
- return await this.requestJson<KnowledgeMemory>("POST", `/v1/workspaces/${workspaceId}/knowledge/memories`, request);
1750
+ /** Deep-merge a settings patch into the workspace (preserves unknown keys). */
1751
+ async updateWorkspaceSettings(
1752
+ workspaceId: string,
1753
+ request: UpdateWorkspaceSettingsRequest,
1754
+ ): Promise<Workspace> {
1755
+ return await this.requestJson<Workspace>(
1756
+ "PATCH",
1757
+ `/v1/workspaces/${workspaceId}/settings`,
1758
+ request,
1759
+ );
1046
1760
  }
1047
1761
 
1048
- async updateKnowledgeMemory(workspaceId: string, memoryId: string, request: UpdateKnowledgeMemoryRequest): Promise<KnowledgeMemory> {
1049
- return await this.requestJson<KnowledgeMemory>("PATCH", `/v1/workspaces/${workspaceId}/knowledge/memories/${memoryId}`, request);
1762
+ async setWorkspaceDefaultRig(
1763
+ workspaceId: string,
1764
+ request: SetWorkspaceDefaultRigRequest,
1765
+ ): Promise<Workspace> {
1766
+ return await this.requestJson<Workspace>(
1767
+ "PUT",
1768
+ `/v1/workspaces/${workspaceId}/default-rig`,
1769
+ request,
1770
+ );
1050
1771
  }
1051
1772
 
1052
1773
  // --- Capability packs ------------------------------------------------------------------
@@ -1057,15 +1778,29 @@ export class OpenGeniClient {
1057
1778
  }
1058
1779
 
1059
1780
  /** Register (or replace) a workspace-scoped pack from a manifest. */
1060
- async registerPack(workspaceId: string, manifest: RegisterCapabilityPackRequest): Promise<WorkspaceRegisteredPack> {
1061
- return await this.requestJson<WorkspaceRegisteredPack>("POST", `/v1/workspaces/${workspaceId}/packs`, manifest);
1781
+ async registerPack(
1782
+ workspaceId: string,
1783
+ manifest: RegisterCapabilityPackRequest,
1784
+ ): Promise<WorkspaceRegisteredPack> {
1785
+ return await this.requestJson<WorkspaceRegisteredPack>(
1786
+ "POST",
1787
+ `/v1/workspaces/${workspaceId}/packs`,
1788
+ manifest,
1789
+ );
1062
1790
  }
1063
1791
 
1064
1792
  async getPack(workspaceId: string, packId: string): Promise<GetPackResponse> {
1065
- return await this.requestJson<GetPackResponse>("GET", `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`);
1793
+ return await this.requestJson<GetPackResponse>(
1794
+ "GET",
1795
+ `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`,
1796
+ );
1066
1797
  }
1067
1798
 
1068
- async enablePack(workspaceId: string, packId: string, request: EnablePackRequest = {}): Promise<PackInstallation> {
1799
+ async enablePack(
1800
+ workspaceId: string,
1801
+ packId: string,
1802
+ request: EnablePackRequest = {},
1803
+ ): Promise<PackInstallation> {
1069
1804
  return await this.requestJson<PackInstallation>(
1070
1805
  "POST",
1071
1806
  `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}/enable`,
@@ -1075,22 +1810,38 @@ export class OpenGeniClient {
1075
1810
 
1076
1811
  /** Unregister a workspace-scoped pack (built-in packs cannot be deleted). */
1077
1812
  async deletePack(workspaceId: string, packId: string): Promise<void> {
1078
- await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`);
1813
+ await this.requestVoid(
1814
+ "DELETE",
1815
+ `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`,
1816
+ );
1079
1817
  }
1080
1818
 
1081
1819
  async listPackInstallations(workspaceId: string): Promise<PackInstallation[]> {
1082
- return await this.requestJson<PackInstallation[]>("GET", `/v1/workspaces/${workspaceId}/packs/installations`);
1820
+ return await this.requestJson<PackInstallation[]>(
1821
+ "GET",
1822
+ `/v1/workspaces/${workspaceId}/packs/installations`,
1823
+ );
1083
1824
  }
1084
1825
 
1085
1826
  // --- Capabilities -------------------------------------------------------------------------
1086
1827
 
1087
1828
  async listCapabilities(workspaceId: string): Promise<CapabilityCatalogResponse> {
1088
- return await this.requestJson<CapabilityCatalogResponse>("GET", `/v1/workspaces/${workspaceId}/capabilities`);
1829
+ return await this.requestJson<CapabilityCatalogResponse>(
1830
+ "GET",
1831
+ `/v1/workspaces/${workspaceId}/capabilities`,
1832
+ );
1089
1833
  }
1090
1834
 
1091
1835
  /** Add a manual capability catalog item (e.g. a remote MCP server). */
1092
- async createCapability(workspaceId: string, request: CreateCapabilityCatalogItemRequest): Promise<CapabilityCatalogItem> {
1093
- return await this.requestJson<CapabilityCatalogItem>("POST", `/v1/workspaces/${workspaceId}/capabilities`, request);
1836
+ async createCapability(
1837
+ workspaceId: string,
1838
+ request: CreateCapabilityCatalogItemRequest,
1839
+ ): Promise<CapabilityCatalogItem> {
1840
+ return await this.requestJson<CapabilityCatalogItem>(
1841
+ "POST",
1842
+ `/v1/workspaces/${workspaceId}/capabilities`,
1843
+ request,
1844
+ );
1094
1845
  }
1095
1846
 
1096
1847
  async enableCapability(
@@ -1105,7 +1856,10 @@ export class OpenGeniClient {
1105
1856
  );
1106
1857
  }
1107
1858
 
1108
- async disableCapability(workspaceId: string, capabilityId: string): Promise<CapabilityInstallation> {
1859
+ async disableCapability(
1860
+ workspaceId: string,
1861
+ capabilityId: string,
1862
+ ): Promise<CapabilityInstallation> {
1109
1863
  return await this.requestJson<CapabilityInstallation>(
1110
1864
  "POST",
1111
1865
  `/v1/workspaces/${workspaceId}/capabilities/${encodeURIComponent(capabilityId)}/disable`,
@@ -1128,6 +1882,66 @@ export class OpenGeniClient {
1128
1882
  );
1129
1883
  }
1130
1884
 
1885
+ // --- Connections -------------------------------------------------------------------------------
1886
+
1887
+ async listConnections(workspaceId: string): Promise<ConnectionMetadata[]> {
1888
+ const response = await this.requestJson<ListConnectionsResponse>(
1889
+ "GET",
1890
+ `/v1/workspaces/${workspaceId}/connections`,
1891
+ );
1892
+ return response.connections;
1893
+ }
1894
+
1895
+ async createConnection(
1896
+ workspaceId: string,
1897
+ request: CreateConnectionRequest,
1898
+ ): Promise<ConnectionMetadata> {
1899
+ const response = await this.requestJson<ConnectionResponse>(
1900
+ "POST",
1901
+ `/v1/workspaces/${workspaceId}/connections`,
1902
+ request,
1903
+ );
1904
+ return response.connection;
1905
+ }
1906
+
1907
+ async updateConnection(
1908
+ workspaceId: string,
1909
+ connectionId: string,
1910
+ request: UpdateConnectionRequest,
1911
+ ): Promise<ConnectionMetadata> {
1912
+ const response = await this.requestJson<ConnectionResponse>(
1913
+ "PATCH",
1914
+ `/v1/workspaces/${workspaceId}/connections/${connectionId}`,
1915
+ request,
1916
+ );
1917
+ return response.connection;
1918
+ }
1919
+
1920
+ async deleteConnection(workspaceId: string, connectionId: string): Promise<ConnectionMetadata> {
1921
+ const response = await this.requestJson<ConnectionResponse>(
1922
+ "DELETE",
1923
+ `/v1/workspaces/${workspaceId}/connections/${connectionId}`,
1924
+ );
1925
+ return response.connection;
1926
+ }
1927
+
1928
+ /** Start an OAuth connection flow; redirect the user to the returned `authorizationUrl`. */
1929
+ async startConnectionOAuth(
1930
+ workspaceId: string,
1931
+ request: OAuthStartRequest,
1932
+ ): Promise<OAuthStartResponse> {
1933
+ return await this.requestJson<OAuthStartResponse>(
1934
+ "POST",
1935
+ `/v1/workspaces/${workspaceId}/connections/oauth/start`,
1936
+ request,
1937
+ );
1938
+ }
1939
+
1940
+ /** Public, immutably-cached URL for a catalog item's logo, or null when the item has none. */
1941
+ catalogAssetUrl(logoAssetPath: string | null): string | null {
1942
+ return logoAssetPath ? `${this.baseUrl}/v1/${logoAssetPath}` : null;
1943
+ }
1944
+
1131
1945
  // --- GitHub ----------------------------------------------------------------------------------
1132
1946
 
1133
1947
  /** GitHub App configuration status + a signed install URL when configured. */
@@ -1145,12 +1959,18 @@ export class OpenGeniClient {
1145
1959
  }
1146
1960
 
1147
1961
  async listGitHubRepositories(workspaceId: string): Promise<GitHubRepositoriesResponse> {
1148
- return await this.requestJson<GitHubRepositoriesResponse>("GET", `/v1/workspaces/${workspaceId}/github/repositories`);
1962
+ return await this.requestJson<GitHubRepositoriesResponse>(
1963
+ "GET",
1964
+ `/v1/workspaces/${workspaceId}/github/repositories`,
1965
+ );
1149
1966
  }
1150
1967
 
1151
1968
  /** Re-sync the installation's repository list from GitHub. */
1152
1969
  async syncGitHubRepositories(workspaceId: string): Promise<GitHubRepositoriesResponse> {
1153
- return await this.requestJson<GitHubRepositoriesResponse>("POST", `/v1/workspaces/${workspaceId}/github/repositories/sync`);
1970
+ return await this.requestJson<GitHubRepositoriesResponse>(
1971
+ "POST",
1972
+ `/v1/workspaces/${workspaceId}/github/repositories/sync`,
1973
+ );
1154
1974
  }
1155
1975
 
1156
1976
  /** Build a GitHub App manifest + the GitHub URL to submit it to. */
@@ -1168,18 +1988,31 @@ export class OpenGeniClient {
1168
1988
  // --- API keys ----------------------------------------------------------------------------------
1169
1989
 
1170
1990
  async listApiKeys(workspaceId: string): Promise<ApiKey[]> {
1171
- const response = await this.requestJson<ListApiKeysResponse>("GET", `/v1/workspaces/${workspaceId}/api-keys`);
1991
+ const response = await this.requestJson<ListApiKeysResponse>(
1992
+ "GET",
1993
+ `/v1/workspaces/${workspaceId}/api-keys`,
1994
+ );
1172
1995
  return response.apiKeys;
1173
1996
  }
1174
1997
 
1175
1998
  /** The returned `token` is shown once; only its prefix is stored. */
1176
- async createApiKey(workspaceId: string, request: CreateApiKeyRequest): Promise<CreateApiKeyResponse> {
1177
- return await this.requestJson<CreateApiKeyResponse>("POST", `/v1/workspaces/${workspaceId}/api-keys`, request);
1999
+ async createApiKey(
2000
+ workspaceId: string,
2001
+ request: CreateApiKeyRequest,
2002
+ ): Promise<CreateApiKeyResponse> {
2003
+ return await this.requestJson<CreateApiKeyResponse>(
2004
+ "POST",
2005
+ `/v1/workspaces/${workspaceId}/api-keys`,
2006
+ request,
2007
+ );
1178
2008
  }
1179
2009
 
1180
2010
  /** Revoke an API key. Returns the revoked key. */
1181
2011
  async deleteApiKey(workspaceId: string, apiKeyId: string): Promise<ApiKey> {
1182
- return await this.requestJson<ApiKey>("DELETE", `/v1/workspaces/${workspaceId}/api-keys/${apiKeyId}`);
2012
+ return await this.requestJson<ApiKey>(
2013
+ "DELETE",
2014
+ `/v1/workspaces/${workspaceId}/api-keys/${apiKeyId}`,
2015
+ );
1183
2016
  }
1184
2017
 
1185
2018
  // --- Billing (account-scoped) --------------------------------------------------------------------
@@ -1190,17 +2023,26 @@ export class OpenGeniClient {
1190
2023
  });
1191
2024
  }
1192
2025
 
1193
- async getBillingUsage(options: { accountId?: string; workspaceId?: string } = {}): Promise<BillingUsageResponse> {
2026
+ async getBillingUsage(
2027
+ options: { accountId?: string; workspaceId?: string } = {},
2028
+ ): Promise<BillingUsageResponse> {
1194
2029
  return await this.requestJson<BillingUsageResponse>("GET", "/v1/billing/usage", undefined, {
1195
2030
  ...(options.accountId !== undefined ? { accountId: options.accountId } : {}),
1196
2031
  ...(options.workspaceId !== undefined ? { workspaceId: options.workspaceId } : {}),
1197
2032
  });
1198
2033
  }
1199
2034
 
1200
- async getBillingEntitlements(options: { accountId?: string } = {}): Promise<BillingEntitlementsResponse> {
1201
- return await this.requestJson<BillingEntitlementsResponse>("GET", "/v1/billing/entitlements", undefined, {
1202
- ...(options.accountId !== undefined ? { accountId: options.accountId } : {}),
1203
- });
2035
+ async getBillingEntitlements(
2036
+ options: { accountId?: string } = {},
2037
+ ): Promise<BillingEntitlementsResponse> {
2038
+ return await this.requestJson<BillingEntitlementsResponse>(
2039
+ "GET",
2040
+ "/v1/billing/entitlements",
2041
+ undefined,
2042
+ {
2043
+ ...(options.accountId !== undefined ? { accountId: options.accountId } : {}),
2044
+ },
2045
+ );
1204
2046
  }
1205
2047
 
1206
2048
  /** Start a Stripe checkout for prepaid credits. */
@@ -1211,7 +2053,8 @@ export class OpenGeniClient {
1211
2053
  // --- Internals -------------------------------------------------------------
1212
2054
 
1213
2055
  private headers(): Record<string, string> {
1214
- const extra = typeof this.options.headers === "function" ? this.options.headers() : this.options.headers;
2056
+ const extra =
2057
+ typeof this.options.headers === "function" ? this.options.headers() : this.options.headers;
1215
2058
  return {
1216
2059
  ...(this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {}),
1217
2060
  ...extra,
@@ -1227,17 +2070,27 @@ export class OpenGeniClient {
1227
2070
 
1228
2071
  /** Connection state + the codex models the workspace may select (empty until connected). */
1229
2072
  async codexStatus(workspaceId: string): Promise<CodexConnectionStatus> {
1230
- return await this.requestJson<CodexConnectionStatus>("GET", `/v1/workspaces/${workspaceId}/codex/status`);
2073
+ return await this.requestJson<CodexConnectionStatus>(
2074
+ "GET",
2075
+ `/v1/workspaces/${workspaceId}/codex/status`,
2076
+ );
1231
2077
  }
1232
2078
 
1233
2079
  /** Begin device-code login: show `userCode` at `verificationUri`, then poll with `state`. */
1234
2080
  async codexConnectStart(workspaceId: string): Promise<CodexConnectStart> {
1235
- return await this.requestJson<CodexConnectStart>("POST", `/v1/workspaces/${workspaceId}/codex/connect/start`);
2081
+ return await this.requestJson<CodexConnectStart>(
2082
+ "POST",
2083
+ `/v1/workspaces/${workspaceId}/codex/connect/start`,
2084
+ );
1236
2085
  }
1237
2086
 
1238
2087
  /** Poll device-code authorization with the `state` from {@link codexConnectStart}. */
1239
2088
  async codexConnectPoll(workspaceId: string, state: string): Promise<CodexConnectPoll> {
1240
- return await this.requestJson<CodexConnectPoll>("POST", `/v1/workspaces/${workspaceId}/codex/connect/poll`, { state });
2089
+ return await this.requestJson<CodexConnectPoll>(
2090
+ "POST",
2091
+ `/v1/workspaces/${workspaceId}/codex/connect/poll`,
2092
+ { state },
2093
+ );
1241
2094
  }
1242
2095
 
1243
2096
  /** Remaining usage / limits for the connected (ACTIVE) subscription. Back-compat. */
@@ -1247,53 +2100,105 @@ export class OpenGeniClient {
1247
2100
 
1248
2101
  /** Live per-account usage read (refreshes THIS account's bearer; writes the cache). */
1249
2102
  async codexAccountUsage(workspaceId: string, accountId: string): Promise<CodexUsage> {
1250
- return await this.requestJson<CodexUsage>("GET", `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/usage`);
2103
+ return await this.requestJson<CodexUsage>(
2104
+ "GET",
2105
+ `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/usage`,
2106
+ );
1251
2107
  }
1252
2108
 
1253
2109
  /** Batched live refresh across every connected account, keyed by credential id. */
1254
2110
  async refreshCodexUsage(workspaceId: string): Promise<{ usage: CodexUsageMap }> {
1255
- return await this.requestJson<{ usage: CodexUsageMap }>("POST", `/v1/workspaces/${workspaceId}/codex/usage/refresh`);
2111
+ return await this.requestJson<{ usage: CodexUsageMap }>(
2112
+ "POST",
2113
+ `/v1/workspaces/${workspaceId}/codex/usage/refresh`,
2114
+ );
1256
2115
  }
1257
2116
 
1258
2117
  /** Disconnect ALL accounts (legacy workspace-wide). Prefer `disconnectCodexAccount`. */
1259
2118
  async codexDisconnect(workspaceId: string): Promise<{ disconnected: boolean }> {
1260
- return await this.requestJson<{ disconnected: boolean }>("DELETE", `/v1/workspaces/${workspaceId}/codex`);
2119
+ return await this.requestJson<{ disconnected: boolean }>(
2120
+ "DELETE",
2121
+ `/v1/workspaces/${workspaceId}/codex`,
2122
+ );
1261
2123
  }
1262
2124
 
1263
2125
  /** List every connected Codex account + the workspace active pointer + settings. */
1264
2126
  async listCodexAccounts(workspaceId: string): Promise<CodexAccountsResponse> {
1265
- return await this.requestJson<CodexAccountsResponse>("GET", `/v1/workspaces/${workspaceId}/codex/accounts`);
2127
+ return await this.requestJson<CodexAccountsResponse>(
2128
+ "GET",
2129
+ `/v1/workspaces/${workspaceId}/codex/accounts`,
2130
+ );
1266
2131
  }
1267
2132
 
1268
2133
  /** Switch the workspace ACTIVE Codex account (the one unpinned sessions use). */
1269
- async activateCodexAccount(workspaceId: string, accountId: string): Promise<{ activated: boolean; accountId: string }> {
1270
- return await this.requestJson<{ activated: boolean; accountId: string }>("POST", `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/activate`);
2134
+ async activateCodexAccount(
2135
+ workspaceId: string,
2136
+ accountId: string,
2137
+ ): Promise<{ activated: boolean; accountId: string }> {
2138
+ return await this.requestJson<{ activated: boolean; accountId: string }>(
2139
+ "POST",
2140
+ `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/activate`,
2141
+ );
1271
2142
  }
1272
2143
 
1273
2144
  /** P3: enable/disable Codex auto-rotation and/or pick the strategy. Returns the effective settings. */
1274
2145
  async setCodexRotationSettings(
1275
2146
  workspaceId: string,
1276
- patch: { rotationEnabled?: boolean; rotationStrategy?: CodexRotationSettings["rotationStrategy"] },
2147
+ patch: {
2148
+ rotationEnabled?: boolean;
2149
+ rotationStrategy?: CodexRotationSettings["rotationStrategy"];
2150
+ },
1277
2151
  ): Promise<CodexRotationSettings> {
1278
- return await this.requestJson<CodexRotationSettings>("PATCH", `/v1/workspaces/${workspaceId}/codex/settings`, patch);
2152
+ return await this.requestJson<CodexRotationSettings>(
2153
+ "PATCH",
2154
+ `/v1/workspaces/${workspaceId}/codex/settings`,
2155
+ patch,
2156
+ );
1279
2157
  }
1280
2158
 
1281
2159
  /** Disconnect ONE Codex account by id (re-picks active when the removed one was active). */
1282
- async disconnectCodexAccount(workspaceId: string, accountId: string): Promise<{ disconnected: boolean; newActiveId: string | null }> {
1283
- return await this.requestJson<{ disconnected: boolean; newActiveId: string | null }>("DELETE", `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}`);
2160
+ async disconnectCodexAccount(
2161
+ workspaceId: string,
2162
+ accountId: string,
2163
+ ): Promise<{ disconnected: boolean; newActiveId: string | null }> {
2164
+ return await this.requestJson<{ disconnected: boolean; newActiveId: string | null }>(
2165
+ "DELETE",
2166
+ `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}`,
2167
+ );
1284
2168
  }
1285
2169
 
1286
2170
  /** Rename a Codex account (label only in P1). */
1287
- async renameCodexAccount(workspaceId: string, accountId: string, label: string | null): Promise<CodexAccount> {
1288
- return await this.requestJson<CodexAccount>("PATCH", `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}`, { label });
2171
+ async renameCodexAccount(
2172
+ workspaceId: string,
2173
+ accountId: string,
2174
+ label: string | null,
2175
+ ): Promise<CodexAccount> {
2176
+ return await this.requestJson<CodexAccount>(
2177
+ "PATCH",
2178
+ `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}`,
2179
+ { label },
2180
+ );
1289
2181
  }
1290
2182
 
1291
2183
  /** Pin (or unpin via "auto") a session's Codex account. Applies on the next turn. */
1292
- async pinSessionCodexAccount(workspaceId: string, sessionId: string, target: string): Promise<{ pinned: string }> {
1293
- return await this.requestJson<{ pinned: string }>("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/codex-account`, { target });
2184
+ async pinSessionCodexAccount(
2185
+ workspaceId: string,
2186
+ sessionId: string,
2187
+ target: string,
2188
+ ): Promise<{ pinned: string }> {
2189
+ return await this.requestJson<{ pinned: string }>(
2190
+ "POST",
2191
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/codex-account`,
2192
+ { target },
2193
+ );
1294
2194
  }
1295
2195
 
1296
- private async requestJson<T>(method: string, path: string, body?: unknown, query: Record<string, string> = {}): Promise<T> {
2196
+ private async requestJson<T>(
2197
+ method: string,
2198
+ path: string,
2199
+ body?: unknown,
2200
+ query: Record<string, string> = {},
2201
+ ): Promise<T> {
1297
2202
  const response = await this.fetchImpl(this.url(path, query), {
1298
2203
  method,
1299
2204
  headers: {
@@ -1333,7 +2238,3 @@ async function safeText(response: Response): Promise<string> {
1333
2238
  return "";
1334
2239
  }
1335
2240
  }
1336
-
1337
- function delay(ms: number): Promise<void> {
1338
- return new Promise((resolve) => setTimeout(resolve, ms));
1339
- }