@opengeni/sdk 0.11.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,6 +9,16 @@ var OpenGeniApiError = class extends Error {
9
9
  this.body = body;
10
10
  }
11
11
  };
12
+ var OpenGeniApiContractMismatchError = class extends Error {
13
+ expected;
14
+ actual;
15
+ constructor(expected, actual) {
16
+ super(`OpenGeni API contract mismatch: client expects ${expected}, API serves ${actual}`);
17
+ this.name = "OpenGeniApiContractMismatchError";
18
+ this.expected = expected;
19
+ this.actual = actual;
20
+ }
21
+ };
12
22
  var OpenGeniStreamError = class extends Error {
13
23
  constructor(message) {
14
24
  super(message);
@@ -105,7 +115,8 @@ async function* streamSessionEvents(transport, options = {}) {
105
115
  let failedAttempts = 0;
106
116
  let delayMs = baseDelayMs;
107
117
  let everConnected = false;
108
- while (!signal?.aborted) {
118
+ while (true) {
119
+ if (signal?.aborted) break;
109
120
  options.onStateChange?.(everConnected || failedAttempts > 0 ? "reconnecting" : "connecting");
110
121
  const cursorAtOpen = cursor;
111
122
  try {
@@ -113,6 +124,7 @@ async function* streamSessionEvents(transport, options = {}) {
113
124
  everConnected = true;
114
125
  failedAttempts = 0;
115
126
  delayMs = baseDelayMs;
127
+ await options.beforeLive?.();
116
128
  options.onStateChange?.("live");
117
129
  for await (const message of parseSseStream(body)) {
118
130
  if (signal?.aborted) {
@@ -207,6 +219,246 @@ async function sleep(delayMs, signal) {
207
219
  });
208
220
  }
209
221
 
222
+ // src/workspace-control-stream.ts
223
+ async function* streamWorkspaceControlEvents(transport, options = {}) {
224
+ const signal = options.signal;
225
+ const reconnect = options.reconnect ?? true;
226
+ const baseDelayMs = options.reconnectDelayMs ?? 500;
227
+ const maxDelayMs = options.maxReconnectDelayMs ?? 1e4;
228
+ const maxAttempts = options.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
229
+ let cursor = options.after ?? 0;
230
+ let failures = 0;
231
+ let delayMs = baseDelayMs;
232
+ let everConnected = false;
233
+ for (; ; ) {
234
+ if (signal?.aborted) return;
235
+ options.onStateChange?.(everConnected || failures > 0 ? "reconnecting" : "connecting");
236
+ const cursorAtOpen = cursor;
237
+ try {
238
+ const body = await transport.openStream(cursor, signal);
239
+ everConnected = true;
240
+ failures = 0;
241
+ delayMs = baseDelayMs;
242
+ await options.beforeLive?.();
243
+ options.onStateChange?.("live");
244
+ for await (const message of parseSseStream(body)) {
245
+ if (signal?.aborted) return;
246
+ const event = parseWorkspaceControlEvent(message.data);
247
+ if (!event || event.sequence <= cursor) continue;
248
+ cursor = event.sequence;
249
+ yield event;
250
+ }
251
+ if (!reconnect) return;
252
+ if (cursor === cursorAtOpen) await sleep2(baseDelayMs, signal);
253
+ continue;
254
+ } catch (error) {
255
+ if (signal?.aborted || isAbortError(error)) return;
256
+ if (!reconnect || !isRetryableStreamError(error)) throw error;
257
+ failures += 1;
258
+ if (failures > maxAttempts) {
259
+ throw new OpenGeniStreamError(
260
+ `workspace control stream gave up after ${maxAttempts} reconnect attempts: ${error instanceof Error ? error.message : String(error)}`
261
+ );
262
+ }
263
+ }
264
+ await sleep2(delayMs, signal);
265
+ delayMs = Math.min(Math.max(delayMs * 2, baseDelayMs), maxDelayMs);
266
+ }
267
+ }
268
+ function parseWorkspaceControlEvent(data) {
269
+ let value;
270
+ try {
271
+ value = JSON.parse(data);
272
+ } catch {
273
+ return null;
274
+ }
275
+ if (typeof value !== "object" || value === null || value.type !== "workspace.control.changed" || typeof value.id !== "string" || typeof value.sequence !== "number") {
276
+ return null;
277
+ }
278
+ return value;
279
+ }
280
+ async function sleep2(delayMs, signal) {
281
+ if (signal?.aborted || delayMs <= 0) return;
282
+ await new Promise((resolve) => {
283
+ const timer = setTimeout(done, delayMs);
284
+ function done() {
285
+ clearTimeout(timer);
286
+ signal?.removeEventListener("abort", done);
287
+ resolve();
288
+ }
289
+ signal?.addEventListener("abort", done, { once: true });
290
+ });
291
+ }
292
+
293
+ // src/types.ts
294
+ var SESSION_EVENT_TYPES = [
295
+ "session.created",
296
+ "session.status.changed",
297
+ "session.requiresAction",
298
+ "session.context.compaction.requested",
299
+ "session.context.compacted",
300
+ "session.context.compaction.skipped",
301
+ "session.context.cleared",
302
+ "user.message",
303
+ "user.pause",
304
+ "user.approvalDecision",
305
+ "turn.queued",
306
+ "turn.started",
307
+ "turn.completed",
308
+ "turn.failed",
309
+ "turn.cancelled",
310
+ "turn.superseded",
311
+ "turn.recovery.requested",
312
+ "turn.capacity_waiting",
313
+ "agent.message.delta",
314
+ "agent.message.completed",
315
+ "agent.reasoning.delta",
316
+ "agent.toolCall.created",
317
+ "agent.toolCall.output",
318
+ "agent.model.usage",
319
+ "tool.auth_needed",
320
+ "agent.updated",
321
+ "rig.setup.started",
322
+ "rig.setup.completed",
323
+ "rig.setup.skipped",
324
+ "rig.setup.failed",
325
+ "sandbox.operation.started",
326
+ "sandbox.operation.completed",
327
+ "sandbox.operation.failed",
328
+ "sandbox.command.output.delta",
329
+ "artifact.created",
330
+ "goal.set",
331
+ "goal.updated",
332
+ "goal.completed",
333
+ "goal.paused",
334
+ "goal.resumed",
335
+ "goal.cleared",
336
+ "goal.continuation",
337
+ "system.update.pending",
338
+ "system.update.delivered",
339
+ "session.control.paused",
340
+ "session.control.resumed",
341
+ "session.control.steer_requested",
342
+ "workspace.inference.paused",
343
+ "workspace.inference.resumed",
344
+ "session.queue.changed",
345
+ "session.queue.prompt.cancelled",
346
+ "session.queue.history",
347
+ "turn.event.rejected_late",
348
+ "memory.saved",
349
+ "memory.corrected",
350
+ // Channel-B desktop pixel-plane signals (mirror of contracts SessionEventType;
351
+ // the contract-parity test asserts sorted equality).
352
+ "stream.url.rotated",
353
+ "stream.opened",
354
+ "stream.closed",
355
+ "stream.revoked",
356
+ // Channel-B recording signals (P4.3 — "agent films itself proving the fix").
357
+ "recording.started",
358
+ "recording.available",
359
+ "recording.failed",
360
+ // Channel-A structured-service notifications (P4.4; mirror of contracts
361
+ // SessionEventType — the contract-parity test asserts sorted equality).
362
+ "fs.changed",
363
+ "git.changed",
364
+ "terminal.pty.started",
365
+ "terminal.pty.output.delta",
366
+ "terminal.pty.exited",
367
+ "session.title_set",
368
+ // Multi-account Codex (P1): the session's inference account changed.
369
+ "codex.account.switched",
370
+ // OPE-21 metadata-only per-turn credential selection audit.
371
+ "codex.credential.selected",
372
+ // OPE-21 durable zero-capacity wait lifecycle. These are system/runtime
373
+ // events, never synthetic user messages.
374
+ "codex.capacity.waiting",
375
+ "codex.capacity.resumed",
376
+ "codex.capacity.superseded",
377
+ // Sandbox durability observability (mirror of contracts SessionEventType):
378
+ // box lifecycle + manifest-env drift, attributable from the DB alone.
379
+ "sandbox.box.created",
380
+ "sandbox.box.lost",
381
+ "sandbox.box.terminated",
382
+ "sandbox.box.snapshot",
383
+ "sandbox.env.drift",
384
+ // Active-sandbox pointer reconcile (issue #341; announce-only; mirror of contracts
385
+ // SessionEventType — the contract-parity test asserts sorted equality).
386
+ "session.route.reconciled",
387
+ // Workbench v2 turn-end workspace capture (announce-only; mirror of contracts
388
+ // SessionEventType — the contract-parity test asserts sorted equality).
389
+ "workspace.revision.captured",
390
+ "workspace.revision.degraded",
391
+ // Connected Machine op-outcome observability (announce-only, quiet; mirror of
392
+ // contracts SessionEventType — the contract-parity test asserts sorted equality).
393
+ "machine.op.failed",
394
+ "machine.op.recovered",
395
+ // Connected Machine link-plane observability (announce-only, quiet; mirror of
396
+ // contracts SessionEventType — the contract-parity test asserts sorted equality).
397
+ "machine.link.lost",
398
+ "machine.link.restored",
399
+ "machine.runner.restarted"
400
+ ];
401
+ var KNOWN_PERMISSIONS = [
402
+ "account:read",
403
+ "account:admin",
404
+ "members:manage",
405
+ "workspace:create",
406
+ "billing:read",
407
+ "billing:manage",
408
+ "workspace:read",
409
+ "workspace:admin",
410
+ "sessions:create",
411
+ "sessions:read",
412
+ "sessions:control",
413
+ // Sandbox-surfacing (mirror of @opengeni/contracts Permission). stream:view is
414
+ // strictly broader than sessions:read (un-redacted pixels); stream:control is
415
+ // the never-granted-v1 raw-input plane; stream:acknowledge is the secret-leak
416
+ // consent gate.
417
+ "stream:view",
418
+ "stream:control",
419
+ "stream:acknowledge",
420
+ "files:upload",
421
+ "files:read",
422
+ "files:write",
423
+ "terminal:attach",
424
+ "documents:manage",
425
+ "documents:search",
426
+ "scheduled_tasks:manage",
427
+ "scheduled_tasks:run",
428
+ "github:manage",
429
+ "github:use",
430
+ "api_keys:manage",
431
+ "connections:read",
432
+ "connections:write",
433
+ "environments:manage",
434
+ "environments:use",
435
+ "variable-sets:manage",
436
+ "variable-sets:use",
437
+ "mcp_servers:attach",
438
+ "toolspace:call",
439
+ "goals:manage",
440
+ "enrollments:read",
441
+ "enrollments:manage",
442
+ "rigs:use",
443
+ "rigs:manage"
444
+ ];
445
+ var OPENGENI_API_CONTRACT_REVISION = "2026-07-session-control-v1";
446
+ var OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract";
447
+ var KNOWN_USAGE_EVENT_TYPES = [
448
+ "agent_run.created",
449
+ "agent_run.completed",
450
+ "model.tokens",
451
+ "model.cost",
452
+ "file.uploaded",
453
+ "file.deleted",
454
+ "document.indexed",
455
+ "scheduled_task.fired",
456
+ "api_key.request",
457
+ // sandbox warm-time metering (P2.1) — mirrors contracts UsageEventType.
458
+ "sandbox.warm_seconds",
459
+ "sandbox.warm_cost"
460
+ ];
461
+
210
462
  // src/client.ts
211
463
  var OpenGeniClient = class {
212
464
  baseUrl;
@@ -219,23 +471,79 @@ var OpenGeniClient = class {
219
471
  }
220
472
  // --- Session lifecycle ---------------------------------------------------
221
473
  async createSession(workspaceId, request) {
222
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions`, request);
474
+ return await this.requestJson(
475
+ "POST",
476
+ `/v1/workspaces/${workspaceId}/sessions`,
477
+ request
478
+ );
223
479
  }
224
480
  async getSession(workspaceId, sessionId) {
225
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}`);
481
+ return await this.requestJson(
482
+ "GET",
483
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}`
484
+ );
226
485
  }
227
486
  async updateSession(workspaceId, sessionId, request) {
228
- return await this.requestJson("PATCH", `/v1/workspaces/${workspaceId}/sessions/${sessionId}`, request);
487
+ return await this.requestJson(
488
+ "PATCH",
489
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}`,
490
+ request
491
+ );
229
492
  }
230
493
  async listSessions(workspaceId, options = {}) {
231
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/sessions`, void 0, {
232
- ...options.limit !== void 0 ? { limit: String(options.limit) } : {}
233
- });
494
+ return await this.requestJson(
495
+ "GET",
496
+ `/v1/workspaces/${workspaceId}/sessions`,
497
+ void 0,
498
+ {
499
+ ...options.limit !== void 0 ? { limit: String(options.limit) } : {},
500
+ ...options.search?.trim() ? { search: options.search.trim() } : {},
501
+ ...Object.prototype.hasOwnProperty.call(options, "parentSessionId") && options.parentSessionId !== void 0 ? {
502
+ parentSessionId: options.parentSessionId === null ? "null" : String(options.parentSessionId)
503
+ } : {}
504
+ }
505
+ );
506
+ }
507
+ /** Pin-aware ordinary-session page with a stable keyset cursor. */
508
+ async listSessionPage(workspaceId, options = {}) {
509
+ return await this.requestJson(
510
+ "GET",
511
+ `/v1/workspaces/${workspaceId}/sessions`,
512
+ void 0,
513
+ {
514
+ view: "page",
515
+ ...options.limit !== void 0 ? { limit: String(options.limit) } : {},
516
+ ...options.cursor !== void 0 ? { cursor: options.cursor } : {},
517
+ ...options.search?.trim() ? { search: options.search.trim() } : {},
518
+ ...Object.prototype.hasOwnProperty.call(options, "parentSessionId") && options.parentSessionId !== void 0 ? {
519
+ parentSessionId: options.parentSessionId === null ? "null" : String(options.parentSessionId)
520
+ } : {}
521
+ }
522
+ );
523
+ }
524
+ /** Set this authenticated member's personal workspace pin for a session. */
525
+ async updateSessionPin(workspaceId, sessionId, request) {
526
+ return await this.requestJson(
527
+ "PUT",
528
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/pin`,
529
+ request
530
+ );
531
+ }
532
+ async getSessionLineage(workspaceId, sessionId) {
533
+ return await this.requestJson(
534
+ "GET",
535
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`
536
+ );
234
537
  }
235
538
  async listTurns(workspaceId, sessionId, options = {}) {
236
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns`, void 0, {
237
- ...options.limit !== void 0 ? { limit: String(options.limit) } : {}
238
- });
539
+ return await this.requestJson(
540
+ "GET",
541
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns`,
542
+ void 0,
543
+ {
544
+ ...options.limit !== void 0 ? { limit: String(options.limit) } : {}
545
+ }
546
+ );
239
547
  }
240
548
  // --- Bring-your-own-compute: Machines dashboard + metrics (M10) ------------
241
549
  /**
@@ -245,9 +553,14 @@ var OpenGeniClient = class {
245
553
  * session's synthetic Modal group box + the active-sandbox pointer.
246
554
  */
247
555
  async listMachines(workspaceId, options = {}) {
248
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/machines`, void 0, {
249
- ...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {}
250
- });
556
+ return await this.requestJson(
557
+ "GET",
558
+ `/v1/workspaces/${workspaceId}/machines`,
559
+ void 0,
560
+ {
561
+ ...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {}
562
+ }
563
+ );
251
564
  }
252
565
  /**
253
566
  * Read the downsampled (~1/min) metrics series for ONE machine over a time
@@ -273,7 +586,11 @@ var OpenGeniClient = class {
273
586
  * the request.
274
587
  */
275
588
  async lookupDeviceEnrollment(userCode) {
276
- return await this.requestJson("POST", "/v1/enrollments/device/lookup", { userCode });
589
+ return await this.requestJson(
590
+ "POST",
591
+ "/v1/enrollments/device/lookup",
592
+ { userCode }
593
+ );
277
594
  }
278
595
  /**
279
596
  * Approve a pending device-enrollment flow (the LOUD consent step). `allowScreenControl`
@@ -325,12 +642,20 @@ var OpenGeniClient = class {
325
642
  }
326
643
  // --- Scheduled tasks -------------------------------------------------------
327
644
  async listScheduledTasks(workspaceId, options = {}) {
328
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/scheduled-tasks`, void 0, {
329
- ...options.limit !== void 0 ? { limit: String(options.limit) } : {}
330
- });
645
+ return await this.requestJson(
646
+ "GET",
647
+ `/v1/workspaces/${workspaceId}/scheduled-tasks`,
648
+ void 0,
649
+ {
650
+ ...options.limit !== void 0 ? { limit: String(options.limit) } : {}
651
+ }
652
+ );
331
653
  }
332
654
  async getScheduledTask(workspaceId, taskId) {
333
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`);
655
+ return await this.requestJson(
656
+ "GET",
657
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`
658
+ );
334
659
  }
335
660
  // --- Events: replay, send, stream ----------------------------------------
336
661
  /**
@@ -340,16 +665,25 @@ var OpenGeniClient = class {
340
665
  * for resume cursors.
341
666
  */
342
667
  async listEvents(workspaceId, sessionId, options = {}) {
343
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, void 0, {
344
- ...options.after !== void 0 ? { after: String(options.after) } : {},
345
- ...options.before !== void 0 ? { before: String(options.before) } : {},
346
- ...options.limit !== void 0 ? { limit: String(options.limit) } : {},
347
- ...options.compact ? { compact: "1" } : {}
348
- });
668
+ return await this.requestJson(
669
+ "GET",
670
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`,
671
+ void 0,
672
+ {
673
+ ...options.after !== void 0 ? { after: String(options.after) } : {},
674
+ ...options.before !== void 0 ? { before: String(options.before) } : {},
675
+ ...options.limit !== void 0 ? { limit: String(options.limit) } : {},
676
+ ...options.compact ? { compact: "1" } : {}
677
+ }
678
+ );
349
679
  }
350
680
  /** POST a user/control event to the session. Returns the accepted event. */
351
681
  async sendEvent(workspaceId, sessionId, event) {
352
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, event);
682
+ return await this.requestJson(
683
+ "POST",
684
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`,
685
+ event
686
+ );
353
687
  }
354
688
  async sendMessage(workspaceId, sessionId, message) {
355
689
  const input = typeof message === "string" ? { text: message } : message;
@@ -360,11 +694,12 @@ var OpenGeniClient = class {
360
694
  payload
361
695
  });
362
696
  }
363
- async interrupt(workspaceId, sessionId, options = {}) {
364
- return await this.sendEvent(workspaceId, sessionId, {
365
- type: "user.interrupt",
366
- ...options.clientEventId !== void 0 ? { clientEventId: options.clientEventId } : {},
367
- payload: options.reason !== void 0 ? { reason: options.reason } : {}
697
+ async pauseSession(workspaceId, sessionId, options = {}) {
698
+ return await this.controlSession(workspaceId, sessionId, {
699
+ action: "pause",
700
+ clientEventId: options.clientEventId ?? crypto.randomUUID(),
701
+ ...options.reason ? { reason: options.reason } : {},
702
+ ...options.expectedControlEtag ? { expectedControlEtag: options.expectedControlEtag } : {}
368
703
  });
369
704
  }
370
705
  async sendApprovalDecision(workspaceId, sessionId, decision) {
@@ -386,7 +721,10 @@ var OpenGeniClient = class {
386
721
  /** The transport `streamEvents` runs on; useful for custom streaming layers. */
387
722
  eventStreamTransport(workspaceId, sessionId) {
388
723
  return {
389
- openStream: async (after, signal) => await this.openEventStream(workspaceId, sessionId, { after, ...signal ? { signal } : {} }),
724
+ openStream: async (after, signal) => await this.openEventStream(workspaceId, sessionId, {
725
+ after,
726
+ ...signal ? { signal } : {}
727
+ }),
390
728
  listEvents: async (after, limit) => await this.listEvents(workspaceId, sessionId, { after, limit })
391
729
  };
392
730
  }
@@ -400,6 +738,7 @@ var OpenGeniClient = class {
400
738
  headers: { ...this.headers(), Accept: "text/event-stream" },
401
739
  ...options.signal ? { signal: options.signal } : {}
402
740
  });
741
+ assertApiContractResponse(response);
403
742
  if (!response.ok) {
404
743
  throw new OpenGeniApiError(response.status, await safeText(response));
405
744
  }
@@ -409,87 +748,144 @@ var OpenGeniClient = class {
409
748
  return response.body;
410
749
  }
411
750
  // --- Turn queue ------------------------------------------------------------
412
- /** Edit a still-queued turn (prompt, model, resources, tools, ...). */
413
- async updateQueuedTurn(workspaceId, sessionId, turnId, update) {
751
+ async getQueue(workspaceId, sessionId) {
414
752
  return await this.requestJson(
415
- "PATCH",
416
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns/${turnId}`,
417
- update
753
+ "GET",
754
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`
418
755
  );
419
756
  }
420
- /**
421
- * Reorder the queued turns. `turnIds` must all reference queued turns; the
422
- * server assigns positions in the given order and returns the queue.
423
- */
424
- async reorderQueuedTurns(workspaceId, sessionId, turnIds) {
757
+ async moveQueueItem(workspaceId, sessionId, turnId, request) {
425
758
  return await this.requestJson(
426
759
  "POST",
427
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns/reorder`,
428
- { turnIds }
760
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/move`,
761
+ request
429
762
  );
430
763
  }
431
- /** Cancel a queued turn before it is claimed. Returns the cancelled turn. */
432
- async deleteQueuedTurn(workspaceId, sessionId, turnId) {
764
+ async editQueueItem(workspaceId, sessionId, turnId, request) {
433
765
  return await this.requestJson(
434
- "DELETE",
435
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns/${turnId}`
766
+ "POST",
767
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/edit`,
768
+ request
436
769
  );
437
770
  }
438
- /**
439
- * Steer: deliver a message *now* instead of behind the queue. Sends the
440
- * message, promotes its queued turn to the front, and interrupts the
441
- * running turn so the session picks the steer turn up next. On a session
442
- * that is not running this degrades gracefully to a plain queued message.
443
- *
444
- * The steer turn is located by `triggerEventId` across ALL turns (retried
445
- * briefly in case the server is still materializing it) — not just the
446
- * queued ones, because the worker can claim the steer turn before it is
447
- * ever observed queued, and a claimed steer turn means the message is
448
- * already being delivered: interrupting then would cancel the very message
449
- * being steered. If the turn cannot be found while other turns are queued,
450
- * the interrupt is also skipped — stopping the running turn would otherwise
451
- * promote someone else's queued work over this message — and the call
452
- * degrades to a plain queued send (`interrupted: false`).
453
- */
454
- async steerMessage(workspaceId, sessionId, message) {
455
- const accepted = await this.sendMessage(workspaceId, sessionId, message);
456
- let steerTurn = null;
457
- let queued = [];
458
- for (let attempt = 0; attempt < 4; attempt += 1) {
459
- if (attempt > 0) {
460
- await delay(150 * attempt);
461
- }
462
- const turns = await this.listTurns(workspaceId, sessionId);
463
- queued = turns.filter((turn) => turn.status === "queued").sort((a, b) => a.position - b.position || a.createdAt.localeCompare(b.createdAt));
464
- steerTurn = turns.find((turn) => turn.triggerEventId === accepted.id) ?? null;
465
- if (steerTurn) {
466
- break;
467
- }
468
- }
469
- const steerTurnQueued = steerTurn?.status === "queued";
470
- if (steerTurn && steerTurnQueued && queued.length > 1) {
471
- const front = steerTurn;
472
- await this.reorderQueuedTurns(workspaceId, sessionId, [
473
- front.id,
474
- ...queued.filter((turn) => turn.id !== front.id).map((turn) => turn.id)
475
- ]);
476
- }
477
- const canDeliverNext = steerTurnQueued || steerTurn === null && queued.length === 0;
478
- const session = await this.getSession(workspaceId, sessionId);
479
- const steerTurnAlreadyActive = steerTurn !== null && session.activeTurnId === steerTurn.id;
480
- const interrupted = canDeliverNext && !steerTurnAlreadyActive && (session.status === "running" || session.status === "requires_action");
481
- if (interrupted) {
482
- await this.interrupt(workspaceId, sessionId, { reason: "steer" });
483
- }
484
- return { accepted, turn: steerTurn, interrupted };
485
- }
486
- // --- Goals -------------------------------------------------------------------
487
- /** The session's goal. 404s when the session never had one. */
488
- async getGoal(workspaceId, sessionId) {
489
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`);
490
- }
491
- async updateGoal(workspaceId, sessionId, request) {
492
- return await this.requestJson("PATCH", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`, request);
771
+ async steerQueueItem(workspaceId, sessionId, turnId, request) {
772
+ return await this.requestJson(
773
+ "POST",
774
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/steer`,
775
+ request
776
+ );
777
+ }
778
+ async deleteQueueItem(workspaceId, sessionId, turnId, request) {
779
+ return await this.requestJson(
780
+ "POST",
781
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/delete`,
782
+ request
783
+ );
784
+ }
785
+ async getComposerDraft(workspaceId, sessionId) {
786
+ return await this.requestJson(
787
+ "GET",
788
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`
789
+ );
790
+ }
791
+ async saveComposerDraft(workspaceId, sessionId, request) {
792
+ return await this.requestJson(
793
+ "PUT",
794
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`,
795
+ request
796
+ );
797
+ }
798
+ async controlSession(workspaceId, sessionId, request) {
799
+ return await this.requestJson(
800
+ "POST",
801
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/control`,
802
+ request
803
+ );
804
+ }
805
+ async resumeSession(workspaceId, sessionId, options = {}) {
806
+ return await this.controlSession(workspaceId, sessionId, {
807
+ action: "resume",
808
+ clientEventId: options.clientEventId ?? crypto.randomUUID(),
809
+ ...options.reason ? { reason: options.reason } : {},
810
+ ...options.expectedControlEtag ? { expectedControlEtag: options.expectedControlEtag } : {}
811
+ });
812
+ }
813
+ async setWorkspaceInferenceState(workspaceId, request) {
814
+ return await this.requestJson(
815
+ "POST",
816
+ `/v1/workspaces/${workspaceId}/inference-control`,
817
+ request
818
+ );
819
+ }
820
+ async listWorkspaceControlEvents(workspaceId, options = {}) {
821
+ return await this.requestJson(
822
+ "GET",
823
+ `/v1/workspaces/${workspaceId}/control-events`,
824
+ void 0,
825
+ {
826
+ ...options.after !== void 0 ? { after: String(options.after) } : {},
827
+ ...options.limit !== void 0 ? { limit: String(options.limit) } : {}
828
+ }
829
+ );
830
+ }
831
+ streamWorkspaceControlEvents(workspaceId, options = {}) {
832
+ return streamWorkspaceControlEvents(this.workspaceControlStreamTransport(workspaceId), options);
833
+ }
834
+ workspaceControlStreamTransport(workspaceId) {
835
+ return {
836
+ openStream: async (after, signal) => await this.openWorkspaceControlEventStream(workspaceId, {
837
+ after,
838
+ ...signal ? { signal } : {}
839
+ })
840
+ };
841
+ }
842
+ async openWorkspaceControlEventStream(workspaceId, options = {}) {
843
+ const response = await this.fetchImpl(
844
+ this.url(`/v1/workspaces/${workspaceId}/control-events/stream`, {
845
+ after: String(options.after ?? 0)
846
+ }),
847
+ {
848
+ method: "GET",
849
+ headers: { ...this.headers(), Accept: "text/event-stream" },
850
+ ...options.signal ? { signal: options.signal } : {}
851
+ }
852
+ );
853
+ assertApiContractResponse(response);
854
+ if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
855
+ if (!response.body) {
856
+ throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
857
+ }
858
+ return response.body;
859
+ }
860
+ /**
861
+ * Steer: atomically put this prompt at the head and supersede the current
862
+ * inference. The client performs one request and renders server order.
863
+ */
864
+ async steerMessage(workspaceId, sessionId, message) {
865
+ const input = typeof message === "string" ? { text: message } : message;
866
+ return await this.requestJson(
867
+ "POST",
868
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/steer`,
869
+ input
870
+ );
871
+ }
872
+ // --- Goals -------------------------------------------------------------------
873
+ /** The session's goal. 404s when the session never had one. */
874
+ async getGoal(workspaceId, sessionId) {
875
+ return await this.requestJson(
876
+ "GET",
877
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`
878
+ );
879
+ }
880
+ async updateGoal(workspaceId, sessionId, request) {
881
+ return await this.requestJson(
882
+ "PATCH",
883
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`,
884
+ request
885
+ );
886
+ }
887
+ async deleteGoal(workspaceId, sessionId) {
888
+ await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`);
493
889
  }
494
890
  /** Pause the goal loop: the session stops self-continuing until resumed. */
495
891
  async pauseGoal(workspaceId, sessionId, options = {}) {
@@ -511,16 +907,19 @@ var OpenGeniClient = class {
511
907
  * context — the destructive intent is explicit on the wire.
512
908
  */
513
909
  async clearSessionContext(workspaceId, sessionId) {
514
- await this.requestVoid("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/context/clear`, { confirm: true });
910
+ await this.requestVoid(
911
+ "POST",
912
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/context/clear`,
913
+ { confirm: true }
914
+ );
515
915
  }
516
- /**
517
- * Trigger conversation compaction now. On the client-managed (Azure) path this
518
- * queues a forced compaction the worker honors before the next turn
519
- * (`status:"queued"`); on a server-managed provider or when compaction is off
520
- * it is a no-op (`status:"noop"`) with an explanatory message.
521
- */
916
+ /** Request one durable portable compaction at the next safe model boundary. */
522
917
  async compactSessionContext(workspaceId, sessionId) {
523
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/context/compact`, {});
918
+ return await this.requestJson(
919
+ "POST",
920
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/context/compact`,
921
+ {}
922
+ );
524
923
  }
525
924
  // --- Channel-A structured services (P4.4) ------------------------------------
526
925
  // FileSystem (Pierre tree), Git (Pierre diff), Terminal (exec + PTY). Each is a
@@ -528,64 +927,147 @@ var OpenGeniClient = class {
528
927
  // notifications + the PTY output stream arrive on the existing event SSE.
529
928
  /** FileSystem: list a directory tree (feeds the Pierre file tree). */
530
929
  async fsList(workspaceId, sessionId, request = {}) {
531
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list`, request);
930
+ return await this.requestJson(
931
+ "POST",
932
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list`,
933
+ request
934
+ );
532
935
  }
533
936
  /** FileSystem: read a file (text or base64; binary-safe, size-capped). */
534
937
  async fsRead(workspaceId, sessionId, request) {
535
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/read`, request);
938
+ return await this.requestJson(
939
+ "POST",
940
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/read`,
941
+ request
942
+ );
536
943
  }
537
944
  /** FileSystem: write a file (last-writer-wins; emits fs.changed). */
538
945
  async fsWrite(workspaceId, sessionId, request) {
539
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/write`, request);
946
+ return await this.requestJson(
947
+ "POST",
948
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/write`,
949
+ request
950
+ );
540
951
  }
541
952
  /** FileSystem: delete a path (emits fs.changed). */
542
953
  async fsDelete(workspaceId, sessionId, request) {
543
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/delete`, request);
954
+ return await this.requestJson(
955
+ "POST",
956
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/delete`,
957
+ request
958
+ );
544
959
  }
545
960
  /** FileSystem: move/rename a path (emits fs.changed; 409 if destination exists and overwrite is false). */
546
961
  async fsMove(workspaceId, sessionId, request) {
547
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/move`, request);
962
+ return await this.requestJson(
963
+ "POST",
964
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/move`,
965
+ request
966
+ );
548
967
  }
549
968
  /** FileSystem: create a directory (emits fs.changed; recursive defaults to true). */
550
969
  async fsMkdir(workspaceId, sessionId, request) {
551
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/mkdir`, request);
970
+ return await this.requestJson(
971
+ "POST",
972
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/mkdir`,
973
+ request
974
+ );
552
975
  }
553
976
  /** Git: working-tree/index status (the Pierre file-status feed). */
554
977
  async gitStatus(workspaceId, sessionId, request = {}) {
555
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/status`, request);
978
+ return await this.requestJson(
979
+ "POST",
980
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/status`,
981
+ request
982
+ );
556
983
  }
557
984
  /** Git: structured diff hunks (the Pierre diff feed). */
558
985
  async gitDiff(workspaceId, sessionId, request = {}) {
559
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/diff`, request);
986
+ return await this.requestJson(
987
+ "POST",
988
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/diff`,
989
+ request
990
+ );
560
991
  }
561
992
  /** Git: commit log. */
562
993
  async gitLog(workspaceId, sessionId, request = {}) {
563
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/log`, request);
994
+ return await this.requestJson(
995
+ "POST",
996
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/log`,
997
+ request
998
+ );
564
999
  }
565
1000
  /** Git: show a commit (diff vs first parent) or fetch a raw blob at a ref. */
566
1001
  async gitShow(workspaceId, sessionId, request) {
567
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/show`, request);
1002
+ return await this.requestJson(
1003
+ "POST",
1004
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/show`,
1005
+ request
1006
+ );
1007
+ }
1008
+ /** Workspace capture: the latest turn-end snapshot of the session's workspace
1009
+ * (tree + per-repo diff + file after-image refs), served from durable storage
1010
+ * WITHOUT warming a machine — the workbench cold-paint source. Returns
1011
+ * `{available:false}` when no capture exists yet (fall back to the live path). */
1012
+ async getWorkspaceCapture(workspaceId, sessionId) {
1013
+ return await this.requestJson(
1014
+ "GET",
1015
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture`
1016
+ );
1017
+ }
1018
+ /** Workspace capture: a single file's after-image from the capture (revision
1019
+ * pins a specific one; omitted → latest). Content is inline for small files,
1020
+ * else a short-TTL signed URL; a tooLarge file returns metadata only. */
1021
+ async getWorkspaceCaptureFile(workspaceId, sessionId, path, revision) {
1022
+ const query = { path };
1023
+ if (revision !== void 0) query.revision = String(revision);
1024
+ return await this.requestJson(
1025
+ "GET",
1026
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture/file`,
1027
+ void 0,
1028
+ query
1029
+ );
568
1030
  }
569
1031
  /** Terminal: run a bounded command, returning buffered stdout/stderr inline. */
570
1032
  async terminalExec(workspaceId, sessionId, request) {
571
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/exec`, request);
1033
+ return await this.requestJson(
1034
+ "POST",
1035
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/exec`,
1036
+ request
1037
+ );
572
1038
  }
573
1039
  /** Terminal: open an interactive PTY. Output streams on the event SSE as
574
1040
  * terminal.pty.output.delta; drive it with terminalPtyWrite. */
575
1041
  async terminalPtyOpen(workspaceId, sessionId, request = {}) {
576
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty`, request);
1042
+ return await this.requestJson(
1043
+ "POST",
1044
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty`,
1045
+ request
1046
+ );
577
1047
  }
578
1048
  /** Terminal: send stdin to an open PTY (output rides A1). */
579
1049
  async terminalPtyWrite(workspaceId, sessionId, request) {
580
- await this.requestVoid("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/write`, request);
1050
+ await this.requestVoid(
1051
+ "POST",
1052
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/write`,
1053
+ request
1054
+ );
581
1055
  }
582
1056
  /** Terminal: resize an open PTY. */
583
1057
  async terminalPtyResize(workspaceId, sessionId, request) {
584
- await this.requestVoid("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/resize`, request);
1058
+ await this.requestVoid(
1059
+ "POST",
1060
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/resize`,
1061
+ request
1062
+ );
585
1063
  }
586
1064
  /** Terminal: close an open PTY (idempotent). */
587
1065
  async terminalPtyClose(workspaceId, sessionId, request) {
588
- await this.requestVoid("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/close`, request);
1066
+ await this.requestVoid(
1067
+ "POST",
1068
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/close`,
1069
+ request
1070
+ );
589
1071
  }
590
1072
  // --- Stream surfacing: capability negotiation + viewer lifecycle (Phase 5) ---
591
1073
  // The capability doc is the single source of UI truth (degradation is always a
@@ -641,7 +1123,10 @@ var OpenGeniClient = class {
641
1123
  }
642
1124
  /** Detach a viewer (delete this holder; idempotent delete-my-row). */
643
1125
  async detachViewer(workspaceId, sessionId, viewerId) {
644
- await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers/${viewerId}`);
1126
+ await this.requestVoid(
1127
+ "DELETE",
1128
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers/${viewerId}`
1129
+ );
645
1130
  }
646
1131
  // --- Access + workspaces -----------------------------------------------------
647
1132
  /**
@@ -652,7 +1137,14 @@ var OpenGeniClient = class {
652
1137
  * knowledge of the host setup; safe to call before any auth is established.
653
1138
  */
654
1139
  async getClientConfig() {
655
- return await this.requestJson("GET", "/v1/config/client");
1140
+ const config = await this.requestJson("GET", "/v1/config/client");
1141
+ if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
1142
+ throw new OpenGeniApiContractMismatchError(
1143
+ OPENGENI_API_CONTRACT_REVISION,
1144
+ String(config.apiContractRevision || "(missing)")
1145
+ );
1146
+ }
1147
+ return config;
656
1148
  }
657
1149
  /** The caller's access context: subject, account + workspace grants, defaults. */
658
1150
  async getAccessContext() {
@@ -680,7 +1172,10 @@ var OpenGeniClient = class {
680
1172
  // --- Members ("People with access") -------------------------------------------
681
1173
  /** The workspace's members (user + api_key subjects). */
682
1174
  async listWorkspaceMembers(workspaceId) {
683
- const response = await this.requestJson("GET", `/v1/workspaces/${workspaceId}/members`);
1175
+ const response = await this.requestJson(
1176
+ "GET",
1177
+ `/v1/workspaces/${workspaceId}/members`
1178
+ );
684
1179
  return response.members;
685
1180
  }
686
1181
  /**
@@ -688,7 +1183,11 @@ var OpenGeniClient = class {
688
1183
  * exists (email invites for unknown users are deferred).
689
1184
  */
690
1185
  async addWorkspaceMember(workspaceId, request) {
691
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/members`, request);
1186
+ return await this.requestJson(
1187
+ "POST",
1188
+ `/v1/workspaces/${workspaceId}/members`,
1189
+ request
1190
+ );
692
1191
  }
693
1192
  async updateWorkspaceMember(workspaceId, subjectId, request) {
694
1193
  return await this.requestJson(
@@ -702,20 +1201,37 @@ var OpenGeniClient = class {
702
1201
  * member who can still manage the workspace.
703
1202
  */
704
1203
  async removeWorkspaceMember(workspaceId, subjectId) {
705
- await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/members/${encodeURIComponent(subjectId)}`);
1204
+ await this.requestVoid(
1205
+ "DELETE",
1206
+ `/v1/workspaces/${workspaceId}/members/${encodeURIComponent(subjectId)}`
1207
+ );
706
1208
  }
707
1209
  // --- Scheduled tasks (write + runs) -------------------------------------------
708
1210
  async createScheduledTask(workspaceId, request) {
709
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/scheduled-tasks`, request);
1211
+ return await this.requestJson(
1212
+ "POST",
1213
+ `/v1/workspaces/${workspaceId}/scheduled-tasks`,
1214
+ request
1215
+ );
710
1216
  }
711
1217
  async updateScheduledTask(workspaceId, taskId, request) {
712
- return await this.requestJson("PATCH", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`, request);
1218
+ return await this.requestJson(
1219
+ "PATCH",
1220
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`,
1221
+ request
1222
+ );
713
1223
  }
714
1224
  async pauseScheduledTask(workspaceId, taskId) {
715
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/pause`);
1225
+ return await this.requestJson(
1226
+ "POST",
1227
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/pause`
1228
+ );
716
1229
  }
717
1230
  async resumeScheduledTask(workspaceId, taskId) {
718
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/resume`);
1231
+ return await this.requestJson(
1232
+ "POST",
1233
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/resume`
1234
+ );
719
1235
  }
720
1236
  /**
721
1237
  * Fire the task immediately (manual trigger), independent of its schedule.
@@ -730,7 +1246,10 @@ var OpenGeniClient = class {
730
1246
  );
731
1247
  }
732
1248
  async deleteScheduledTask(workspaceId, taskId) {
733
- await this.requestJson("DELETE", `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`);
1249
+ await this.requestJson(
1250
+ "DELETE",
1251
+ `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`
1252
+ );
734
1253
  }
735
1254
  async listScheduledTaskRuns(workspaceId, taskId, options = {}) {
736
1255
  return await this.requestJson(
@@ -740,45 +1259,178 @@ var OpenGeniClient = class {
740
1259
  { ...options.limit !== void 0 ? { limit: String(options.limit) } : {} }
741
1260
  );
742
1261
  }
743
- // --- Environments --------------------------------------------------------------
1262
+ // --- VariableSets --------------------------------------------------------------
744
1263
  // Variable values are write-only: reads return name/version metadata only.
745
- async listEnvironments(workspaceId) {
746
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/environments`);
1264
+ async listVariableSets(workspaceId) {
1265
+ return await this.requestJson(
1266
+ "GET",
1267
+ `/v1/workspaces/${workspaceId}/variable-sets`
1268
+ );
747
1269
  }
748
- async createEnvironment(workspaceId, request) {
749
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/environments`, request);
1270
+ async createVariableSet(workspaceId, request) {
1271
+ return await this.requestJson(
1272
+ "POST",
1273
+ `/v1/workspaces/${workspaceId}/variable-sets`,
1274
+ request
1275
+ );
750
1276
  }
751
- async getEnvironment(workspaceId, environmentId) {
752
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/environments/${environmentId}`);
1277
+ async getVariableSet(workspaceId, variableSetId) {
1278
+ return await this.requestJson(
1279
+ "GET",
1280
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`
1281
+ );
753
1282
  }
754
- async updateEnvironment(workspaceId, environmentId, request) {
1283
+ async updateVariableSet(workspaceId, variableSetId, request) {
755
1284
  return await this.requestJson(
756
1285
  "PATCH",
757
- `/v1/workspaces/${workspaceId}/environments/${environmentId}`,
1286
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`,
758
1287
  request
759
1288
  );
760
1289
  }
761
- async deleteEnvironment(workspaceId, environmentId) {
762
- await this.requestJson("DELETE", `/v1/workspaces/${workspaceId}/environments/${environmentId}`);
1290
+ async deleteVariableSet(workspaceId, variableSetId) {
1291
+ await this.requestJson(
1292
+ "DELETE",
1293
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`
1294
+ );
763
1295
  }
764
1296
  /** Create or rotate a variable. The value never comes back on any read. */
765
- async setEnvironmentVariable(workspaceId, environmentId, name, value) {
1297
+ async setVariableSetVariable(workspaceId, variableSetId, name, value) {
766
1298
  return await this.requestJson(
767
1299
  "PUT",
768
- `/v1/workspaces/${workspaceId}/environments/${environmentId}/variables/${encodeURIComponent(name)}`,
1300
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}/variables/${encodeURIComponent(name)}`,
769
1301
  { value }
770
1302
  );
771
1303
  }
772
- async deleteEnvironmentVariable(workspaceId, environmentId, name) {
1304
+ async deleteVariableSetVariable(workspaceId, variableSetId, name) {
773
1305
  await this.requestJson(
774
1306
  "DELETE",
775
- `/v1/workspaces/${workspaceId}/environments/${environmentId}/variables/${encodeURIComponent(name)}`
1307
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}/variables/${encodeURIComponent(name)}`
776
1308
  );
777
1309
  }
1310
+ // --- Rigs ------------------------------------------------------------------
1311
+ // Workspace-scoped, versioned sandbox machine definitions. rigs:use gates read
1312
+ // + proposeRigChange; rigs:manage gates create / update / delete / activate.
1313
+ async listRigs(workspaceId) {
1314
+ return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/rigs`);
1315
+ }
1316
+ async createRig(workspaceId, request) {
1317
+ return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/rigs`, request);
1318
+ }
1319
+ async getRig(workspaceId, rigId) {
1320
+ return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/rigs/${rigId}`);
1321
+ }
1322
+ async updateRig(workspaceId, rigId, request) {
1323
+ return await this.requestJson(
1324
+ "PATCH",
1325
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}`,
1326
+ request
1327
+ );
1328
+ }
1329
+ async deleteRig(workspaceId, rigId) {
1330
+ await this.requestJson("DELETE", `/v1/workspaces/${workspaceId}/rigs/${rigId}`);
1331
+ }
1332
+ async listRigVersions(workspaceId, rigId) {
1333
+ return await this.requestJson(
1334
+ "GET",
1335
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/versions`
1336
+ );
1337
+ }
1338
+ /** Roll the active version to an existing one (rollback / promote-activate). */
1339
+ async activateRigVersion(workspaceId, rigId, versionId) {
1340
+ return await this.requestJson(
1341
+ "POST",
1342
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/versions/${versionId}/activate`
1343
+ );
1344
+ }
1345
+ async listRigChanges(workspaceId, rigId) {
1346
+ return await this.requestJson(
1347
+ "GET",
1348
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes`
1349
+ );
1350
+ }
1351
+ /** Propose a change against the rig's active version (rigs:use). */
1352
+ async proposeRigChange(workspaceId, rigId, request) {
1353
+ return await this.requestJson(
1354
+ "POST",
1355
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes`,
1356
+ request
1357
+ );
1358
+ }
1359
+ async getRigChange(workspaceId, rigId, changeId) {
1360
+ return await this.requestJson(
1361
+ "GET",
1362
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes/${changeId}`
1363
+ );
1364
+ }
1365
+ /**
1366
+ * Re-run verification for a change (rigs:use). Verification is asynchronous:
1367
+ * this returns the change immediately with status `verifying`; poll
1368
+ * `getRigChange`/`listRigChanges` for the terminal outcome + logs.
1369
+ */
1370
+ async verifyRigChange(workspaceId, rigId, changeId) {
1371
+ return await this.requestJson(
1372
+ "POST",
1373
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes/${changeId}/verify`
1374
+ );
1375
+ }
1376
+ /**
1377
+ * Promote a verified `definition_edit` change into a new active rig version
1378
+ * (rigs:manage). Only valid once the change's verification passed; returns the
1379
+ * newly minted version.
1380
+ */
1381
+ async promoteRigChange(workspaceId, rigId, changeId) {
1382
+ return await this.requestJson(
1383
+ "POST",
1384
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/changes/${changeId}/promote`
1385
+ );
1386
+ }
1387
+ /**
1388
+ * Re-run the active version's checks in a clean throwaway sandbox (rigs:use).
1389
+ * Asynchronous — returns the version id being verified; the outcome lands on
1390
+ * the version's audit trail.
1391
+ */
1392
+ async verifyRig(workspaceId, rigId) {
1393
+ return await this.requestJson(
1394
+ "POST",
1395
+ `/v1/workspaces/${workspaceId}/rigs/${rigId}/verify`
1396
+ );
1397
+ }
1398
+ /** @deprecated use listVariableSets */
1399
+ async listEnvironments(workspaceId) {
1400
+ return await this.listVariableSets(workspaceId);
1401
+ }
1402
+ /** @deprecated use createVariableSet */
1403
+ async createEnvironment(workspaceId, request) {
1404
+ return await this.createVariableSet(workspaceId, request);
1405
+ }
1406
+ /** @deprecated use getVariableSet */
1407
+ async getEnvironment(workspaceId, environmentId) {
1408
+ return await this.getVariableSet(workspaceId, environmentId);
1409
+ }
1410
+ /** @deprecated use updateVariableSet */
1411
+ async updateEnvironment(workspaceId, environmentId, request) {
1412
+ return await this.updateVariableSet(workspaceId, environmentId, request);
1413
+ }
1414
+ /** @deprecated use deleteVariableSet */
1415
+ async deleteEnvironment(workspaceId, environmentId) {
1416
+ await this.deleteVariableSet(workspaceId, environmentId);
1417
+ }
1418
+ /** @deprecated use setVariableSetVariable */
1419
+ async setEnvironmentVariable(workspaceId, environmentId, name, value) {
1420
+ return await this.setVariableSetVariable(workspaceId, environmentId, name, value);
1421
+ }
1422
+ /** @deprecated use deleteVariableSetVariable */
1423
+ async deleteEnvironmentVariable(workspaceId, environmentId, name) {
1424
+ await this.deleteVariableSetVariable(workspaceId, environmentId, name);
1425
+ }
778
1426
  // --- Files -----------------------------------------------------------------------
779
1427
  /** Step 1 of the upload flow: returns the pre-signed PUT target. */
780
1428
  async beginFileUpload(workspaceId, request) {
781
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/files/uploads`, request);
1429
+ return await this.requestJson(
1430
+ "POST",
1431
+ `/v1/workspaces/${workspaceId}/files/uploads`,
1432
+ request
1433
+ );
782
1434
  }
783
1435
  /** Step 3 of the upload flow: server verifies the object and marks it ready. */
784
1436
  async completeFileUpload(workspaceId, uploadId) {
@@ -819,28 +1471,51 @@ var OpenGeniClient = class {
819
1471
  return await this.completeFileUpload(workspaceId, upload.uploadId);
820
1472
  }
821
1473
  async getFile(workspaceId, fileId) {
822
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/files/${fileId}`);
1474
+ return await this.requestJson(
1475
+ "GET",
1476
+ `/v1/workspaces/${workspaceId}/files/${fileId}`
1477
+ );
823
1478
  }
824
1479
  /** Mint a short-lived signed download URL for a ready file. */
825
1480
  async createFileDownloadUrl(workspaceId, fileId) {
826
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/files/${fileId}/download-url`);
1481
+ return await this.requestJson(
1482
+ "POST",
1483
+ `/v1/workspaces/${workspaceId}/files/${fileId}/download-url`
1484
+ );
827
1485
  }
828
1486
  // --- Documents ----------------------------------------------------------------------
829
1487
  async createDocumentBase(workspaceId, request) {
830
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/document-bases`, request);
1488
+ return await this.requestJson(
1489
+ "POST",
1490
+ `/v1/workspaces/${workspaceId}/document-bases`,
1491
+ request
1492
+ );
831
1493
  }
832
1494
  async listDocumentBases(workspaceId) {
833
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/document-bases`);
1495
+ return await this.requestJson(
1496
+ "GET",
1497
+ `/v1/workspaces/${workspaceId}/document-bases`
1498
+ );
834
1499
  }
835
1500
  async getDocumentBase(workspaceId, baseId) {
836
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/document-bases/${baseId}`);
1501
+ return await this.requestJson(
1502
+ "GET",
1503
+ `/v1/workspaces/${workspaceId}/document-bases/${baseId}`
1504
+ );
837
1505
  }
838
1506
  /** Index an uploaded file into the base. The file must be `ready`. */
839
1507
  async addDocument(workspaceId, baseId, request) {
840
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`, request);
1508
+ return await this.requestJson(
1509
+ "POST",
1510
+ `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`,
1511
+ request
1512
+ );
841
1513
  }
842
1514
  async listDocuments(workspaceId, baseId) {
843
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`);
1515
+ return await this.requestJson(
1516
+ "GET",
1517
+ `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`
1518
+ );
844
1519
  }
845
1520
  /** Retry indexing for a failed document. */
846
1521
  async reindexDocument(workspaceId, baseId, documentId) {
@@ -867,7 +1542,11 @@ var OpenGeniClient = class {
867
1542
  );
868
1543
  }
869
1544
  async searchKnowledge(workspaceId, request) {
870
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/knowledge/search`, request);
1545
+ return await this.requestJson(
1546
+ "POST",
1547
+ `/v1/workspaces/${workspaceId}/knowledge/search`,
1548
+ request
1549
+ );
871
1550
  }
872
1551
  async listKnowledgeMemories(workspaceId, request = {}) {
873
1552
  const params = new URLSearchParams();
@@ -877,16 +1556,53 @@ var OpenGeniClient = class {
877
1556
  if (request.scope) params.set("scope", request.scope);
878
1557
  if (request.limit) params.set("limit", String(request.limit));
879
1558
  const query = params.toString();
880
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/knowledge/memories${query ? `?${query}` : ""}`);
1559
+ return await this.requestJson(
1560
+ "GET",
1561
+ `/v1/workspaces/${workspaceId}/knowledge/memories${query ? `?${query}` : ""}`
1562
+ );
881
1563
  }
882
1564
  async getKnowledgeMemory(workspaceId, memoryId) {
883
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/knowledge/memories/${memoryId}`);
1565
+ return await this.requestJson(
1566
+ "GET",
1567
+ `/v1/workspaces/${workspaceId}/knowledge/memories/${memoryId}`
1568
+ );
884
1569
  }
885
1570
  async createKnowledgeMemory(workspaceId, request) {
886
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/knowledge/memories`, request);
1571
+ return await this.requestJson(
1572
+ "POST",
1573
+ `/v1/workspaces/${workspaceId}/knowledge/memories`,
1574
+ request
1575
+ );
887
1576
  }
888
1577
  async updateKnowledgeMemory(workspaceId, memoryId, request) {
889
- return await this.requestJson("PATCH", `/v1/workspaces/${workspaceId}/knowledge/memories/${memoryId}`, request);
1578
+ return await this.requestJson(
1579
+ "PATCH",
1580
+ `/v1/workspaces/${workspaceId}/knowledge/memories/${memoryId}`,
1581
+ request
1582
+ );
1583
+ }
1584
+ /** Hybrid (semantic + keyword) search over the workspace's agent-visible memory. */
1585
+ async searchWorkspaceMemories(workspaceId, request) {
1586
+ return await this.requestJson(
1587
+ "POST",
1588
+ `/v1/workspaces/${workspaceId}/knowledge/memories/search`,
1589
+ request
1590
+ );
1591
+ }
1592
+ /** Deep-merge a settings patch into the workspace (preserves unknown keys). */
1593
+ async updateWorkspaceSettings(workspaceId, request) {
1594
+ return await this.requestJson(
1595
+ "PATCH",
1596
+ `/v1/workspaces/${workspaceId}/settings`,
1597
+ request
1598
+ );
1599
+ }
1600
+ async setWorkspaceDefaultRig(workspaceId, request) {
1601
+ return await this.requestJson(
1602
+ "PUT",
1603
+ `/v1/workspaces/${workspaceId}/default-rig`,
1604
+ request
1605
+ );
890
1606
  }
891
1607
  // --- Capability packs ------------------------------------------------------------------
892
1608
  /** Built-in + registered packs, with the workspace's installations. */
@@ -895,10 +1611,17 @@ var OpenGeniClient = class {
895
1611
  }
896
1612
  /** Register (or replace) a workspace-scoped pack from a manifest. */
897
1613
  async registerPack(workspaceId, manifest) {
898
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/packs`, manifest);
1614
+ return await this.requestJson(
1615
+ "POST",
1616
+ `/v1/workspaces/${workspaceId}/packs`,
1617
+ manifest
1618
+ );
899
1619
  }
900
1620
  async getPack(workspaceId, packId) {
901
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`);
1621
+ return await this.requestJson(
1622
+ "GET",
1623
+ `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`
1624
+ );
902
1625
  }
903
1626
  async enablePack(workspaceId, packId, request = {}) {
904
1627
  return await this.requestJson(
@@ -909,18 +1632,31 @@ var OpenGeniClient = class {
909
1632
  }
910
1633
  /** Unregister a workspace-scoped pack (built-in packs cannot be deleted). */
911
1634
  async deletePack(workspaceId, packId) {
912
- await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`);
1635
+ await this.requestVoid(
1636
+ "DELETE",
1637
+ `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`
1638
+ );
913
1639
  }
914
1640
  async listPackInstallations(workspaceId) {
915
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/packs/installations`);
1641
+ return await this.requestJson(
1642
+ "GET",
1643
+ `/v1/workspaces/${workspaceId}/packs/installations`
1644
+ );
916
1645
  }
917
1646
  // --- Capabilities -------------------------------------------------------------------------
918
1647
  async listCapabilities(workspaceId) {
919
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/capabilities`);
1648
+ return await this.requestJson(
1649
+ "GET",
1650
+ `/v1/workspaces/${workspaceId}/capabilities`
1651
+ );
920
1652
  }
921
1653
  /** Add a manual capability catalog item (e.g. a remote MCP server). */
922
1654
  async createCapability(workspaceId, request) {
923
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/capabilities`, request);
1655
+ return await this.requestJson(
1656
+ "POST",
1657
+ `/v1/workspaces/${workspaceId}/capabilities`,
1658
+ request
1659
+ );
924
1660
  }
925
1661
  async enableCapability(workspaceId, capabilityId, request = {}) {
926
1662
  return await this.requestJson(
@@ -947,6 +1683,49 @@ var OpenGeniClient = class {
947
1683
  }
948
1684
  );
949
1685
  }
1686
+ // --- Connections -------------------------------------------------------------------------------
1687
+ async listConnections(workspaceId) {
1688
+ const response = await this.requestJson(
1689
+ "GET",
1690
+ `/v1/workspaces/${workspaceId}/connections`
1691
+ );
1692
+ return response.connections;
1693
+ }
1694
+ async createConnection(workspaceId, request) {
1695
+ const response = await this.requestJson(
1696
+ "POST",
1697
+ `/v1/workspaces/${workspaceId}/connections`,
1698
+ request
1699
+ );
1700
+ return response.connection;
1701
+ }
1702
+ async updateConnection(workspaceId, connectionId, request) {
1703
+ const response = await this.requestJson(
1704
+ "PATCH",
1705
+ `/v1/workspaces/${workspaceId}/connections/${connectionId}`,
1706
+ request
1707
+ );
1708
+ return response.connection;
1709
+ }
1710
+ async deleteConnection(workspaceId, connectionId) {
1711
+ const response = await this.requestJson(
1712
+ "DELETE",
1713
+ `/v1/workspaces/${workspaceId}/connections/${connectionId}`
1714
+ );
1715
+ return response.connection;
1716
+ }
1717
+ /** Start an OAuth connection flow; redirect the user to the returned `authorizationUrl`. */
1718
+ async startConnectionOAuth(workspaceId, request) {
1719
+ return await this.requestJson(
1720
+ "POST",
1721
+ `/v1/workspaces/${workspaceId}/connections/oauth/start`,
1722
+ request
1723
+ );
1724
+ }
1725
+ /** Public, immutably-cached URL for a catalog item's logo, or null when the item has none. */
1726
+ catalogAssetUrl(logoAssetPath) {
1727
+ return logoAssetPath ? `${this.baseUrl}/v1/${logoAssetPath}` : null;
1728
+ }
950
1729
  // --- GitHub ----------------------------------------------------------------------------------
951
1730
  /** GitHub App configuration status + a signed install URL when configured. */
952
1731
  async getGitHubApp(workspaceId) {
@@ -961,11 +1740,17 @@ var OpenGeniClient = class {
961
1740
  return this.url(`/v1/workspaces/${workspaceId}/github/connect`, { state });
962
1741
  }
963
1742
  async listGitHubRepositories(workspaceId) {
964
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/github/repositories`);
1743
+ return await this.requestJson(
1744
+ "GET",
1745
+ `/v1/workspaces/${workspaceId}/github/repositories`
1746
+ );
965
1747
  }
966
1748
  /** Re-sync the installation's repository list from GitHub. */
967
1749
  async syncGitHubRepositories(workspaceId) {
968
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/github/repositories/sync`);
1750
+ return await this.requestJson(
1751
+ "POST",
1752
+ `/v1/workspaces/${workspaceId}/github/repositories/sync`
1753
+ );
969
1754
  }
970
1755
  /** Build a GitHub App manifest + the GitHub URL to submit it to. */
971
1756
  async createGitHubAppManifest(workspaceId, request = {}) {
@@ -977,16 +1762,26 @@ var OpenGeniClient = class {
977
1762
  }
978
1763
  // --- API keys ----------------------------------------------------------------------------------
979
1764
  async listApiKeys(workspaceId) {
980
- const response = await this.requestJson("GET", `/v1/workspaces/${workspaceId}/api-keys`);
1765
+ const response = await this.requestJson(
1766
+ "GET",
1767
+ `/v1/workspaces/${workspaceId}/api-keys`
1768
+ );
981
1769
  return response.apiKeys;
982
1770
  }
983
1771
  /** The returned `token` is shown once; only its prefix is stored. */
984
1772
  async createApiKey(workspaceId, request) {
985
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/api-keys`, request);
1773
+ return await this.requestJson(
1774
+ "POST",
1775
+ `/v1/workspaces/${workspaceId}/api-keys`,
1776
+ request
1777
+ );
986
1778
  }
987
1779
  /** Revoke an API key. Returns the revoked key. */
988
1780
  async deleteApiKey(workspaceId, apiKeyId) {
989
- return await this.requestJson("DELETE", `/v1/workspaces/${workspaceId}/api-keys/${apiKeyId}`);
1781
+ return await this.requestJson(
1782
+ "DELETE",
1783
+ `/v1/workspaces/${workspaceId}/api-keys/${apiKeyId}`
1784
+ );
990
1785
  }
991
1786
  // --- Billing (account-scoped) --------------------------------------------------------------------
992
1787
  async getBilling(options = {}) {
@@ -1001,9 +1796,14 @@ var OpenGeniClient = class {
1001
1796
  });
1002
1797
  }
1003
1798
  async getBillingEntitlements(options = {}) {
1004
- return await this.requestJson("GET", "/v1/billing/entitlements", void 0, {
1005
- ...options.accountId !== void 0 ? { accountId: options.accountId } : {}
1006
- });
1799
+ return await this.requestJson(
1800
+ "GET",
1801
+ "/v1/billing/entitlements",
1802
+ void 0,
1803
+ {
1804
+ ...options.accountId !== void 0 ? { accountId: options.accountId } : {}
1805
+ }
1806
+ );
1007
1807
  }
1008
1808
  /** Start a Stripe checkout for prepaid credits. */
1009
1809
  async createBillingCheckout(request) {
@@ -1014,7 +1814,8 @@ var OpenGeniClient = class {
1014
1814
  const extra = typeof this.options.headers === "function" ? this.options.headers() : this.options.headers;
1015
1815
  return {
1016
1816
  ...this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {},
1017
- ...extra
1817
+ ...extra,
1818
+ [OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION
1018
1819
  };
1019
1820
  }
1020
1821
  url(path, query = {}) {
@@ -1024,15 +1825,25 @@ var OpenGeniClient = class {
1024
1825
  // --- Codex (ChatGPT) subscription (workspace-scoped) --------------------------------------------
1025
1826
  /** Connection state + the codex models the workspace may select (empty until connected). */
1026
1827
  async codexStatus(workspaceId) {
1027
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/codex/status`);
1828
+ return await this.requestJson(
1829
+ "GET",
1830
+ `/v1/workspaces/${workspaceId}/codex/status`
1831
+ );
1028
1832
  }
1029
1833
  /** Begin device-code login: show `userCode` at `verificationUri`, then poll with `state`. */
1030
1834
  async codexConnectStart(workspaceId) {
1031
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/codex/connect/start`);
1835
+ return await this.requestJson(
1836
+ "POST",
1837
+ `/v1/workspaces/${workspaceId}/codex/connect/start`
1838
+ );
1032
1839
  }
1033
1840
  /** Poll device-code authorization with the `state` from {@link codexConnectStart}. */
1034
1841
  async codexConnectPoll(workspaceId, state) {
1035
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/codex/connect/poll`, { state });
1842
+ return await this.requestJson(
1843
+ "POST",
1844
+ `/v1/workspaces/${workspaceId}/codex/connect/poll`,
1845
+ { state }
1846
+ );
1036
1847
  }
1037
1848
  /** Remaining usage / limits for the connected (ACTIVE) subscription. Back-compat. */
1038
1849
  async codexUsage(workspaceId) {
@@ -1040,39 +1851,69 @@ var OpenGeniClient = class {
1040
1851
  }
1041
1852
  /** Live per-account usage read (refreshes THIS account's bearer; writes the cache). */
1042
1853
  async codexAccountUsage(workspaceId, accountId) {
1043
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/usage`);
1854
+ return await this.requestJson(
1855
+ "GET",
1856
+ `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/usage`
1857
+ );
1044
1858
  }
1045
1859
  /** Batched live refresh across every connected account, keyed by credential id. */
1046
1860
  async refreshCodexUsage(workspaceId) {
1047
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/codex/usage/refresh`);
1861
+ return await this.requestJson(
1862
+ "POST",
1863
+ `/v1/workspaces/${workspaceId}/codex/usage/refresh`
1864
+ );
1048
1865
  }
1049
1866
  /** Disconnect ALL accounts (legacy workspace-wide). Prefer `disconnectCodexAccount`. */
1050
1867
  async codexDisconnect(workspaceId) {
1051
- return await this.requestJson("DELETE", `/v1/workspaces/${workspaceId}/codex`);
1868
+ return await this.requestJson(
1869
+ "DELETE",
1870
+ `/v1/workspaces/${workspaceId}/codex`
1871
+ );
1052
1872
  }
1053
1873
  /** List every connected Codex account + the workspace active pointer + settings. */
1054
1874
  async listCodexAccounts(workspaceId) {
1055
- return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/codex/accounts`);
1875
+ return await this.requestJson(
1876
+ "GET",
1877
+ `/v1/workspaces/${workspaceId}/codex/accounts`
1878
+ );
1056
1879
  }
1057
1880
  /** Switch the workspace ACTIVE Codex account (the one unpinned sessions use). */
1058
1881
  async activateCodexAccount(workspaceId, accountId) {
1059
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/activate`);
1882
+ return await this.requestJson(
1883
+ "POST",
1884
+ `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/activate`
1885
+ );
1060
1886
  }
1061
1887
  /** P3: enable/disable Codex auto-rotation and/or pick the strategy. Returns the effective settings. */
1062
1888
  async setCodexRotationSettings(workspaceId, patch) {
1063
- return await this.requestJson("PATCH", `/v1/workspaces/${workspaceId}/codex/settings`, patch);
1889
+ return await this.requestJson(
1890
+ "PATCH",
1891
+ `/v1/workspaces/${workspaceId}/codex/settings`,
1892
+ patch
1893
+ );
1064
1894
  }
1065
1895
  /** Disconnect ONE Codex account by id (re-picks active when the removed one was active). */
1066
1896
  async disconnectCodexAccount(workspaceId, accountId) {
1067
- return await this.requestJson("DELETE", `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}`);
1897
+ return await this.requestJson(
1898
+ "DELETE",
1899
+ `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}`
1900
+ );
1068
1901
  }
1069
1902
  /** Rename a Codex account (label only in P1). */
1070
1903
  async renameCodexAccount(workspaceId, accountId, label) {
1071
- return await this.requestJson("PATCH", `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}`, { label });
1904
+ return await this.requestJson(
1905
+ "PATCH",
1906
+ `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}`,
1907
+ { label }
1908
+ );
1072
1909
  }
1073
1910
  /** Pin (or unpin via "auto") a session's Codex account. Applies on the next turn. */
1074
1911
  async pinSessionCodexAccount(workspaceId, sessionId, target) {
1075
- return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/codex-account`, { target });
1912
+ return await this.requestJson(
1913
+ "POST",
1914
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/codex-account`,
1915
+ { target }
1916
+ );
1076
1917
  }
1077
1918
  async requestJson(method, path, body, query = {}) {
1078
1919
  const response = await this.fetchImpl(this.url(path, query), {
@@ -1084,6 +1925,7 @@ var OpenGeniClient = class {
1084
1925
  },
1085
1926
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
1086
1927
  });
1928
+ assertApiContractResponse(response);
1087
1929
  if (!response.ok) {
1088
1930
  throw new OpenGeniApiError(response.status, await safeText(response));
1089
1931
  }
@@ -1100,11 +1942,18 @@ var OpenGeniClient = class {
1100
1942
  },
1101
1943
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
1102
1944
  });
1945
+ assertApiContractResponse(response);
1103
1946
  if (!response.ok) {
1104
1947
  throw new OpenGeniApiError(response.status, await safeText(response));
1105
1948
  }
1106
1949
  }
1107
1950
  };
1951
+ function assertApiContractResponse(response) {
1952
+ const actual = response.headers.get(OPENGENI_API_CONTRACT_HEADER);
1953
+ if (actual && actual !== OPENGENI_API_CONTRACT_REVISION) {
1954
+ throw new OpenGeniApiContractMismatchError(OPENGENI_API_CONTRACT_REVISION, actual);
1955
+ }
1956
+ }
1108
1957
  async function safeText(response) {
1109
1958
  try {
1110
1959
  return await response.text();
@@ -1112,9 +1961,6 @@ async function safeText(response) {
1112
1961
  return "";
1113
1962
  }
1114
1963
  }
1115
- function delay(ms) {
1116
- return new Promise((resolve) => setTimeout(resolve, ms));
1117
- }
1118
1964
 
1119
1965
  // src/proxy.ts
1120
1966
  function formatSseEvent(event) {
@@ -1298,120 +2144,12 @@ function ttydInputFrame(data) {
1298
2144
  function ttydResizeFrame(columns, rows) {
1299
2145
  return TtydClientCommand.RESIZE + JSON.stringify({ columns, rows });
1300
2146
  }
1301
-
1302
- // src/types.ts
1303
- var SESSION_EVENT_TYPES = [
1304
- "session.created",
1305
- "session.status.changed",
1306
- "session.requiresAction",
1307
- "session.context.compacted",
1308
- "session.context.cleared",
1309
- "user.message",
1310
- "user.interrupt",
1311
- "user.approvalDecision",
1312
- "turn.queued",
1313
- "turn.updated",
1314
- "turn.started",
1315
- "turn.completed",
1316
- "turn.failed",
1317
- "turn.cancelled",
1318
- "turn.preempted",
1319
- "agent.message.delta",
1320
- "agent.message.completed",
1321
- "agent.reasoning.delta",
1322
- "agent.toolCall.created",
1323
- "agent.toolCall.output",
1324
- "tool.auth_needed",
1325
- "agent.updated",
1326
- "sandbox.operation.started",
1327
- "sandbox.operation.completed",
1328
- "sandbox.operation.failed",
1329
- "sandbox.command.output.delta",
1330
- "artifact.created",
1331
- "goal.set",
1332
- "goal.updated",
1333
- "goal.completed",
1334
- "goal.paused",
1335
- "goal.resumed",
1336
- "goal.continuation",
1337
- // Channel-B desktop pixel-plane signals (mirror of contracts SessionEventType;
1338
- // the contract-parity test asserts sorted equality).
1339
- "stream.url.rotated",
1340
- "stream.opened",
1341
- "stream.closed",
1342
- "stream.revoked",
1343
- // Channel-B recording signals (P4.3 — "agent films itself proving the fix").
1344
- "recording.started",
1345
- "recording.available",
1346
- "recording.failed",
1347
- // Channel-A structured-service notifications (P4.4; mirror of contracts
1348
- // SessionEventType — the contract-parity test asserts sorted equality).
1349
- "fs.changed",
1350
- "git.changed",
1351
- "terminal.pty.started",
1352
- "terminal.pty.output.delta",
1353
- "terminal.pty.exited",
1354
- "session.title_set",
1355
- // Multi-account Codex (P1): the session's inference account changed.
1356
- "codex.account.switched"
1357
- ];
1358
- var KNOWN_PERMISSIONS = [
1359
- "account:read",
1360
- "account:admin",
1361
- "members:manage",
1362
- "workspace:create",
1363
- "billing:read",
1364
- "billing:manage",
1365
- "workspace:read",
1366
- "workspace:admin",
1367
- "sessions:create",
1368
- "sessions:read",
1369
- "sessions:control",
1370
- // Sandbox-surfacing (mirror of @opengeni/contracts Permission). stream:view is
1371
- // strictly broader than sessions:read (un-redacted pixels); stream:control is
1372
- // the never-granted-v1 raw-input plane; stream:acknowledge is the secret-leak
1373
- // consent gate.
1374
- "stream:view",
1375
- "stream:control",
1376
- "stream:acknowledge",
1377
- "files:upload",
1378
- "files:read",
1379
- "files:write",
1380
- "terminal:attach",
1381
- "documents:manage",
1382
- "documents:search",
1383
- "scheduled_tasks:manage",
1384
- "scheduled_tasks:run",
1385
- "github:manage",
1386
- "github:use",
1387
- "api_keys:manage",
1388
- "connections:read",
1389
- "connections:write",
1390
- "environments:manage",
1391
- "environments:use",
1392
- "mcp_servers:attach",
1393
- "toolspace:call",
1394
- "goals:manage",
1395
- "enrollments:read",
1396
- "enrollments:manage"
1397
- ];
1398
- var KNOWN_USAGE_EVENT_TYPES = [
1399
- "agent_run.created",
1400
- "agent_run.completed",
1401
- "model.tokens",
1402
- "model.cost",
1403
- "file.uploaded",
1404
- "file.deleted",
1405
- "document.indexed",
1406
- "scheduled_task.fired",
1407
- "api_key.request",
1408
- // sandbox warm-time metering (P2.1) — mirrors contracts UsageEventType.
1409
- "sandbox.warm_seconds",
1410
- "sandbox.warm_cost"
1411
- ];
1412
2147
  export {
1413
2148
  KNOWN_PERMISSIONS,
1414
2149
  KNOWN_USAGE_EVENT_TYPES,
2150
+ OPENGENI_API_CONTRACT_HEADER,
2151
+ OPENGENI_API_CONTRACT_REVISION,
2152
+ OpenGeniApiContractMismatchError,
1415
2153
  OpenGeniApiError,
1416
2154
  OpenGeniClient,
1417
2155
  OpenGeniStreamError,
@@ -1430,6 +2168,7 @@ export {
1430
2168
  sessionEventsToSseResponse,
1431
2169
  sessionEventsToSseStream,
1432
2170
  streamSessionEvents,
2171
+ streamWorkspaceControlEvents,
1433
2172
  terminalSocketUrl,
1434
2173
  ttydAuthFrame,
1435
2174
  ttydInputFrame,