@opengeni/sdk 0.13.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);
@@ -114,6 +124,7 @@ async function* streamSessionEvents(transport, options = {}) {
114
124
  everConnected = true;
115
125
  failedAttempts = 0;
116
126
  delayMs = baseDelayMs;
127
+ await options.beforeLive?.();
117
128
  options.onStateChange?.("live");
118
129
  for await (const message of parseSseStream(body)) {
119
130
  if (signal?.aborted) {
@@ -208,6 +219,251 @@ async function sleep(delayMs, signal) {
208
219
  });
209
220
  }
210
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
+ // Defensive bounded projection for malformed/legacy oversized envelopes.
297
+ "session.event.envelope_omitted",
298
+ "session.status.changed",
299
+ "session.requiresAction",
300
+ "session.humanInput.requested",
301
+ "session.context.compaction.requested",
302
+ "session.context.compacted",
303
+ "session.context.compaction.skipped",
304
+ "session.context.cleared",
305
+ "user.message",
306
+ "user.pause",
307
+ "user.approvalDecision",
308
+ "user.humanInputResponse",
309
+ "turn.queued",
310
+ "turn.started",
311
+ "turn.completed",
312
+ "turn.failed",
313
+ "turn.cancelled",
314
+ "turn.superseded",
315
+ "turn.recovery.requested",
316
+ "turn.capacity_waiting",
317
+ "agent.message.delta",
318
+ "agent.message.completed",
319
+ "agent.reasoning.delta",
320
+ "agent.toolCall.created",
321
+ "agent.toolCall.output",
322
+ "agent.model.usage",
323
+ "tool.auth_needed",
324
+ "credential.auth_needed",
325
+ "agent.updated",
326
+ "rig.setup.started",
327
+ "rig.setup.completed",
328
+ "rig.setup.skipped",
329
+ "rig.setup.failed",
330
+ "sandbox.operation.started",
331
+ "sandbox.operation.completed",
332
+ "sandbox.operation.failed",
333
+ "sandbox.command.output.delta",
334
+ "artifact.created",
335
+ "goal.set",
336
+ "goal.updated",
337
+ "goal.completed",
338
+ "goal.paused",
339
+ "goal.resumed",
340
+ "goal.cleared",
341
+ "goal.continuation",
342
+ "system.update.pending",
343
+ "system.update.delivered",
344
+ "session.control.paused",
345
+ "session.control.resumed",
346
+ "session.control.steer_requested",
347
+ "workspace.inference.paused",
348
+ "workspace.inference.resumed",
349
+ "session.queue.changed",
350
+ "session.queue.prompt.cancelled",
351
+ "session.queue.history",
352
+ "turn.event.rejected_late",
353
+ "memory.saved",
354
+ "memory.corrected",
355
+ // Channel-B desktop pixel-plane signals (mirror of contracts SessionEventType;
356
+ // the contract-parity test asserts sorted equality).
357
+ "stream.url.rotated",
358
+ "stream.opened",
359
+ "stream.closed",
360
+ "stream.revoked",
361
+ // Channel-B recording signals (P4.3 — "agent films itself proving the fix").
362
+ "recording.started",
363
+ "recording.available",
364
+ "recording.failed",
365
+ // Channel-A structured-service notifications (P4.4; mirror of contracts
366
+ // SessionEventType — the contract-parity test asserts sorted equality).
367
+ "fs.changed",
368
+ "git.changed",
369
+ "terminal.pty.started",
370
+ "terminal.pty.output.delta",
371
+ "terminal.pty.exited",
372
+ "session.title_set",
373
+ // Multi-account Codex (P1): the session's inference account changed.
374
+ "codex.account.switched",
375
+ // credential allocator metadata-only per-turn credential selection audit.
376
+ "codex.credential.selected",
377
+ // credential allocator durable zero-capacity wait lifecycle. These are system/runtime
378
+ // events, never synthetic user messages.
379
+ "codex.capacity.waiting",
380
+ "codex.capacity.resumed",
381
+ "codex.capacity.superseded",
382
+ // Sandbox durability observability (mirror of contracts SessionEventType):
383
+ // box lifecycle + manifest-env drift, attributable from the DB alone.
384
+ "sandbox.box.created",
385
+ "sandbox.box.lost",
386
+ "sandbox.box.terminated",
387
+ "sandbox.box.snapshot",
388
+ "sandbox.env.drift",
389
+ // Active-sandbox pointer reconcile (issue #341; announce-only; mirror of contracts
390
+ // SessionEventType — the contract-parity test asserts sorted equality).
391
+ "session.route.reconciled",
392
+ // Workbench v2 turn-end workspace capture (announce-only; mirror of contracts
393
+ // SessionEventType — the contract-parity test asserts sorted equality).
394
+ "workspace.revision.captured",
395
+ "workspace.revision.degraded",
396
+ // Connected Machine op-outcome observability (announce-only, quiet; mirror of
397
+ // contracts SessionEventType — the contract-parity test asserts sorted equality).
398
+ "machine.op.failed",
399
+ "machine.op.recovered",
400
+ // Connected Machine link-plane observability (announce-only, quiet; mirror of
401
+ // contracts SessionEventType — the contract-parity test asserts sorted equality).
402
+ "machine.link.lost",
403
+ "machine.link.restored",
404
+ "machine.runner.restarted"
405
+ ];
406
+ var KNOWN_PERMISSIONS = [
407
+ "account:read",
408
+ "account:admin",
409
+ "members:manage",
410
+ "workspace:create",
411
+ "billing:read",
412
+ "billing:manage",
413
+ "workspace:read",
414
+ "workspace:admin",
415
+ "sessions:create",
416
+ "sessions:read",
417
+ "sessions:control",
418
+ // sandbox workspace (mirror of @opengeni/contracts Permission). stream:view is
419
+ // strictly broader than sessions:read (un-redacted pixels); stream:control is
420
+ // the never-granted-v1 raw-input plane; stream:acknowledge is the secret-leak
421
+ // consent gate.
422
+ "stream:view",
423
+ "stream:control",
424
+ "stream:acknowledge",
425
+ "files:upload",
426
+ "files:read",
427
+ "files:write",
428
+ "terminal:attach",
429
+ "documents:manage",
430
+ "documents:search",
431
+ "scheduled_tasks:manage",
432
+ "scheduled_tasks:run",
433
+ "github:manage",
434
+ "github:use",
435
+ "api_keys:manage",
436
+ "connections:read",
437
+ "connections:write",
438
+ "environments:manage",
439
+ "environments:use",
440
+ "variable-sets:manage",
441
+ "variable-sets:use",
442
+ "mcp_servers:attach",
443
+ "toolspace:call",
444
+ "goals:manage",
445
+ "enrollments:read",
446
+ "enrollments:manage",
447
+ "rigs:use",
448
+ "rigs:manage"
449
+ ];
450
+ var OPENGENI_API_CONTRACT_REVISION = "2026-07-turn-instructions-v1";
451
+ var OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract";
452
+ var KNOWN_USAGE_EVENT_TYPES = [
453
+ "agent_run.created",
454
+ "agent_run.completed",
455
+ "model.tokens",
456
+ "model.cost",
457
+ "file.uploaded",
458
+ "file.deleted",
459
+ "document.indexed",
460
+ "scheduled_task.fired",
461
+ "api_key.request",
462
+ // sandbox warm-time metering (P2.1) — mirrors contracts UsageEventType.
463
+ "sandbox.warm_seconds",
464
+ "sandbox.warm_cost"
465
+ ];
466
+
211
467
  // src/client.ts
212
468
  var OpenGeniClient = class {
213
469
  baseUrl;
@@ -255,7 +511,7 @@ var OpenGeniClient = class {
255
511
  }
256
512
  /** Pin-aware ordinary-session page with a stable keyset cursor. */
257
513
  async listSessionPage(workspaceId, options = {}) {
258
- const response = await this.requestJson(
514
+ return await this.requestJson(
259
515
  "GET",
260
516
  `/v1/workspaces/${workspaceId}/sessions`,
261
517
  void 0,
@@ -269,16 +525,6 @@ var OpenGeniClient = class {
269
525
  } : {}
270
526
  }
271
527
  );
272
- if (Array.isArray(response)) {
273
- if (options.cursor) {
274
- throw new Error("The connected OpenGeni API does not support stable session-page cursors");
275
- }
276
- if (options.search?.trim()) {
277
- throw new Error("The connected OpenGeni API does not support session search");
278
- }
279
- return { pinned: [], sessions: response, nextCursor: null };
280
- }
281
- return response;
282
528
  }
283
529
  /** Set this authenticated member's personal workspace pin for a session. */
284
530
  async updateSessionPin(workspaceId, sessionId, request) {
@@ -318,7 +564,8 @@ var OpenGeniClient = class {
318
564
  void 0,
319
565
  {
320
566
  ...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {}
321
- }
567
+ },
568
+ { signal: options.signal }
322
569
  );
323
570
  }
324
571
  /**
@@ -418,23 +665,75 @@ var OpenGeniClient = class {
418
665
  }
419
666
  // --- Events: replay, send, stream ----------------------------------------
420
667
  /**
421
- * Replay durable events by sequence, ascending. `before` is exclusive and
422
- * returns the newest matching window. With `compact`, consecutive delta runs
423
- * may be coalesced; `payload.coalescedUntil` carries the run's last sequence
424
- * for resume cursors.
668
+ * Return the events from one bounded page. With no cursor, this uses the safe
669
+ * semantic monitoring tail; pass explicit forensic options and a cursor for
670
+ * retained audit replay. Use `listEventPage` when projection, coverage, or
671
+ * resume-cursor facts are required.
425
672
  */
426
673
  async listEvents(workspaceId, sessionId, options = {}) {
427
- return await this.requestJson(
428
- "GET",
429
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`,
430
- void 0,
431
- {
674
+ return (await this.listEventPage(workspaceId, sessionId, options)).events;
675
+ }
676
+ /** Bounded durable/monitoring page plus exact projection and cursor facts. */
677
+ async listEventPage(workspaceId, sessionId, options = {}) {
678
+ if (options.latest && ["includeTypes", "excludeTypes", "includeClasses", "excludeClasses"].some(
679
+ (name) => Object.prototype.hasOwnProperty.call(options, name)
680
+ )) {
681
+ throw new TypeError("latest cannot be combined with event filters");
682
+ }
683
+ const response = await this.fetchImpl(
684
+ this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, {
432
685
  ...options.after !== void 0 ? { after: String(options.after) } : {},
433
686
  ...options.before !== void 0 ? { before: String(options.before) } : {},
434
687
  ...options.limit !== void 0 ? { limit: String(options.limit) } : {},
435
- ...options.compact ? { compact: "1" } : {}
688
+ ...options.compact ? { compact: "1" } : {},
689
+ ...options.mode ? { mode: options.mode } : {},
690
+ ...options.direction ? { direction: options.direction } : {},
691
+ ...options.payloadMode ? { payloadMode: options.payloadMode } : {},
692
+ ...options.includeTypes?.length ? { includeTypes: options.includeTypes.join(",") } : {},
693
+ ...options.excludeTypes?.length ? { excludeTypes: options.excludeTypes.join(",") } : {},
694
+ ...options.includeClasses?.length ? { includeClasses: options.includeClasses.join(",") } : {},
695
+ ...options.excludeClasses?.length ? { excludeClasses: options.excludeClasses.join(",") } : {},
696
+ ...options.latest ? { latest: options.latest } : {}
697
+ }),
698
+ {
699
+ method: "GET",
700
+ headers: { ...this.headers(), Accept: "application/json" }
436
701
  }
437
702
  );
703
+ assertApiContractResponse(response);
704
+ if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
705
+ const events = await response.json();
706
+ const integerHeader = (name) => {
707
+ const raw = response.headers.get(name);
708
+ if (raw === null) return null;
709
+ const value = Number(raw);
710
+ return Number.isSafeInteger(value) && value >= 0 ? value : null;
711
+ };
712
+ const mode = response.headers.get("X-OpenGeni-Event-Mode") === "forensic" ? "forensic" : "monitoring";
713
+ const direction = response.headers.get("X-OpenGeni-Event-Direction") === "after" ? "after" : "before";
714
+ const payloadHeader = response.headers.get("X-OpenGeni-Payload-Mode");
715
+ const payloadMode = payloadHeader === "none" || payloadHeader === "full" ? payloadHeader : "summary";
716
+ const first = integerHeader("X-OpenGeni-Covered-First");
717
+ const last = integerHeader("X-OpenGeni-Covered-Last");
718
+ const bytes = integerHeader("X-OpenGeni-Page-Bytes") ?? new TextEncoder().encode(JSON.stringify(events)).byteLength;
719
+ const maxBytes = integerHeader("X-OpenGeni-Page-Max-Bytes") ?? 1024 * 1024;
720
+ const truncatedByHeader = response.headers.get("X-OpenGeni-Truncated-By");
721
+ const truncatedBy = truncatedByHeader === "count" || truncatedByHeader === "bytes" || truncatedByHeader === "http_bytes" ? truncatedByHeader : null;
722
+ return {
723
+ events,
724
+ mode,
725
+ payloadMode,
726
+ direction,
727
+ bytes,
728
+ maxBytes,
729
+ truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
730
+ hasMore: response.headers.get("X-OpenGeni-Has-More") === "true",
731
+ truncatedBy,
732
+ coveredSequence: first === null || last === null ? null : { first, last },
733
+ nextAfter: integerHeader("X-OpenGeni-Next-After"),
734
+ nextBefore: integerHeader("X-OpenGeni-Next-Before"),
735
+ forensicExact: response.headers.get("X-OpenGeni-Forensic-Exact") === "true"
736
+ };
438
737
  }
439
738
  /** POST a user/control event to the session. Returns the accepted event. */
440
739
  async sendEvent(workspaceId, sessionId, event) {
@@ -454,10 +753,12 @@ var OpenGeniClient = class {
454
753
  });
455
754
  }
456
755
  async pauseSession(workspaceId, sessionId, options = {}) {
457
- return (await this.controlSession(workspaceId, sessionId, {
458
- mode: "pause",
459
- ...options
460
- })).event;
756
+ return await this.controlSession(workspaceId, sessionId, {
757
+ action: "pause",
758
+ clientEventId: options.clientEventId ?? crypto.randomUUID(),
759
+ ...options.reason ? { reason: options.reason } : {},
760
+ ...options.expectedControlEtag ? { expectedControlEtag: options.expectedControlEtag } : {}
761
+ });
461
762
  }
462
763
  async sendApprovalDecision(workspaceId, sessionId, decision) {
463
764
  const { clientEventId, ...payload } = decision;
@@ -467,6 +768,28 @@ var OpenGeniClient = class {
467
768
  payload
468
769
  });
469
770
  }
771
+ async listHumanInputRequests(workspaceId, sessionId, options = {}) {
772
+ const result = await this.requestJson(
773
+ "GET",
774
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests`,
775
+ void 0,
776
+ options.status ? { status: options.status } : void 0
777
+ );
778
+ return result.requests;
779
+ }
780
+ async getHumanInputRequest(workspaceId, sessionId, requestId) {
781
+ return await this.requestJson(
782
+ "GET",
783
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests/${requestId}`
784
+ );
785
+ }
786
+ async submitHumanInputResponse(workspaceId, sessionId, requestId, response, options = {}) {
787
+ return await this.sendEvent(workspaceId, sessionId, {
788
+ type: "user.humanInputResponse",
789
+ ...options.clientEventId ? { clientEventId: options.clientEventId } : {},
790
+ payload: { requestId, response }
791
+ });
792
+ }
470
793
  /**
471
794
  * Live-stream a session's events with automatic reconnect, resume from the
472
795
  * last seen sequence, gap backfill, and duplicate suppression. See
@@ -495,6 +818,7 @@ var OpenGeniClient = class {
495
818
  headers: { ...this.headers(), Accept: "text/event-stream" },
496
819
  ...options.signal ? { signal: options.signal } : {}
497
820
  });
821
+ assertApiContractResponse(response);
498
822
  if (!response.ok) {
499
823
  throw new OpenGeniApiError(response.status, await safeText(response));
500
824
  }
@@ -510,10 +834,44 @@ var OpenGeniClient = class {
510
834
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`
511
835
  );
512
836
  }
513
- async cancelQueueItem(workspaceId, sessionId, turnId, request) {
837
+ async moveQueueItem(workspaceId, sessionId, turnId, request) {
838
+ return await this.requestJson(
839
+ "POST",
840
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/move`,
841
+ request
842
+ );
843
+ }
844
+ async editQueueItem(workspaceId, sessionId, turnId, request) {
845
+ return await this.requestJson(
846
+ "POST",
847
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/edit`,
848
+ request
849
+ );
850
+ }
851
+ async steerQueueItem(workspaceId, sessionId, turnId, request) {
514
852
  return await this.requestJson(
515
853
  "POST",
516
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/cancel`,
854
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/steer`,
855
+ request
856
+ );
857
+ }
858
+ async deleteQueueItem(workspaceId, sessionId, turnId, request) {
859
+ return await this.requestJson(
860
+ "POST",
861
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/delete`,
862
+ request
863
+ );
864
+ }
865
+ async getComposerDraft(workspaceId, sessionId) {
866
+ return await this.requestJson(
867
+ "GET",
868
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`
869
+ );
870
+ }
871
+ async saveComposerDraft(workspaceId, sessionId, request) {
872
+ return await this.requestJson(
873
+ "PUT",
874
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`,
517
875
  request
518
876
  );
519
877
  }
@@ -525,7 +883,12 @@ var OpenGeniClient = class {
525
883
  );
526
884
  }
527
885
  async resumeSession(workspaceId, sessionId, options = {}) {
528
- return await this.controlSession(workspaceId, sessionId, { mode: "resume", ...options });
886
+ return await this.controlSession(workspaceId, sessionId, {
887
+ action: "resume",
888
+ clientEventId: options.clientEventId ?? crypto.randomUUID(),
889
+ ...options.reason ? { reason: options.reason } : {},
890
+ ...options.expectedControlEtag ? { expectedControlEtag: options.expectedControlEtag } : {}
891
+ });
529
892
  }
530
893
  async setWorkspaceInferenceState(workspaceId, request) {
531
894
  return await this.requestJson(
@@ -534,12 +897,65 @@ var OpenGeniClient = class {
534
897
  request
535
898
  );
536
899
  }
537
- /** Cancel a queued turn before it is claimed. Returns the cancelled turn. */
538
- async deleteQueuedTurn(workspaceId, sessionId, turnId) {
539
- return await this.requestJson(
540
- "DELETE",
541
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns/${turnId}`
900
+ async listWorkspaceControlEvents(workspaceId, options = {}) {
901
+ return (await this.listWorkspaceControlEventPage(workspaceId, options)).events;
902
+ }
903
+ /** Count/byte-bounded page plus an explicit continuation cursor. */
904
+ async listWorkspaceControlEventPage(workspaceId, options = {}) {
905
+ const response = await this.fetchImpl(
906
+ this.url(`/v1/workspaces/${workspaceId}/control-events`, {
907
+ ...options.after !== void 0 ? { after: String(options.after) } : {},
908
+ ...options.limit !== void 0 ? { limit: String(options.limit) } : {}
909
+ }),
910
+ {
911
+ method: "GET",
912
+ headers: { ...this.headers(), Accept: "application/json" }
913
+ }
542
914
  );
915
+ assertApiContractResponse(response);
916
+ if (!response.ok) {
917
+ throw new OpenGeniApiError(response.status, await safeText(response));
918
+ }
919
+ const events = await response.json();
920
+ const bytesHeader = response.headers.get("X-OpenGeni-Page-Bytes");
921
+ const nextHeader = response.headers.get("X-OpenGeni-Next-After");
922
+ const parsedBytes = bytesHeader === null ? Number.NaN : Number(bytesHeader);
923
+ const parsedNext = nextHeader === null ? null : Number(nextHeader);
924
+ return {
925
+ events,
926
+ bytes: Number.isSafeInteger(parsedBytes) && parsedBytes >= 0 ? parsedBytes : new TextEncoder().encode(JSON.stringify(events)).byteLength,
927
+ truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
928
+ nextAfter: parsedNext !== null && Number.isSafeInteger(parsedNext) && parsedNext >= 0 ? parsedNext : null
929
+ };
930
+ }
931
+ streamWorkspaceControlEvents(workspaceId, options = {}) {
932
+ return streamWorkspaceControlEvents(this.workspaceControlStreamTransport(workspaceId), options);
933
+ }
934
+ workspaceControlStreamTransport(workspaceId) {
935
+ return {
936
+ openStream: async (after, signal) => await this.openWorkspaceControlEventStream(workspaceId, {
937
+ after,
938
+ ...signal ? { signal } : {}
939
+ })
940
+ };
941
+ }
942
+ async openWorkspaceControlEventStream(workspaceId, options = {}) {
943
+ const response = await this.fetchImpl(
944
+ this.url(`/v1/workspaces/${workspaceId}/control-events/stream`, {
945
+ after: String(options.after ?? 0)
946
+ }),
947
+ {
948
+ method: "GET",
949
+ headers: { ...this.headers(), Accept: "text/event-stream" },
950
+ ...options.signal ? { signal: options.signal } : {}
951
+ }
952
+ );
953
+ assertApiContractResponse(response);
954
+ if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
955
+ if (!response.body) {
956
+ throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
957
+ }
958
+ return response.body;
543
959
  }
544
960
  /**
545
961
  * Steer: atomically put this prompt at the head and supersede the current
@@ -610,19 +1026,23 @@ var OpenGeniClient = class {
610
1026
  // synchronous API-direct point query; the fs.changed/git.changed/terminal.pty.*
611
1027
  // notifications + the PTY output stream arrive on the existing event SSE.
612
1028
  /** FileSystem: list a directory tree (feeds the Pierre file tree). */
613
- async fsList(workspaceId, sessionId, request = {}) {
1029
+ async fsList(workspaceId, sessionId, request = {}, options = {}) {
614
1030
  return await this.requestJson(
615
1031
  "POST",
616
1032
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list`,
617
- request
1033
+ request,
1034
+ {},
1035
+ options
618
1036
  );
619
1037
  }
620
1038
  /** FileSystem: read a file (text or base64; binary-safe, size-capped). */
621
- async fsRead(workspaceId, sessionId, request) {
1039
+ async fsRead(workspaceId, sessionId, request, options = {}) {
622
1040
  return await this.requestJson(
623
1041
  "POST",
624
1042
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/read`,
625
- request
1043
+ request,
1044
+ {},
1045
+ options
626
1046
  );
627
1047
  }
628
1048
  /** FileSystem: write a file (last-writer-wins; emits fs.changed). */
@@ -658,19 +1078,23 @@ var OpenGeniClient = class {
658
1078
  );
659
1079
  }
660
1080
  /** Git: working-tree/index status (the Pierre file-status feed). */
661
- async gitStatus(workspaceId, sessionId, request = {}) {
1081
+ async gitStatus(workspaceId, sessionId, request = {}, options = {}) {
662
1082
  return await this.requestJson(
663
1083
  "POST",
664
1084
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/status`,
665
- request
1085
+ request,
1086
+ {},
1087
+ options
666
1088
  );
667
1089
  }
668
1090
  /** Git: structured diff hunks (the Pierre diff feed). */
669
- async gitDiff(workspaceId, sessionId, request = {}) {
1091
+ async gitDiff(workspaceId, sessionId, request = {}, options = {}) {
670
1092
  return await this.requestJson(
671
1093
  "POST",
672
1094
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/diff`,
673
- request
1095
+ request,
1096
+ {},
1097
+ options
674
1098
  );
675
1099
  }
676
1100
  /** Git: commit log. */
@@ -693,23 +1117,27 @@ var OpenGeniClient = class {
693
1117
  * (tree + per-repo diff + file after-image refs), served from durable storage
694
1118
  * WITHOUT warming a machine — the workbench cold-paint source. Returns
695
1119
  * `{available:false}` when no capture exists yet (fall back to the live path). */
696
- async getWorkspaceCapture(workspaceId, sessionId) {
1120
+ async getWorkspaceCapture(workspaceId, sessionId, options = {}) {
697
1121
  return await this.requestJson(
698
1122
  "GET",
699
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture`
1123
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture`,
1124
+ void 0,
1125
+ {},
1126
+ options
700
1127
  );
701
1128
  }
702
1129
  /** Workspace capture: a single file's after-image from the capture (revision
703
1130
  * pins a specific one; omitted → latest). Content is inline for small files,
704
1131
  * else a short-TTL signed URL; a tooLarge file returns metadata only. */
705
- async getWorkspaceCaptureFile(workspaceId, sessionId, path, revision) {
1132
+ async getWorkspaceCaptureFile(workspaceId, sessionId, path, revision, options = {}) {
706
1133
  const query = { path };
707
1134
  if (revision !== void 0) query.revision = String(revision);
708
1135
  return await this.requestJson(
709
1136
  "GET",
710
1137
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture/file`,
711
1138
  void 0,
712
- query
1139
+ query,
1140
+ options
713
1141
  );
714
1142
  }
715
1143
  /** Terminal: run a bounded command, returning buffered stdout/stderr inline. */
@@ -764,10 +1192,13 @@ var OpenGeniClient = class {
764
1192
  * liveness the client polls on while `cold`/`warming`. The desktop URL/token
765
1193
  * are minted in-process only when the box is warm AND the principal has
766
1194
  * acknowledged the un-redacted plane. */
767
- async getStreamCapabilities(workspaceId, sessionId) {
1195
+ async getStreamCapabilities(workspaceId, sessionId, options = {}) {
768
1196
  return await this.requestJson(
769
1197
  "GET",
770
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`
1198
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`,
1199
+ void 0,
1200
+ {},
1201
+ options
771
1202
  );
772
1203
  }
773
1204
  /** Record the calling principal's acknowledgment of the un-redacted desktop
@@ -821,7 +1252,14 @@ var OpenGeniClient = class {
821
1252
  * knowledge of the host setup; safe to call before any auth is established.
822
1253
  */
823
1254
  async getClientConfig() {
824
- return await this.requestJson("GET", "/v1/config/client");
1255
+ const config = await this.requestJson("GET", "/v1/config/client");
1256
+ if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
1257
+ throw new OpenGeniApiContractMismatchError(
1258
+ OPENGENI_API_CONTRACT_REVISION,
1259
+ String(config.apiContractRevision || "(missing)")
1260
+ );
1261
+ }
1262
+ return config;
825
1263
  }
826
1264
  /** The caller's access context: subject, account + workspace grants, defaults. */
827
1265
  async getAccessContext() {
@@ -1404,14 +1842,13 @@ var OpenGeniClient = class {
1404
1842
  return logoAssetPath ? `${this.baseUrl}/v1/${logoAssetPath}` : null;
1405
1843
  }
1406
1844
  // --- GitHub ----------------------------------------------------------------------------------
1407
- /** GitHub App configuration status + a signed install URL when configured. */
1845
+ /** GitHub App configuration status; install/link URLs are null while new binding is disabled. */
1408
1846
  async getGitHubApp(workspaceId) {
1409
1847
  return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/github/app`);
1410
1848
  }
1411
1849
  /**
1412
- * Browser entry point that plants the CSRF cookie and forwards to GitHub's
1413
- * install page. Open this in a browser (it redirects); `state` comes from
1414
- * `getGitHubApp().installUrl` or a github_connect_link tool.
1850
+ * Compatibility URL for previously issued state. New installation binding is
1851
+ * disabled, so the endpoint validates state and terminates with HTTP 410.
1415
1852
  */
1416
1853
  githubConnectUrl(workspaceId, state) {
1417
1854
  return this.url(`/v1/workspaces/${workspaceId}/github/connect`, { state });
@@ -1429,6 +1866,13 @@ var OpenGeniClient = class {
1429
1866
  `/v1/workspaces/${workspaceId}/github/repositories/sync`
1430
1867
  );
1431
1868
  }
1869
+ /** Remove one workspace binding without uninstalling the GitHub App itself. */
1870
+ async unlinkGitHubInstallation(workspaceId, installationId) {
1871
+ await this.requestVoid(
1872
+ "DELETE",
1873
+ `/v1/workspaces/${workspaceId}/github/installations/${installationId}`
1874
+ );
1875
+ }
1432
1876
  /** Build a GitHub App manifest + the GitHub URL to submit it to. */
1433
1877
  async createGitHubAppManifest(workspaceId, request = {}) {
1434
1878
  return await this.requestJson(
@@ -1491,7 +1935,8 @@ var OpenGeniClient = class {
1491
1935
  const extra = typeof this.options.headers === "function" ? this.options.headers() : this.options.headers;
1492
1936
  return {
1493
1937
  ...this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {},
1494
- ...extra
1938
+ ...extra,
1939
+ [OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION
1495
1940
  };
1496
1941
  }
1497
1942
  url(path, query = {}) {
@@ -1591,7 +2036,7 @@ var OpenGeniClient = class {
1591
2036
  { target }
1592
2037
  );
1593
2038
  }
1594
- async requestJson(method, path, body, query = {}) {
2039
+ async requestJson(method, path, body, query = {}, options = {}) {
1595
2040
  const response = await this.fetchImpl(this.url(path, query), {
1596
2041
  method,
1597
2042
  headers: {
@@ -1599,8 +2044,10 @@ var OpenGeniClient = class {
1599
2044
  Accept: "application/json",
1600
2045
  ...body !== void 0 ? { "Content-Type": "application/json" } : {}
1601
2046
  },
1602
- ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2047
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {},
2048
+ ...options.signal ? { signal: options.signal } : {}
1603
2049
  });
2050
+ assertApiContractResponse(response);
1604
2051
  if (!response.ok) {
1605
2052
  throw new OpenGeniApiError(response.status, await safeText(response));
1606
2053
  }
@@ -1617,11 +2064,18 @@ var OpenGeniClient = class {
1617
2064
  },
1618
2065
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
1619
2066
  });
2067
+ assertApiContractResponse(response);
1620
2068
  if (!response.ok) {
1621
2069
  throw new OpenGeniApiError(response.status, await safeText(response));
1622
2070
  }
1623
2071
  }
1624
2072
  };
2073
+ function assertApiContractResponse(response) {
2074
+ const actual = response.headers.get(OPENGENI_API_CONTRACT_HEADER);
2075
+ if (actual && actual !== OPENGENI_API_CONTRACT_REVISION) {
2076
+ throw new OpenGeniApiContractMismatchError(OPENGENI_API_CONTRACT_REVISION, actual);
2077
+ }
2078
+ }
1625
2079
  async function safeText(response) {
1626
2080
  try {
1627
2081
  return await response.text();
@@ -1813,174 +2267,225 @@ function ttydResizeFrame(columns, rows) {
1813
2267
  return TtydClientCommand.RESIZE + JSON.stringify({ columns, rows });
1814
2268
  }
1815
2269
 
1816
- // src/types.ts
1817
- var SESSION_EVENT_TYPES = [
1818
- "session.created",
1819
- "session.status.changed",
1820
- "session.requiresAction",
1821
- "session.context.compaction.requested",
1822
- "session.context.compacted",
1823
- "session.context.compaction.skipped",
1824
- "session.context.cleared",
1825
- "user.message",
1826
- "user.pause",
1827
- "user.approvalDecision",
1828
- "turn.queued",
1829
- "turn.started",
1830
- "turn.completed",
1831
- "turn.failed",
1832
- "turn.cancelled",
1833
- "turn.superseded",
1834
- "turn.recovery.requested",
1835
- "turn.capacity_waiting",
1836
- "agent.message.delta",
1837
- "agent.message.completed",
1838
- "agent.reasoning.delta",
1839
- "agent.toolCall.created",
1840
- "agent.toolCall.output",
1841
- "agent.model.usage",
1842
- "tool.auth_needed",
1843
- "agent.updated",
1844
- "rig.setup.started",
1845
- "rig.setup.completed",
1846
- "rig.setup.skipped",
1847
- "rig.setup.failed",
1848
- "sandbox.operation.started",
1849
- "sandbox.operation.completed",
1850
- "sandbox.operation.failed",
1851
- "sandbox.command.output.delta",
1852
- "artifact.created",
1853
- "goal.set",
1854
- "goal.updated",
1855
- "goal.completed",
1856
- "goal.paused",
1857
- "goal.resumed",
1858
- "goal.cleared",
1859
- "goal.continuation",
1860
- "system.update.pending",
1861
- "system.update.delivered",
1862
- "session.control.paused",
1863
- "session.control.resumed",
1864
- "session.control.steer_requested",
1865
- "workspace.inference.paused",
1866
- "workspace.inference.resumed",
1867
- "session.queue.prompt.cancelled",
1868
- "session.queue.history",
1869
- "turn.event.rejected_late",
1870
- "memory.saved",
1871
- "memory.corrected",
1872
- // Channel-B desktop pixel-plane signals (mirror of contracts SessionEventType;
1873
- // the contract-parity test asserts sorted equality).
1874
- "stream.url.rotated",
1875
- "stream.opened",
1876
- "stream.closed",
1877
- "stream.revoked",
1878
- // Channel-B recording signals (P4.3 — "agent films itself proving the fix").
1879
- "recording.started",
1880
- "recording.available",
1881
- "recording.failed",
1882
- // Channel-A structured-service notifications (P4.4; mirror of contracts
1883
- // SessionEventType — the contract-parity test asserts sorted equality).
1884
- "fs.changed",
1885
- "git.changed",
1886
- "terminal.pty.started",
1887
- "terminal.pty.output.delta",
1888
- "terminal.pty.exited",
1889
- "session.title_set",
1890
- // Multi-account Codex (P1): the session's inference account changed.
1891
- "codex.account.switched",
1892
- // OPE-21 metadata-only per-turn credential selection audit.
1893
- "codex.credential.selected",
1894
- // OPE-21 durable zero-capacity wait lifecycle. These are system/runtime
1895
- // events, never synthetic user messages.
1896
- "codex.capacity.waiting",
1897
- "codex.capacity.resumed",
1898
- "codex.capacity.superseded",
1899
- // Sandbox durability observability (mirror of contracts SessionEventType):
1900
- // box lifecycle + manifest-env drift, attributable from the DB alone.
1901
- "sandbox.box.created",
1902
- "sandbox.box.lost",
1903
- "sandbox.box.terminated",
1904
- "sandbox.box.snapshot",
1905
- "sandbox.env.drift",
1906
- // Active-sandbox pointer reconcile (issue #341; announce-only; mirror of contracts
1907
- // SessionEventType — the contract-parity test asserts sorted equality).
1908
- "session.route.reconciled",
1909
- // Workbench v2 turn-end workspace capture (announce-only; mirror of contracts
1910
- // SessionEventType the contract-parity test asserts sorted equality).
1911
- "workspace.revision.captured",
1912
- "workspace.revision.degraded",
1913
- // Connected Machine op-outcome observability (announce-only, quiet; mirror of
1914
- // contracts SessionEventType — the contract-parity test asserts sorted equality).
1915
- "machine.op.failed",
1916
- "machine.op.recovered",
1917
- // Connected Machine link-plane observability (announce-only, quiet; mirror of
1918
- // contracts SessionEventType the contract-parity test asserts sorted equality).
1919
- "machine.link.lost",
1920
- "machine.link.restored",
1921
- "machine.runner.restarted"
1922
- ];
1923
- var KNOWN_PERMISSIONS = [
1924
- "account:read",
1925
- "account:admin",
1926
- "members:manage",
1927
- "workspace:create",
1928
- "billing:read",
1929
- "billing:manage",
1930
- "workspace:read",
1931
- "workspace:admin",
1932
- "sessions:create",
1933
- "sessions:read",
1934
- "sessions:control",
1935
- // Sandbox-surfacing (mirror of @opengeni/contracts Permission). stream:view is
1936
- // strictly broader than sessions:read (un-redacted pixels); stream:control is
1937
- // the never-granted-v1 raw-input plane; stream:acknowledge is the secret-leak
1938
- // consent gate.
1939
- "stream:view",
1940
- "stream:control",
1941
- "stream:acknowledge",
1942
- "files:upload",
1943
- "files:read",
1944
- "files:write",
1945
- "terminal:attach",
1946
- "documents:manage",
1947
- "documents:search",
1948
- "scheduled_tasks:manage",
1949
- "scheduled_tasks:run",
1950
- "github:manage",
1951
- "github:use",
1952
- "api_keys:manage",
1953
- "connections:read",
1954
- "connections:write",
1955
- "environments:manage",
1956
- "environments:use",
1957
- "variable-sets:manage",
1958
- "variable-sets:use",
1959
- "mcp_servers:attach",
1960
- "toolspace:call",
1961
- "goals:manage",
1962
- "enrollments:read",
1963
- "enrollments:manage",
1964
- "rigs:use",
1965
- "rigs:manage"
1966
- ];
1967
- var KNOWN_USAGE_EVENT_TYPES = [
1968
- "agent_run.created",
1969
- "agent_run.completed",
1970
- "model.tokens",
1971
- "model.cost",
1972
- "file.uploaded",
1973
- "file.deleted",
1974
- "document.indexed",
1975
- "scheduled_task.fired",
1976
- "api_key.request",
1977
- // sandbox warm-time metering (P2.1) mirrors contracts UsageEventType.
1978
- "sandbox.warm_seconds",
1979
- "sandbox.warm_cost"
1980
- ];
2270
+ // src/transcription.ts
2271
+ var DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY = {
2272
+ enabled: false,
2273
+ acceptanceId: null,
2274
+ primary: null,
2275
+ language: null,
2276
+ autoDetectLanguage: false,
2277
+ diarization: { enabled: false, maxSpeakers: null },
2278
+ retention: { mode: "none", maxDays: null },
2279
+ privacy: { allowProviderLogging: false, allowProviderTraining: false },
2280
+ fallback: { mode: "disabled", targets: [] },
2281
+ cost: { currency: "USD", maxPerHour: null, maxPerMonth: null }
2282
+ };
2283
+ function resolveWorkspaceTranscriptionPolicy(settings) {
2284
+ if (!isRecord(settings)) return cloneDefaultPolicy();
2285
+ const candidate = settings.transcription;
2286
+ if (!isWorkspaceTranscriptionPolicy(candidate)) return cloneDefaultPolicy();
2287
+ return {
2288
+ ...candidate,
2289
+ primary: candidate.primary ? normalizeTarget(candidate.primary) : null,
2290
+ language: candidate.language?.trim() ?? null,
2291
+ diarization: { ...candidate.diarization },
2292
+ retention: { ...candidate.retention },
2293
+ privacy: { ...candidate.privacy },
2294
+ fallback: {
2295
+ mode: candidate.fallback.mode,
2296
+ targets: candidate.fallback.targets.map(normalizeTarget)
2297
+ },
2298
+ cost: { ...candidate.cost }
2299
+ };
2300
+ }
2301
+ function authorizeTranscriptionAdapter(policy, descriptor, selection = { kind: "primary" }) {
2302
+ if (!isWorkspaceTranscriptionPolicy(policy)) {
2303
+ return { authorized: false, reason: "unaccepted" };
2304
+ }
2305
+ if (!policy.enabled) return { authorized: false, reason: "disabled" };
2306
+ if (!policy.acceptanceId) return { authorized: false, reason: "unaccepted" };
2307
+ let target;
2308
+ if (selection.kind === "primary") {
2309
+ target = policy.primary;
2310
+ } else {
2311
+ if (policy.fallback.mode !== "explicit") {
2312
+ return { authorized: false, reason: "fallback_disabled" };
2313
+ }
2314
+ target = policy.fallback.targets[selection.index];
2315
+ if (!target) return { authorized: false, reason: "fallback_unaccepted" };
2316
+ }
2317
+ if (!target) return { authorized: false, reason: "target_missing" };
2318
+ const acceptedTarget = normalizeTarget(target);
2319
+ if (acceptedTarget.provider !== descriptor.provider) {
2320
+ return { authorized: false, reason: "provider_mismatch" };
2321
+ }
2322
+ if (acceptedTarget.model !== descriptor.model) {
2323
+ return { authorized: false, reason: "model_mismatch" };
2324
+ }
2325
+ if (acceptedTarget.credentialMode !== descriptor.credentialMode) {
2326
+ return { authorized: false, reason: "credential_mode_mismatch" };
2327
+ }
2328
+ if (acceptedTarget.region !== descriptor.region) {
2329
+ return { authorized: false, reason: "region_mismatch" };
2330
+ }
2331
+ return {
2332
+ authorized: true,
2333
+ acceptanceId: policy.acceptanceId,
2334
+ target: acceptedTarget,
2335
+ selection
2336
+ };
2337
+ }
2338
+ function createTranscriptionSessionRequest(input) {
2339
+ const sequenceFloor = input.sequenceFloor ?? 0;
2340
+ if (!Number.isSafeInteger(sequenceFloor) || sequenceFloor < 0) return null;
2341
+ const authorization = authorizeTranscriptionAdapter(
2342
+ input.policy,
2343
+ input.adapter.descriptor,
2344
+ input.selection
2345
+ );
2346
+ if (!authorization.authorized) return null;
2347
+ return {
2348
+ localSessionId: input.localSessionId,
2349
+ policyAcceptanceId: authorization.acceptanceId,
2350
+ selection: authorization.selection,
2351
+ target: { ...authorization.target },
2352
+ language: input.policy.language?.trim() ?? null,
2353
+ autoDetectLanguage: input.policy.autoDetectLanguage,
2354
+ diarization: { ...input.policy.diarization },
2355
+ retention: { ...input.policy.retention },
2356
+ privacy: { ...input.policy.privacy },
2357
+ cost: { ...input.policy.cost },
2358
+ sequenceFloor
2359
+ };
2360
+ }
2361
+ function cloneDefaultPolicy() {
2362
+ return {
2363
+ ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
2364
+ diarization: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.diarization },
2365
+ retention: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.retention },
2366
+ privacy: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.privacy },
2367
+ fallback: { mode: "disabled", targets: [] },
2368
+ cost: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.cost }
2369
+ };
2370
+ }
2371
+ function isWorkspaceTranscriptionPolicy(value) {
2372
+ if (!isRecord(value) || typeof value.enabled !== "boolean") return false;
2373
+ if (!hasOnlyKeys(value, [
2374
+ "enabled",
2375
+ "acceptanceId",
2376
+ "primary",
2377
+ "language",
2378
+ "autoDetectLanguage",
2379
+ "diarization",
2380
+ "retention",
2381
+ "privacy",
2382
+ "fallback",
2383
+ "cost"
2384
+ ])) {
2385
+ return false;
2386
+ }
2387
+ if (!(value.acceptanceId === null || isUuid(value.acceptanceId))) return false;
2388
+ if (!(value.primary === null || isTarget(value.primary))) return false;
2389
+ if (!(value.language === null || isBoundedString(value.language, 64))) return false;
2390
+ if (typeof value.autoDetectLanguage !== "boolean") return false;
2391
+ if (!isRecord(value.diarization) || !hasOnlyKeys(value.diarization, ["enabled", "maxSpeakers"]) || typeof value.diarization.enabled !== "boolean" || !(value.diarization.maxSpeakers === null || isBoundedInteger(value.diarization.maxSpeakers, 100) && value.diarization.maxSpeakers >= 2)) {
2392
+ return false;
2393
+ }
2394
+ if (!value.diarization.enabled && value.diarization.maxSpeakers !== null) return false;
2395
+ if (!isRecord(value.retention) || !hasOnlyKeys(value.retention, ["mode", "maxDays"])) {
2396
+ return false;
2397
+ }
2398
+ if (value.retention.mode !== "none" && value.retention.mode !== "provider-policy") return false;
2399
+ if (!(value.retention.maxDays === null || isBoundedInteger(value.retention.maxDays, 3650))) {
2400
+ return false;
2401
+ }
2402
+ if (!isRecord(value.privacy) || !hasOnlyKeys(value.privacy, ["allowProviderLogging", "allowProviderTraining"]) || typeof value.privacy.allowProviderLogging !== "boolean" || typeof value.privacy.allowProviderTraining !== "boolean") {
2403
+ return false;
2404
+ }
2405
+ if (!isRecord(value.fallback) || !hasOnlyKeys(value.fallback, ["mode", "targets"])) {
2406
+ return false;
2407
+ }
2408
+ if (value.fallback.mode !== "disabled" && value.fallback.mode !== "explicit") return false;
2409
+ if (!Array.isArray(value.fallback.targets) || value.fallback.targets.length > 8 || !value.fallback.targets.every(isTarget)) {
2410
+ return false;
2411
+ }
2412
+ if (value.fallback.mode === "disabled" && value.fallback.targets.length !== 0) return false;
2413
+ if (value.fallback.mode === "explicit" && value.fallback.targets.length === 0) return false;
2414
+ if (!isRecord(value.cost) || !hasOnlyKeys(value.cost, ["currency", "maxPerHour", "maxPerMonth"]) || value.cost.currency !== "USD") {
2415
+ return false;
2416
+ }
2417
+ if (!isNullableBoundedNumber(value.cost.maxPerHour, 1e4)) return false;
2418
+ if (!isNullableBoundedNumber(value.cost.maxPerMonth, 1e6)) return false;
2419
+ if (value.enabled && (!value.acceptanceId || !value.primary)) return false;
2420
+ if (value.enabled && !value.autoDetectLanguage && value.language === null) return false;
2421
+ if (value.autoDetectLanguage && value.language !== null) return false;
2422
+ const targets = [value.primary, ...value.fallback.targets].filter(
2423
+ (target) => target !== null
2424
+ );
2425
+ if (new Set(targets.map(targetKey)).size !== targets.length) return false;
2426
+ return true;
2427
+ }
2428
+ function targetKey(target) {
2429
+ return [
2430
+ target.provider.trim(),
2431
+ target.model?.trim() ?? "",
2432
+ target.credentialMode,
2433
+ target.credentialConnectionId ?? "",
2434
+ target.region?.trim() ?? ""
2435
+ ].join("\0");
2436
+ }
2437
+ function isTarget(value) {
2438
+ if (!isRecord(value)) return false;
2439
+ if (!hasOnlyKeys(value, ["provider", "model", "credentialMode", "credentialConnectionId", "region"])) {
2440
+ return false;
2441
+ }
2442
+ if (!isBoundedString(value.provider, 128)) return false;
2443
+ if (!(value.model === null || isBoundedString(value.model, 256))) return false;
2444
+ if (value.credentialMode !== "managed" && value.credentialMode !== "byok") return false;
2445
+ if (value.provider.trim() === "azure-speech" && value.credentialMode !== "byok") return false;
2446
+ if (!(value.credentialConnectionId === null || isUuid(value.credentialConnectionId))) {
2447
+ return false;
2448
+ }
2449
+ if (!(value.region === null || isBoundedString(value.region, 128))) return false;
2450
+ if (value.credentialMode === "byok" && value.credentialConnectionId === null) return false;
2451
+ if (value.credentialMode === "managed" && value.credentialConnectionId !== null) return false;
2452
+ return true;
2453
+ }
2454
+ function normalizeTarget(target) {
2455
+ return {
2456
+ provider: target.provider.trim(),
2457
+ model: target.model?.trim() ?? null,
2458
+ credentialMode: target.credentialMode,
2459
+ credentialConnectionId: target.credentialConnectionId,
2460
+ region: target.region?.trim() ?? null
2461
+ };
2462
+ }
2463
+ function isRecord(value) {
2464
+ return typeof value === "object" && value !== null;
2465
+ }
2466
+ function hasOnlyKeys(value, keys) {
2467
+ const accepted = new Set(keys);
2468
+ return Object.keys(value).every((key) => accepted.has(key));
2469
+ }
2470
+ function isBoundedString(value, maximum) {
2471
+ return typeof value === "string" && value.trim().length > 0 && value.length <= maximum;
2472
+ }
2473
+ function isUuid(value) {
2474
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
2475
+ }
2476
+ function isBoundedInteger(value, maximum) {
2477
+ return Number.isInteger(value) && value >= 0 && value <= maximum;
2478
+ }
2479
+ function isNullableBoundedNumber(value, maximum) {
2480
+ return value === null || typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= maximum;
2481
+ }
1981
2482
  export {
2483
+ DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
1982
2484
  KNOWN_PERMISSIONS,
1983
2485
  KNOWN_USAGE_EVENT_TYPES,
2486
+ OPENGENI_API_CONTRACT_HEADER,
2487
+ OPENGENI_API_CONTRACT_REVISION,
2488
+ OpenGeniApiContractMismatchError,
1984
2489
  OpenGeniApiError,
1985
2490
  OpenGeniClient,
1986
2491
  OpenGeniStreamError,
@@ -1989,16 +2494,20 @@ export {
1989
2494
  TtydClientCommand,
1990
2495
  TtydServerCommand,
1991
2496
  applyUrlRotation,
2497
+ authorizeTranscriptionAdapter,
2498
+ createTranscriptionSessionRequest,
1992
2499
  desktopSocketUrl,
1993
2500
  formatSseEvent,
1994
2501
  isRetryableStreamError,
1995
2502
  nextDesktopState,
1996
2503
  parseSseStream,
1997
2504
  proxySessionEventStream,
2505
+ resolveWorkspaceTranscriptionPolicy,
1998
2506
  resumeSequenceFromRequest,
1999
2507
  sessionEventsToSseResponse,
2000
2508
  sessionEventsToSseStream,
2001
2509
  streamSessionEvents,
2510
+ streamWorkspaceControlEvents,
2002
2511
  terminalSocketUrl,
2003
2512
  ttydAuthFrame,
2004
2513
  ttydInputFrame,