@opengeni/sdk 0.9.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,
@@ -18,23 +22,28 @@ import type {
18
22
  CapabilityCatalogItem,
19
23
  CapabilityCatalogResponse,
20
24
  CapabilityInstallation,
25
+ AddDocumentRequest,
21
26
  ClientConfig,
22
27
  ClientSessionEventInput,
23
28
  CompactSessionContextResult,
24
29
  CompleteFileUploadResponse,
30
+ ConnectionMetadata,
25
31
  CreateApiKeyRequest,
26
32
  CreateApiKeyResponse,
27
33
  CreateCapabilityCatalogItemRequest,
28
34
  CreateCheckoutRequest,
29
35
  CreateCheckoutResponse,
36
+ CreateConnectionRequest,
30
37
  CreateDocumentBaseRequest,
31
38
  CreateFileUploadRequest,
32
39
  CreateFileUploadResponse,
33
40
  CreateGitHubAppManifestRequest,
34
41
  CreateGitHubAppManifestResponse,
42
+ CreateKnowledgeMemoryRequest,
35
43
  CreateScheduledTaskRequest,
36
44
  CreateSessionRequest,
37
- CreateWorkspaceEnvironmentRequest,
45
+ CreateVariableSetRequest,
46
+ CreateRigRequest,
38
47
  CreateWorkspaceRequest,
39
48
  // Enrollment UX (design 11): the click-Grant approve-page lookup/deny + headless
40
49
  // enroll-token mint.
@@ -45,6 +54,7 @@ import type {
45
54
  DiscoverMcpCapabilitiesResponse,
46
55
  Document,
47
56
  DocumentBase,
57
+ DocumentSearchRequest,
48
58
  DocumentSearchResponse,
49
59
  EnableCapabilityRequest,
50
60
  EnablePackRequest,
@@ -53,11 +63,12 @@ import type {
53
63
  GetPackResponse,
54
64
  GitHubAppInfo,
55
65
  GitHubRepositoriesResponse,
66
+ KnowledgeMemory,
67
+ KnowledgeMemorySearchRequest,
56
68
  ListApiKeysResponse,
57
69
  ListPacksResponse,
58
70
  // Bring-your-own-compute: the Machines dashboard + per-machine metrics (M10).
59
71
  MachinesResponse,
60
- MachineView,
61
72
  MetricSample,
62
73
  MachineMetricsSeriesResponse,
63
74
  // Bring-your-own-compute: the user-authenticated active-sandbox swap (M7).
@@ -71,8 +82,16 @@ import type {
71
82
  ScheduledTask,
72
83
  ScheduledTaskRun,
73
84
  Session,
85
+ SessionListResponse,
86
+ UpdateSessionPinRequest,
74
87
  SessionEvent,
75
88
  SessionGoal,
89
+ SessionLineageResponse,
90
+ SessionMcpCredentialUpdateInput,
91
+ SessionQueueSnapshot,
92
+ SessionQueueMutationResponse,
93
+ SessionControlResponse,
94
+ WorkspaceInferenceControlResponse,
76
95
  SessionTurn,
77
96
  // Stream surfacing (Phase 5): capability negotiation + viewer lifecycle + config.
78
97
  SessionCapabilities,
@@ -103,6 +122,9 @@ import type {
103
122
  GitLogResponse,
104
123
  GitShowRequest,
105
124
  GitShowResponse,
125
+ // Workbench v2 turn-end capture reads (M2, dossier §10.3).
126
+ GetWorkspaceCaptureResponse,
127
+ GetWorkspaceCaptureFileResponse,
106
128
  TerminalExecRequest,
107
129
  TerminalExecResponse,
108
130
  PtyOpenRequest,
@@ -111,19 +133,33 @@ import type {
111
133
  PtyResizeRequest,
112
134
  PtyCloseRequest,
113
135
  ToolRef,
136
+ UpdateConnectionRequest,
137
+ UpdateKnowledgeMemoryRequest,
114
138
  UpdateScheduledTaskRequest,
115
139
  UpdateSessionGoalRequest,
116
140
  UpdateSessionRequest,
117
- UpdateSessionTurnRequest,
118
- UpdateWorkspaceEnvironmentRequest,
141
+ UpdateVariableSetRequest,
142
+ UpdateRigRequest,
119
143
  UpdateWorkspaceMemberRequest,
120
144
  UpdateWorkspaceRequest,
145
+ UpdateWorkspaceSettingsRequest,
146
+ SetWorkspaceDefaultRigRequest,
121
147
  UploadFileInput,
122
- WorkspaceEnvironment,
123
- WorkspaceEnvironmentVariableMetadata,
148
+ VariableSet,
149
+ VariableSetVariableMetadata,
150
+ Rig,
151
+ RigVersion,
152
+ RigChange,
153
+ ProposeRigChangeRequest,
124
154
  WorkspaceMember,
155
+ WorkspaceMemorySearchRequest,
156
+ WorkspaceMemorySearchResponse,
125
157
  WorkspaceRegisteredPack,
126
158
  Workspace,
159
+ ListConnectionsResponse,
160
+ ConnectionResponse,
161
+ OAuthStartRequest,
162
+ OAuthStartResponse,
127
163
  } from "./types";
128
164
 
129
165
  export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
@@ -146,19 +182,16 @@ export type SendMessageInput = {
146
182
  model?: string;
147
183
  reasoningEffort?: ReasoningEffort;
148
184
  clientEventId?: string;
185
+ expectedControlGeneration?: number;
186
+ expectedWorkspaceInferenceGeneration?: number;
187
+ mcpCredentialUpdates?: SessionMcpCredentialUpdateInput[];
149
188
  };
150
189
 
151
190
  export type SteerMessageResult = {
152
191
  /** The accepted `user.message` event. */
153
192
  accepted: SessionEvent;
154
- /**
155
- * The turn created for the message, when it could be located — usually
156
- * still queued, but already claimed (running/requires_action or even
157
- * finished) when the worker picked it up mid-call.
158
- */
159
- turn: SessionTurn | null;
160
- /** True when the running turn was interrupted to make way for the message. */
161
- interrupted: boolean;
193
+ /** The exact turn created for this message in the same server transaction. */
194
+ turn: SessionTurn;
162
195
  };
163
196
 
164
197
  /**
@@ -174,34 +207,145 @@ export class OpenGeniClient {
174
207
  constructor(options: OpenGeniClientOptions) {
175
208
  this.baseUrl = options.baseUrl.replace(/\/+$/, "");
176
209
  this.options = options;
177
- // Bind lazily so environments that polyfill fetch after module load work.
210
+ // Bind lazily so variable sets that polyfill fetch after module load work.
178
211
  this.fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
179
212
  }
180
213
 
181
214
  // --- Session lifecycle ---------------------------------------------------
182
215
 
183
216
  async createSession(workspaceId: string, request: CreateSessionRequest): Promise<Session> {
184
- 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
+ );
185
222
  }
186
223
 
187
224
  async getSession(workspaceId: string, sessionId: string): Promise<Session> {
188
- 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
+ );
189
229
  }
190
230
 
191
- async updateSession(workspaceId: string, sessionId: string, request: UpdateSessionRequest): Promise<Session> {
192
- 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
+ );
193
241
  }
194
242
 
195
- async listSessions(workspaceId: string, options: { limit?: number } = {}): Promise<Session[]> {
196
- return await this.requestJson<Session[]>("GET", `/v1/workspaces/${workspaceId}/sessions`, undefined, {
197
- ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
198
- });
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
+ );
199
267
  }
200
268
 
201
- async listTurns(workspaceId: string, sessionId: string, options: { limit?: number } = {}): Promise<SessionTurn[]> {
202
- return await this.requestJson<SessionTurn[]>("GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns`, undefined, {
203
- ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
204
- });
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
+ );
205
349
  }
206
350
 
207
351
  // --- Bring-your-own-compute: Machines dashboard + metrics (M10) ------------
@@ -212,10 +356,18 @@ export class OpenGeniClient {
212
356
  * sharedSessionCount. Pass `sessionId` for an in-session view, which adds the
213
357
  * session's synthetic Modal group box + the active-sandbox pointer.
214
358
  */
215
- async listMachines(workspaceId: string, options: { sessionId?: string } = {}): Promise<MachinesResponse> {
216
- return await this.requestJson<MachinesResponse>("GET", `/v1/workspaces/${workspaceId}/machines`, undefined, {
217
- ...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
218
- });
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
+ );
219
371
  }
220
372
 
221
373
  /**
@@ -248,7 +400,11 @@ export class OpenGeniClient {
248
400
  * the request.
249
401
  */
250
402
  async lookupDeviceEnrollment(userCode: string): Promise<DeviceEnrollmentLookupResponse> {
251
- 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
+ );
252
408
  }
253
409
 
254
410
  /**
@@ -318,14 +474,25 @@ export class OpenGeniClient {
318
474
 
319
475
  // --- Scheduled tasks -------------------------------------------------------
320
476
 
321
- async listScheduledTasks(workspaceId: string, options: { limit?: number } = {}): Promise<ScheduledTask[]> {
322
- return await this.requestJson<ScheduledTask[]>("GET", `/v1/workspaces/${workspaceId}/scheduled-tasks`, undefined, {
323
- ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
324
- });
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
+ );
325
489
  }
326
490
 
327
491
  async getScheduledTask(workspaceId: string, taskId: string): Promise<ScheduledTask> {
328
- 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
+ );
329
496
  }
330
497
 
331
498
  // --- Events: replay, send, stream ----------------------------------------
@@ -341,20 +508,37 @@ export class OpenGeniClient {
341
508
  sessionId: string,
342
509
  options: { after?: number; before?: number; limit?: number; compact?: boolean } = {},
343
510
  ): Promise<SessionEvent[]> {
344
- return await this.requestJson<SessionEvent[]>("GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, undefined, {
345
- ...(options.after !== undefined ? { after: String(options.after) } : {}),
346
- ...(options.before !== undefined ? { before: String(options.before) } : {}),
347
- ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
348
- ...(options.compact ? { compact: "1" } : {}),
349
- });
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
+ );
350
522
  }
351
523
 
352
524
  /** POST a user/control event to the session. Returns the accepted event. */
353
- async sendEvent(workspaceId: string, sessionId: string, event: ClientSessionEventInput): Promise<SessionEvent> {
354
- 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
+ );
355
535
  }
356
536
 
357
- 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> {
358
542
  const input = typeof message === "string" ? { text: message } : message;
359
543
  const { clientEventId, ...payload } = input;
360
544
  return await this.sendEvent(workspaceId, sessionId, {
@@ -364,22 +548,28 @@ export class OpenGeniClient {
364
548
  });
365
549
  }
366
550
 
367
- async interrupt(
551
+ async pauseSession(
368
552
  workspaceId: string,
369
553
  sessionId: string,
370
554
  options: { reason?: string; clientEventId?: string } = {},
371
555
  ): Promise<SessionEvent> {
372
- return await this.sendEvent(workspaceId, sessionId, {
373
- type: "user.interrupt",
374
- ...(options.clientEventId !== undefined ? { clientEventId: options.clientEventId } : {}),
375
- payload: options.reason !== undefined ? { reason: options.reason } : {},
376
- });
556
+ return (
557
+ await this.controlSession(workspaceId, sessionId, {
558
+ mode: "pause",
559
+ ...options,
560
+ })
561
+ ).event;
377
562
  }
378
563
 
379
564
  async sendApprovalDecision(
380
565
  workspaceId: string,
381
566
  sessionId: string,
382
- 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
+ },
383
573
  ): Promise<SessionEvent> {
384
574
  const { clientEventId, ...payload } = decision;
385
575
  return await this.sendEvent(workspaceId, sessionId, {
@@ -405,8 +595,13 @@ export class OpenGeniClient {
405
595
  /** The transport `streamEvents` runs on; useful for custom streaming layers. */
406
596
  eventStreamTransport(workspaceId: string, sessionId: string): SessionEventStreamTransport {
407
597
  return {
408
- openStream: async (after, signal) => await this.openEventStream(workspaceId, sessionId, { after, ...(signal ? { signal } : {}) }),
409
- 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 }),
410
605
  };
411
606
  }
412
607
 
@@ -435,34 +630,77 @@ export class OpenGeniClient {
435
630
 
436
631
  // --- Turn queue ------------------------------------------------------------
437
632
 
438
- /** Edit a still-queued turn (prompt, model, resources, tools, ...). */
439
- 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(
440
641
  workspaceId: string,
441
642
  sessionId: string,
442
643
  turnId: string,
443
- update: UpdateSessionTurnRequest,
444
- ): Promise<SessionTurn> {
445
- return await this.requestJson<SessionTurn>(
446
- "PATCH",
447
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns/${turnId}`,
448
- 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,
449
650
  );
450
651
  }
451
652
 
452
- /**
453
- * Reorder the queued turns. `turnIds` must all reference queued turns; the
454
- * server assigns positions in the given order and returns the queue.
455
- */
456
- async reorderQueuedTurns(workspaceId: string, sessionId: string, turnIds: string[]): Promise<SessionTurn[]> {
457
- 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>(
458
692
  "POST",
459
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns/reorder`,
460
- { turnIds },
693
+ `/v1/workspaces/${workspaceId}/inference-control`,
694
+ request,
461
695
  );
462
696
  }
463
697
 
464
698
  /** Cancel a queued turn before it is claimed. Returns the cancelled turn. */
465
- 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> {
466
704
  return await this.requestJson<SessionTurn>(
467
705
  "DELETE",
468
706
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns/${turnId}`,
@@ -470,87 +708,54 @@ export class OpenGeniClient {
470
708
  }
471
709
 
472
710
  /**
473
- * Steer: deliver a message *now* instead of behind the queue. Sends the
474
- * message, promotes its queued turn to the front, and interrupts the
475
- * running turn so the session picks the steer turn up next. On a session
476
- * that is not running this degrades gracefully to a plain queued message.
477
- *
478
- * The steer turn is located by `triggerEventId` across ALL turns (retried
479
- * briefly in case the server is still materializing it) — not just the
480
- * queued ones, because the worker can claim the steer turn before it is
481
- * ever observed queued, and a claimed steer turn means the message is
482
- * already being delivered: interrupting then would cancel the very message
483
- * being steered. If the turn cannot be found while other turns are queued,
484
- * the interrupt is also skipped — stopping the running turn would otherwise
485
- * promote someone else's queued work over this message — and the call
486
- * 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.
487
713
  */
488
714
  async steerMessage(
489
715
  workspaceId: string,
490
716
  sessionId: string,
491
717
  message: string | SendMessageInput,
492
718
  ): Promise<SteerMessageResult> {
493
- const accepted = await this.sendMessage(workspaceId, sessionId, message);
494
- let steerTurn: SessionTurn | null = null;
495
- let queued: SessionTurn[] = [];
496
- for (let attempt = 0; attempt < 4; attempt += 1) {
497
- if (attempt > 0) {
498
- await delay(150 * attempt);
499
- }
500
- const turns = await this.listTurns(workspaceId, sessionId);
501
- queued = turns
502
- .filter((turn) => turn.status === "queued")
503
- .sort((a, b) => a.position - b.position || a.createdAt.localeCompare(b.createdAt));
504
- // Match against every turn, whatever its status: a steer turn that is
505
- // already running/requires_action (or even finished) was claimed before
506
- // this listing — that is delivery, not grounds for an interrupt.
507
- steerTurn = turns.find((turn) => turn.triggerEventId === accepted.id) ?? null;
508
- if (steerTurn) {
509
- break;
510
- }
511
- }
512
- const steerTurnQueued = steerTurn?.status === "queued";
513
- if (steerTurn && steerTurnQueued && queued.length > 1) {
514
- const front = steerTurn;
515
- await this.reorderQueuedTurns(workspaceId, sessionId, [
516
- front.id,
517
- ...queued.filter((turn) => turn.id !== front.id).map((turn) => turn.id),
518
- ]);
519
- }
520
- // Interrupting is only safe when the next claim is provably this message:
521
- // either the steer turn sits queued (now at the front), or no turn
522
- // materialized yet AND nothing else is queued. A steer turn observed in
523
- // any non-queued state was already claimed — skip the interrupt.
524
- const canDeliverNext = steerTurnQueued || (steerTurn === null && queued.length === 0);
525
- const session = await this.getSession(workspaceId, sessionId);
526
- // If the previously running turn already finished and the session claimed
527
- // the steer turn itself, interrupting now would cancel the very message
528
- // being steered. `activeTurnId` is the claim check; the residual window
529
- // between this read and the interrupt landing is accepted (an interrupt
530
- // can never be atomic with a status read over HTTP).
531
- const steerTurnAlreadyActive = steerTurn !== null && session.activeTurnId === steerTurn.id;
532
- const interrupted = canDeliverNext
533
- && !steerTurnAlreadyActive
534
- && (session.status === "running" || session.status === "requires_action");
535
- if (interrupted) {
536
- await this.interrupt(workspaceId, sessionId, { reason: "steer" });
537
- }
538
- 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
+ );
539
725
  }
540
726
 
541
727
  // --- Goals -------------------------------------------------------------------
542
728
 
543
729
  /** The session's goal. 404s when the session never had one. */
544
730
  async getGoal(workspaceId: string, sessionId: string): Promise<SessionGoal> {
545
- 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
+ );
546
735
  }
547
736
 
548
- async updateGoal(workspaceId: string, sessionId: string, request: UpdateSessionGoalRequest): Promise<SessionGoal> {
549
- return await this.requestJson<SessionGoal>("PATCH", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`, request);
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
+ );
747
+ }
748
+
749
+ async deleteGoal(workspaceId: string, sessionId: string): Promise<void> {
750
+ await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`);
550
751
  }
551
752
 
552
753
  /** Pause the goal loop: the session stops self-continuing until resumed. */
553
- 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> {
554
759
  return await this.updateGoal(workspaceId, sessionId, {
555
760
  status: "paused",
556
761
  ...(options.rationale !== undefined ? { rationale: options.rationale } : {}),
@@ -572,17 +777,23 @@ export class OpenGeniClient {
572
777
  * context — the destructive intent is explicit on the wire.
573
778
  */
574
779
  async clearSessionContext(workspaceId: string, sessionId: string): Promise<void> {
575
- 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
+ );
576
785
  }
577
786
 
578
- /**
579
- * Trigger conversation compaction now. On the client-managed (Azure) path this
580
- * queues a forced compaction the worker honors before the next turn
581
- * (`status:"queued"`); on a server-managed provider or when compaction is off
582
- * it is a no-op (`status:"noop"`) with an explanatory message.
583
- */
584
- async compactSessionContext(workspaceId: string, sessionId: string): Promise<CompactSessionContextResult> {
585
- 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
+ );
586
797
  }
587
798
 
588
799
  // --- Channel-A structured services (P4.4) ------------------------------------
@@ -591,79 +802,232 @@ export class OpenGeniClient {
591
802
  // notifications + the PTY output stream arrive on the existing event SSE.
592
803
 
593
804
  /** FileSystem: list a directory tree (feeds the Pierre file tree). */
594
- async fsList(workspaceId: string, sessionId: string, request: FsListRequest = {}): Promise<FsListResponse> {
595
- 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
+ );
596
815
  }
597
816
 
598
817
  /** FileSystem: read a file (text or base64; binary-safe, size-capped). */
599
- async fsRead(workspaceId: string, sessionId: string, request: FsReadRequest): Promise<FsReadResponse> {
600
- 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
+ );
601
828
  }
602
829
 
603
830
  /** FileSystem: write a file (last-writer-wins; emits fs.changed). */
604
- async fsWrite(workspaceId: string, sessionId: string, request: FsWriteRequest): Promise<FsWriteResponse> {
605
- 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
+ );
606
841
  }
607
842
 
608
843
  /** FileSystem: delete a path (emits fs.changed). */
609
- async fsDelete(workspaceId: string, sessionId: string, request: FsDeleteRequest): Promise<FsDeleteResponse> {
610
- 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
+ );
611
854
  }
612
855
 
613
856
  /** FileSystem: move/rename a path (emits fs.changed; 409 if destination exists and overwrite is false). */
614
- async fsMove(workspaceId: string, sessionId: string, request: FsMoveRequest): Promise<FsMoveResponse> {
615
- 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
+ );
616
867
  }
617
868
 
618
869
  /** FileSystem: create a directory (emits fs.changed; recursive defaults to true). */
619
- async fsMkdir(workspaceId: string, sessionId: string, request: FsMkdirRequest): Promise<FsMkdirResponse> {
620
- 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
+ );
621
880
  }
622
881
 
623
882
  /** Git: working-tree/index status (the Pierre file-status feed). */
624
- async gitStatus(workspaceId: string, sessionId: string, request: GitStatusRequest = {}): Promise<GitStatusResponse> {
625
- 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
+ );
626
893
  }
627
894
 
628
895
  /** Git: structured diff hunks (the Pierre diff feed). */
629
- async gitDiff(workspaceId: string, sessionId: string, request: GitDiffRequest = {}): Promise<GitDiffResponse> {
630
- 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
+ );
631
906
  }
632
907
 
633
908
  /** Git: commit log. */
634
- async gitLog(workspaceId: string, sessionId: string, request: GitLogRequest = {}): Promise<GitLogResponse> {
635
- 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
+ );
636
919
  }
637
920
 
638
921
  /** Git: show a commit (diff vs first parent) or fetch a raw blob at a ref. */
639
- async gitShow(workspaceId: string, sessionId: string, request: GitShowRequest): Promise<GitShowResponse> {
640
- 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
+ );
641
965
  }
642
966
 
643
967
  /** Terminal: run a bounded command, returning buffered stdout/stderr inline. */
644
- async terminalExec(workspaceId: string, sessionId: string, request: TerminalExecRequest): Promise<TerminalExecResponse> {
645
- 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
+ );
646
978
  }
647
979
 
648
980
  /** Terminal: open an interactive PTY. Output streams on the event SSE as
649
981
  * terminal.pty.output.delta; drive it with terminalPtyWrite. */
650
- async terminalPtyOpen(workspaceId: string, sessionId: string, request: PtyOpenRequest = {}): Promise<PtyOpenResponse> {
651
- 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
+ );
652
992
  }
653
993
 
654
994
  /** Terminal: send stdin to an open PTY (output rides A1). */
655
- async terminalPtyWrite(workspaceId: string, sessionId: string, request: PtyWriteRequest): Promise<void> {
656
- 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
+ );
657
1005
  }
658
1006
 
659
1007
  /** Terminal: resize an open PTY. */
660
- async terminalPtyResize(workspaceId: string, sessionId: string, request: PtyResizeRequest): Promise<void> {
661
- 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
+ );
662
1018
  }
663
1019
 
664
1020
  /** Terminal: close an open PTY (idempotent). */
665
- async terminalPtyClose(workspaceId: string, sessionId: string, request: PtyCloseRequest): Promise<void> {
666
- 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
+ );
667
1031
  }
668
1032
 
669
1033
  // --- Stream surfacing: capability negotiation + viewer lifecycle (Phase 5) ---
@@ -678,19 +1042,29 @@ export class OpenGeniClient {
678
1042
  * liveness the client polls on while `cold`/`warming`. The desktop URL/token
679
1043
  * are minted in-process only when the box is warm AND the principal has
680
1044
  * acknowledged the un-redacted plane. */
681
- async getStreamCapabilities(workspaceId: string, sessionId: string): Promise<SessionCapabilities> {
1045
+ async getStreamCapabilities(
1046
+ workspaceId: string,
1047
+ sessionId: string,
1048
+ ): Promise<SessionCapabilities> {
682
1049
  return await this.requestJson<SessionCapabilities>(
683
- "GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`);
1050
+ "GET",
1051
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`,
1052
+ );
684
1053
  }
685
1054
 
686
1055
  /** Record the calling principal's acknowledgment of the un-redacted desktop
687
1056
  * pixel plane (and, when the box is shared, the shared-exposure disclosure).
688
1057
  * The desktop viewer-attach path returns 409 until this is recorded. */
689
1058
  async acknowledgeStream(
690
- workspaceId: string, sessionId: string, request: AcknowledgeStreamRequest = {},
1059
+ workspaceId: string,
1060
+ sessionId: string,
1061
+ request: AcknowledgeStreamRequest = {},
691
1062
  ): Promise<AcknowledgeStreamResponse> {
692
1063
  return await this.requestJson<AcknowledgeStreamResponse>(
693
- "POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities/acknowledge`, request);
1064
+ "POST",
1065
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities/acknowledge`,
1066
+ request,
1067
+ );
694
1068
  }
695
1069
 
696
1070
  /** Attach a viewer holder (refcounted liveness — keeps the box warm while
@@ -702,25 +1076,39 @@ export class OpenGeniClient {
702
1076
  * (`desktop` omitted/false) warms the box + mints the pty-ws terminal cell with
703
1077
  * NO consent gate. An omitted `viewerId` mints a fresh one. */
704
1078
  async attachViewer(
705
- workspaceId: string, sessionId: string, request: AttachViewerRequest = {},
1079
+ workspaceId: string,
1080
+ sessionId: string,
1081
+ request: AttachViewerRequest = {},
706
1082
  ): Promise<AttachViewerResponse> {
707
1083
  return await this.requestJson<AttachViewerResponse>(
708
- "POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers`, request);
1084
+ "POST",
1085
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers`,
1086
+ request,
1087
+ );
709
1088
  }
710
1089
 
711
1090
  /** Heartbeat a viewer holder (Channel-A app-level liveness). A closed laptop
712
1091
  * stops sending these → the reaper drops the holder within ~90s. Echoes
713
1092
  * `leaseEpoch` so a superseded epoch is rejected (`alive:false` → re-attach). */
714
1093
  async heartbeatViewer(
715
- workspaceId: string, sessionId: string, viewerId: string, request: ViewerHeartbeatRequest,
1094
+ workspaceId: string,
1095
+ sessionId: string,
1096
+ viewerId: string,
1097
+ request: ViewerHeartbeatRequest,
716
1098
  ): Promise<ViewerHeartbeatResponse> {
717
1099
  return await this.requestJson<ViewerHeartbeatResponse>(
718
- "POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers/${viewerId}/heartbeat`, request);
1100
+ "POST",
1101
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers/${viewerId}/heartbeat`,
1102
+ request,
1103
+ );
719
1104
  }
720
1105
 
721
1106
  /** Detach a viewer (delete this holder; idempotent delete-my-row). */
722
1107
  async detachViewer(workspaceId: string, sessionId: string, viewerId: string): Promise<void> {
723
- 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
+ );
724
1112
  }
725
1113
 
726
1114
  // --- Access + workspaces -----------------------------------------------------
@@ -753,155 +1141,392 @@ export class OpenGeniClient {
753
1141
  return await this.requestJson<Workspace>("GET", `/v1/workspaces/${workspaceId}`);
754
1142
  }
755
1143
 
756
- async updateWorkspace(workspaceId: string, request: UpdateWorkspaceRequest): Promise<Workspace> {
757
- return await this.requestJson<Workspace>("PATCH", `/v1/workspaces/${workspaceId}`, request);
758
- }
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
+ );
1340
+ }
1341
+
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
+ );
1351
+ }
1352
+
1353
+ // --- Rigs ------------------------------------------------------------------
1354
+ // Workspace-scoped, versioned sandbox machine definitions. rigs:use gates read
1355
+ // + proposeRigChange; rigs:manage gates create / update / delete / activate.
759
1356
 
760
- /**
761
- * Delete a workspace and everything in it. Refused (409) for the account's
762
- * only workspace and while it still has a running session. Irreversible.
763
- */
764
- async deleteWorkspace(workspaceId: string): Promise<void> {
765
- await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}`);
1357
+ async listRigs(workspaceId: string): Promise<Rig[]> {
1358
+ return await this.requestJson<Rig[]>("GET", `/v1/workspaces/${workspaceId}/rigs`);
766
1359
  }
767
1360
 
768
- // --- Members ("People with access") -------------------------------------------
769
-
770
- /** The workspace's members (user + api_key subjects). */
771
- async listWorkspaceMembers(workspaceId: string): Promise<WorkspaceMember[]> {
772
- const response = await this.requestJson<ListWorkspaceMembersResponse>("GET", `/v1/workspaces/${workspaceId}/members`);
773
- return response.members;
1361
+ async createRig(workspaceId: string, request: CreateRigRequest): Promise<Rig> {
1362
+ return await this.requestJson<Rig>("POST", `/v1/workspaces/${workspaceId}/rigs`, request);
774
1363
  }
775
1364
 
776
- /**
777
- * Add an already-registered user by email. 404s when no user with that email
778
- * exists (email invites for unknown users are deferred).
779
- */
780
- async addWorkspaceMember(workspaceId: string, request: AddWorkspaceMemberRequest): Promise<WorkspaceMember> {
781
- return await this.requestJson<WorkspaceMember>("POST", `/v1/workspaces/${workspaceId}/members`, request);
1365
+ async getRig(workspaceId: string, rigId: string): Promise<Rig> {
1366
+ return await this.requestJson<Rig>("GET", `/v1/workspaces/${workspaceId}/rigs/${rigId}`);
782
1367
  }
783
1368
 
784
- async updateWorkspaceMember(workspaceId: string, subjectId: string, request: UpdateWorkspaceMemberRequest): Promise<WorkspaceMember> {
785
- return await this.requestJson<WorkspaceMember>(
1369
+ async updateRig(workspaceId: string, rigId: string, request: UpdateRigRequest): Promise<Rig> {
1370
+ return await this.requestJson<Rig>(
786
1371
  "PATCH",
787
- `/v1/workspaces/${workspaceId}/members/${encodeURIComponent(subjectId)}`,
1372
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}`,
788
1373
  request,
789
1374
  );
790
1375
  }
791
1376
 
792
- /**
793
- * Remove a member. Refused (409) for your own membership and for the last
794
- * member who can still manage the workspace.
795
- */
796
- async removeWorkspaceMember(workspaceId: string, subjectId: string): Promise<void> {
797
- 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}`);
798
1379
  }
799
1380
 
800
- // --- 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
+ }
801
1387
 
802
- async createScheduledTask(workspaceId: string, request: CreateScheduledTaskRequest): Promise<ScheduledTask> {
803
- 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
+ );
804
1398
  }
805
1399
 
806
- async updateScheduledTask(workspaceId: string, taskId: string, request: UpdateScheduledTaskRequest): Promise<ScheduledTask> {
807
- 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
+ );
808
1405
  }
809
1406
 
810
- async pauseScheduledTask(workspaceId: string, taskId: string): Promise<ScheduledTask> {
811
- 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
+ );
812
1418
  }
813
1419
 
814
- async resumeScheduledTask(workspaceId: string, taskId: string): Promise<ScheduledTask> {
815
- 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
+ );
816
1425
  }
817
1426
 
818
1427
  /**
819
- * Fire the task immediately (manual trigger), independent of its schedule.
820
- * Pass a stable `triggerId` to make a retried trigger idempotent — the same
821
- * 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.
822
1431
  */
823
- async triggerScheduledTask(workspaceId: string, taskId: string, options: { triggerId?: string } = {}): Promise<ScheduledTask> {
824
- return await this.requestJson<ScheduledTask>(
1432
+ async verifyRigChange(workspaceId: string, rigId: string, changeId: string): Promise<RigChange> {
1433
+ return await this.requestJson<RigChange>(
825
1434
  "POST",
826
- `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/trigger`,
827
- options.triggerId ? { triggerId: options.triggerId } : undefined,
1435
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes/${changeId}/verify`,
828
1436
  );
829
1437
  }
830
1438
 
831
- async deleteScheduledTask(workspaceId: string, taskId: string): Promise<void> {
832
- await this.requestJson<unknown>("DELETE", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`);
833
- }
834
-
835
- 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(
836
1445
  workspaceId: string,
837
- taskId: string,
838
- options: { limit?: number } = {},
839
- ): Promise<ScheduledTaskRun[]> {
840
- return await this.requestJson<ScheduledTaskRun[]>(
841
- "GET",
842
- `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/runs`,
843
- undefined,
844
- { ...(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`,
845
1452
  );
846
1453
  }
847
1454
 
848
- // --- Environments --------------------------------------------------------------
849
- // 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
+ }
850
1466
 
851
- async listEnvironments(workspaceId: string): Promise<WorkspaceEnvironment[]> {
852
- 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);
853
1470
  }
854
1471
 
855
- async createEnvironment(workspaceId: string, request: CreateWorkspaceEnvironmentRequest): Promise<WorkspaceEnvironment> {
856
- 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);
857
1478
  }
858
1479
 
859
- async getEnvironment(workspaceId: string, environmentId: string): Promise<WorkspaceEnvironment> {
860
- 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);
861
1483
  }
862
1484
 
1485
+ /** @deprecated use updateVariableSet */
863
1486
  async updateEnvironment(
864
1487
  workspaceId: string,
865
1488
  environmentId: string,
866
- request: UpdateWorkspaceEnvironmentRequest,
867
- ): Promise<WorkspaceEnvironment> {
868
- return await this.requestJson<WorkspaceEnvironment>(
869
- "PATCH",
870
- `/v1/workspaces/${workspaceId}/environments/${environmentId}`,
871
- request,
872
- );
1489
+ request: UpdateVariableSetRequest,
1490
+ ): Promise<VariableSet> {
1491
+ return await this.updateVariableSet(workspaceId, environmentId, request);
873
1492
  }
874
1493
 
1494
+ /** @deprecated use deleteVariableSet */
875
1495
  async deleteEnvironment(workspaceId: string, environmentId: string): Promise<void> {
876
- await this.requestJson<unknown>("DELETE", `/v1/workspaces/${workspaceId}/environments/${environmentId}`);
1496
+ await this.deleteVariableSet(workspaceId, environmentId);
877
1497
  }
878
1498
 
879
- /** Create or rotate a variable. The value never comes back on any read. */
1499
+ /** @deprecated use setVariableSetVariable */
880
1500
  async setEnvironmentVariable(
881
1501
  workspaceId: string,
882
1502
  environmentId: string,
883
1503
  name: string,
884
1504
  value: string,
885
- ): Promise<WorkspaceEnvironmentVariableMetadata> {
886
- return await this.requestJson<WorkspaceEnvironmentVariableMetadata>(
887
- "PUT",
888
- `/v1/workspaces/${workspaceId}/environments/${environmentId}/variables/${encodeURIComponent(name)}`,
889
- { value },
890
- );
1505
+ ): Promise<VariableSetVariableMetadata> {
1506
+ return await this.setVariableSetVariable(workspaceId, environmentId, name, value);
891
1507
  }
892
1508
 
893
- async deleteEnvironmentVariable(workspaceId: string, environmentId: string, name: string): Promise<void> {
894
- await this.requestJson<unknown>(
895
- "DELETE",
896
- `/v1/workspaces/${workspaceId}/environments/${environmentId}/variables/${encodeURIComponent(name)}`,
897
- );
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);
898
1516
  }
899
1517
 
900
1518
  // --- Files -----------------------------------------------------------------------
901
1519
 
902
1520
  /** Step 1 of the upload flow: returns the pre-signed PUT target. */
903
- async beginFileUpload(workspaceId: string, request: CreateFileUploadRequest): Promise<CreateFileUploadResponse> {
904
- 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
+ );
905
1530
  }
906
1531
 
907
1532
  /** Step 3 of the upload flow: server verifies the object and marks it ready. */
@@ -921,12 +1546,14 @@ export class OpenGeniClient {
921
1546
  async uploadFile(workspaceId: string, input: UploadFileInput): Promise<FileAsset> {
922
1547
  // Copy Uint8Array views into a Blob so byte offsets/shared buffers can't
923
1548
  // leak surrounding bytes into the PUT body.
924
- const body: Blob | ArrayBuffer | string = input.data instanceof Uint8Array
925
- ? new Blob([input.data.slice()])
926
- : input.data;
927
- const sizeBytes = typeof body === "string"
928
- ? new TextEncoder().encode(body).byteLength
929
- : 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;
930
1557
  const upload = await this.beginFileUpload(workspaceId, {
931
1558
  filename: input.filename,
932
1559
  contentType: input.contentType,
@@ -951,39 +1578,76 @@ export class OpenGeniClient {
951
1578
  }
952
1579
 
953
1580
  async getFile(workspaceId: string, fileId: string): Promise<FileAsset> {
954
- 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
+ );
955
1585
  }
956
1586
 
957
1587
  /** Mint a short-lived signed download URL for a ready file. */
958
- async createFileDownloadUrl(workspaceId: string, fileId: string): Promise<FileDownloadUrlResponse> {
959
- 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
+ );
960
1596
  }
961
1597
 
962
1598
  // --- Documents ----------------------------------------------------------------------
963
1599
 
964
- async createDocumentBase(workspaceId: string, request: CreateDocumentBaseRequest): Promise<DocumentBase> {
965
- 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
+ );
966
1609
  }
967
1610
 
968
1611
  async listDocumentBases(workspaceId: string): Promise<DocumentBase[]> {
969
- 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
+ );
970
1616
  }
971
1617
 
972
1618
  async getDocumentBase(workspaceId: string, baseId: string): Promise<DocumentBase> {
973
- 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
+ );
974
1623
  }
975
1624
 
976
1625
  /** Index an uploaded file into the base. The file must be `ready`. */
977
- async addDocument(workspaceId: string, baseId: string, request: { fileId: string }): Promise<Document> {
978
- 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
+ );
979
1636
  }
980
1637
 
981
1638
  async listDocuments(workspaceId: string, baseId: string): Promise<Document[]> {
982
- 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
+ );
983
1643
  }
984
1644
 
985
1645
  /** Retry indexing for a failed document. */
986
- 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> {
987
1651
  return await this.requestJson<Document>(
988
1652
  "POST",
989
1653
  `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents/${documentId}/reindex`,
@@ -1004,7 +1668,7 @@ export class OpenGeniClient {
1004
1668
  async searchDocuments(
1005
1669
  workspaceId: string,
1006
1670
  baseId: string,
1007
- request: { query: string; limit?: number },
1671
+ request: Omit<DocumentSearchRequest, "baseIds">,
1008
1672
  ): Promise<DocumentSearchResponse> {
1009
1673
  return await this.requestJson<DocumentSearchResponse>(
1010
1674
  "POST",
@@ -1013,6 +1677,99 @@ export class OpenGeniClient {
1013
1677
  );
1014
1678
  }
1015
1679
 
1680
+ async searchKnowledge(
1681
+ workspaceId: string,
1682
+ request: DocumentSearchRequest,
1683
+ ): Promise<DocumentSearchResponse> {
1684
+ return await this.requestJson<DocumentSearchResponse>(
1685
+ "POST",
1686
+ `/v1/workspaces/${workspaceId}/knowledge/search`,
1687
+ request,
1688
+ );
1689
+ }
1690
+
1691
+ async listKnowledgeMemories(
1692
+ workspaceId: string,
1693
+ request: KnowledgeMemorySearchRequest = {},
1694
+ ): Promise<KnowledgeMemory[]> {
1695
+ const params = new URLSearchParams();
1696
+ if (request.query) params.set("query", request.query);
1697
+ if (request.status) params.set("status", request.status);
1698
+ if (request.kind) params.set("kind", request.kind);
1699
+ if (request.scope) params.set("scope", request.scope);
1700
+ if (request.limit) params.set("limit", String(request.limit));
1701
+ const query = params.toString();
1702
+ return await this.requestJson<KnowledgeMemory[]>(
1703
+ "GET",
1704
+ `/v1/workspaces/${workspaceId}/knowledge/memories${query ? `?${query}` : ""}`,
1705
+ );
1706
+ }
1707
+
1708
+ async getKnowledgeMemory(workspaceId: string, memoryId: string): Promise<KnowledgeMemory> {
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
+ );
1748
+ }
1749
+
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
+ );
1760
+ }
1761
+
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
+ );
1771
+ }
1772
+
1016
1773
  // --- Capability packs ------------------------------------------------------------------
1017
1774
 
1018
1775
  /** Built-in + registered packs, with the workspace's installations. */
@@ -1021,15 +1778,29 @@ export class OpenGeniClient {
1021
1778
  }
1022
1779
 
1023
1780
  /** Register (or replace) a workspace-scoped pack from a manifest. */
1024
- async registerPack(workspaceId: string, manifest: RegisterCapabilityPackRequest): Promise<WorkspaceRegisteredPack> {
1025
- 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
+ );
1026
1790
  }
1027
1791
 
1028
1792
  async getPack(workspaceId: string, packId: string): Promise<GetPackResponse> {
1029
- 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
+ );
1030
1797
  }
1031
1798
 
1032
- 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> {
1033
1804
  return await this.requestJson<PackInstallation>(
1034
1805
  "POST",
1035
1806
  `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}/enable`,
@@ -1039,22 +1810,38 @@ export class OpenGeniClient {
1039
1810
 
1040
1811
  /** Unregister a workspace-scoped pack (built-in packs cannot be deleted). */
1041
1812
  async deletePack(workspaceId: string, packId: string): Promise<void> {
1042
- await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`);
1813
+ await this.requestVoid(
1814
+ "DELETE",
1815
+ `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`,
1816
+ );
1043
1817
  }
1044
1818
 
1045
1819
  async listPackInstallations(workspaceId: string): Promise<PackInstallation[]> {
1046
- 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
+ );
1047
1824
  }
1048
1825
 
1049
1826
  // --- Capabilities -------------------------------------------------------------------------
1050
1827
 
1051
1828
  async listCapabilities(workspaceId: string): Promise<CapabilityCatalogResponse> {
1052
- return await this.requestJson<CapabilityCatalogResponse>("GET", `/v1/workspaces/${workspaceId}/capabilities`);
1829
+ return await this.requestJson<CapabilityCatalogResponse>(
1830
+ "GET",
1831
+ `/v1/workspaces/${workspaceId}/capabilities`,
1832
+ );
1053
1833
  }
1054
1834
 
1055
1835
  /** Add a manual capability catalog item (e.g. a remote MCP server). */
1056
- async createCapability(workspaceId: string, request: CreateCapabilityCatalogItemRequest): Promise<CapabilityCatalogItem> {
1057
- 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
+ );
1058
1845
  }
1059
1846
 
1060
1847
  async enableCapability(
@@ -1069,7 +1856,10 @@ export class OpenGeniClient {
1069
1856
  );
1070
1857
  }
1071
1858
 
1072
- async disableCapability(workspaceId: string, capabilityId: string): Promise<CapabilityInstallation> {
1859
+ async disableCapability(
1860
+ workspaceId: string,
1861
+ capabilityId: string,
1862
+ ): Promise<CapabilityInstallation> {
1073
1863
  return await this.requestJson<CapabilityInstallation>(
1074
1864
  "POST",
1075
1865
  `/v1/workspaces/${workspaceId}/capabilities/${encodeURIComponent(capabilityId)}/disable`,
@@ -1092,6 +1882,66 @@ export class OpenGeniClient {
1092
1882
  );
1093
1883
  }
1094
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
+
1095
1945
  // --- GitHub ----------------------------------------------------------------------------------
1096
1946
 
1097
1947
  /** GitHub App configuration status + a signed install URL when configured. */
@@ -1109,12 +1959,18 @@ export class OpenGeniClient {
1109
1959
  }
1110
1960
 
1111
1961
  async listGitHubRepositories(workspaceId: string): Promise<GitHubRepositoriesResponse> {
1112
- 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
+ );
1113
1966
  }
1114
1967
 
1115
1968
  /** Re-sync the installation's repository list from GitHub. */
1116
1969
  async syncGitHubRepositories(workspaceId: string): Promise<GitHubRepositoriesResponse> {
1117
- 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
+ );
1118
1974
  }
1119
1975
 
1120
1976
  /** Build a GitHub App manifest + the GitHub URL to submit it to. */
@@ -1132,18 +1988,31 @@ export class OpenGeniClient {
1132
1988
  // --- API keys ----------------------------------------------------------------------------------
1133
1989
 
1134
1990
  async listApiKeys(workspaceId: string): Promise<ApiKey[]> {
1135
- 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
+ );
1136
1995
  return response.apiKeys;
1137
1996
  }
1138
1997
 
1139
1998
  /** The returned `token` is shown once; only its prefix is stored. */
1140
- async createApiKey(workspaceId: string, request: CreateApiKeyRequest): Promise<CreateApiKeyResponse> {
1141
- 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
+ );
1142
2008
  }
1143
2009
 
1144
2010
  /** Revoke an API key. Returns the revoked key. */
1145
2011
  async deleteApiKey(workspaceId: string, apiKeyId: string): Promise<ApiKey> {
1146
- 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
+ );
1147
2016
  }
1148
2017
 
1149
2018
  // --- Billing (account-scoped) --------------------------------------------------------------------
@@ -1154,17 +2023,26 @@ export class OpenGeniClient {
1154
2023
  });
1155
2024
  }
1156
2025
 
1157
- async getBillingUsage(options: { accountId?: string; workspaceId?: string } = {}): Promise<BillingUsageResponse> {
2026
+ async getBillingUsage(
2027
+ options: { accountId?: string; workspaceId?: string } = {},
2028
+ ): Promise<BillingUsageResponse> {
1158
2029
  return await this.requestJson<BillingUsageResponse>("GET", "/v1/billing/usage", undefined, {
1159
2030
  ...(options.accountId !== undefined ? { accountId: options.accountId } : {}),
1160
2031
  ...(options.workspaceId !== undefined ? { workspaceId: options.workspaceId } : {}),
1161
2032
  });
1162
2033
  }
1163
2034
 
1164
- async getBillingEntitlements(options: { accountId?: string } = {}): Promise<BillingEntitlementsResponse> {
1165
- return await this.requestJson<BillingEntitlementsResponse>("GET", "/v1/billing/entitlements", undefined, {
1166
- ...(options.accountId !== undefined ? { accountId: options.accountId } : {}),
1167
- });
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
+ );
1168
2046
  }
1169
2047
 
1170
2048
  /** Start a Stripe checkout for prepaid credits. */
@@ -1175,7 +2053,8 @@ export class OpenGeniClient {
1175
2053
  // --- Internals -------------------------------------------------------------
1176
2054
 
1177
2055
  private headers(): Record<string, string> {
1178
- 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;
1179
2058
  return {
1180
2059
  ...(this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {}),
1181
2060
  ...extra,
@@ -1191,17 +2070,27 @@ export class OpenGeniClient {
1191
2070
 
1192
2071
  /** Connection state + the codex models the workspace may select (empty until connected). */
1193
2072
  async codexStatus(workspaceId: string): Promise<CodexConnectionStatus> {
1194
- 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
+ );
1195
2077
  }
1196
2078
 
1197
2079
  /** Begin device-code login: show `userCode` at `verificationUri`, then poll with `state`. */
1198
2080
  async codexConnectStart(workspaceId: string): Promise<CodexConnectStart> {
1199
- 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
+ );
1200
2085
  }
1201
2086
 
1202
2087
  /** Poll device-code authorization with the `state` from {@link codexConnectStart}. */
1203
2088
  async codexConnectPoll(workspaceId: string, state: string): Promise<CodexConnectPoll> {
1204
- 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
+ );
1205
2094
  }
1206
2095
 
1207
2096
  /** Remaining usage / limits for the connected (ACTIVE) subscription. Back-compat. */
@@ -1211,53 +2100,105 @@ export class OpenGeniClient {
1211
2100
 
1212
2101
  /** Live per-account usage read (refreshes THIS account's bearer; writes the cache). */
1213
2102
  async codexAccountUsage(workspaceId: string, accountId: string): Promise<CodexUsage> {
1214
- 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
+ );
1215
2107
  }
1216
2108
 
1217
2109
  /** Batched live refresh across every connected account, keyed by credential id. */
1218
2110
  async refreshCodexUsage(workspaceId: string): Promise<{ usage: CodexUsageMap }> {
1219
- 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
+ );
1220
2115
  }
1221
2116
 
1222
2117
  /** Disconnect ALL accounts (legacy workspace-wide). Prefer `disconnectCodexAccount`. */
1223
2118
  async codexDisconnect(workspaceId: string): Promise<{ disconnected: boolean }> {
1224
- 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
+ );
1225
2123
  }
1226
2124
 
1227
2125
  /** List every connected Codex account + the workspace active pointer + settings. */
1228
2126
  async listCodexAccounts(workspaceId: string): Promise<CodexAccountsResponse> {
1229
- 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
+ );
1230
2131
  }
1231
2132
 
1232
2133
  /** Switch the workspace ACTIVE Codex account (the one unpinned sessions use). */
1233
- async activateCodexAccount(workspaceId: string, accountId: string): Promise<{ activated: boolean; accountId: string }> {
1234
- 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
+ );
1235
2142
  }
1236
2143
 
1237
2144
  /** P3: enable/disable Codex auto-rotation and/or pick the strategy. Returns the effective settings. */
1238
2145
  async setCodexRotationSettings(
1239
2146
  workspaceId: string,
1240
- patch: { rotationEnabled?: boolean; rotationStrategy?: CodexRotationSettings["rotationStrategy"] },
2147
+ patch: {
2148
+ rotationEnabled?: boolean;
2149
+ rotationStrategy?: CodexRotationSettings["rotationStrategy"];
2150
+ },
1241
2151
  ): Promise<CodexRotationSettings> {
1242
- 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
+ );
1243
2157
  }
1244
2158
 
1245
2159
  /** Disconnect ONE Codex account by id (re-picks active when the removed one was active). */
1246
- async disconnectCodexAccount(workspaceId: string, accountId: string): Promise<{ disconnected: boolean; newActiveId: string | null }> {
1247
- 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
+ );
1248
2168
  }
1249
2169
 
1250
2170
  /** Rename a Codex account (label only in P1). */
1251
- async renameCodexAccount(workspaceId: string, accountId: string, label: string | null): Promise<CodexAccount> {
1252
- 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
+ );
1253
2181
  }
1254
2182
 
1255
2183
  /** Pin (or unpin via "auto") a session's Codex account. Applies on the next turn. */
1256
- async pinSessionCodexAccount(workspaceId: string, sessionId: string, target: string): Promise<{ pinned: string }> {
1257
- 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
+ );
1258
2194
  }
1259
2195
 
1260
- 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> {
1261
2202
  const response = await this.fetchImpl(this.url(path, query), {
1262
2203
  method,
1263
2204
  headers: {
@@ -1297,7 +2238,3 @@ async function safeText(response: Response): Promise<string> {
1297
2238
  return "";
1298
2239
  }
1299
2240
  }
1300
-
1301
- function delay(ms: number): Promise<void> {
1302
- return new Promise((resolve) => setTimeout(resolve, ms));
1303
- }