@opengeni/sdk 0.15.0 → 0.23.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
@@ -293,8 +293,11 @@ async function sleep2(delayMs, signal) {
293
293
  // src/types.ts
294
294
  var SESSION_EVENT_TYPES = [
295
295
  "session.created",
296
+ // Defensive bounded projection for malformed/legacy oversized envelopes.
297
+ "session.event.envelope_omitted",
296
298
  "session.status.changed",
297
299
  "session.requiresAction",
300
+ "session.humanInput.requested",
298
301
  "session.context.compaction.requested",
299
302
  "session.context.compacted",
300
303
  "session.context.compaction.skipped",
@@ -302,6 +305,7 @@ var SESSION_EVENT_TYPES = [
302
305
  "user.message",
303
306
  "user.pause",
304
307
  "user.approvalDecision",
308
+ "user.humanInputResponse",
305
309
  "turn.queued",
306
310
  "turn.started",
307
311
  "turn.completed",
@@ -315,8 +319,10 @@ var SESSION_EVENT_TYPES = [
315
319
  "agent.reasoning.delta",
316
320
  "agent.toolCall.created",
317
321
  "agent.toolCall.output",
322
+ "agent.model.request",
318
323
  "agent.model.usage",
319
324
  "tool.auth_needed",
325
+ "credential.auth_needed",
320
326
  "agent.updated",
321
327
  "rig.setup.started",
322
328
  "rig.setup.completed",
@@ -365,11 +371,12 @@ var SESSION_EVENT_TYPES = [
365
371
  "terminal.pty.output.delta",
366
372
  "terminal.pty.exited",
367
373
  "session.title_set",
374
+ "session.mcp.approval_policy.updated",
368
375
  // Multi-account Codex (P1): the session's inference account changed.
369
376
  "codex.account.switched",
370
- // OPE-21 metadata-only per-turn credential selection audit.
377
+ // credential allocator metadata-only per-turn credential selection audit.
371
378
  "codex.credential.selected",
372
- // OPE-21 durable zero-capacity wait lifecycle. These are system/runtime
379
+ // credential allocator durable zero-capacity wait lifecycle. These are system/runtime
373
380
  // events, never synthetic user messages.
374
381
  "codex.capacity.waiting",
375
382
  "codex.capacity.resumed",
@@ -410,7 +417,7 @@ var KNOWN_PERMISSIONS = [
410
417
  "sessions:create",
411
418
  "sessions:read",
412
419
  "sessions:control",
413
- // Sandbox-surfacing (mirror of @opengeni/contracts Permission). stream:view is
420
+ // sandbox workspace (mirror of @opengeni/contracts Permission). stream:view is
414
421
  // strictly broader than sessions:read (un-redacted pixels); stream:control is
415
422
  // the never-granted-v1 raw-input plane; stream:acknowledge is the secret-leak
416
423
  // consent gate.
@@ -442,8 +449,10 @@ var KNOWN_PERMISSIONS = [
442
449
  "rigs:use",
443
450
  "rigs:manage"
444
451
  ];
445
- var OPENGENI_API_CONTRACT_REVISION = "2026-07-session-control-v1";
452
+ var OPENGENI_API_CONTRACT_REVISION = "2026-07-turn-instructions-v1";
446
453
  var OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract";
454
+ var RETAINED_OUTPUT_DEFAULT_PAGE_BYTES = 256 * 1024;
455
+ var RETAINED_OUTPUT_MAX_PAGE_BYTES = 1024 * 1024;
447
456
  var KNOWN_USAGE_EVENT_TYPES = [
448
457
  "agent_run.created",
449
458
  "agent_run.completed",
@@ -490,6 +499,18 @@ var OpenGeniClient = class {
490
499
  request
491
500
  );
492
501
  }
502
+ /**
503
+ * Replace one attached MCP server's approval policy. The change is captured
504
+ * by the next claimed attempt; already-claimed work keeps its immutable
505
+ * policy snapshot.
506
+ */
507
+ async updateSessionMcpApprovalPolicy(workspaceId, sessionId, serverId, request) {
508
+ return await this.requestJson(
509
+ "PATCH",
510
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/mcp-servers/${encodeURIComponent(serverId)}/approval-policy`,
511
+ request
512
+ );
513
+ }
493
514
  async listSessions(workspaceId, options = {}) {
494
515
  return await this.requestJson(
495
516
  "GET",
@@ -559,7 +580,8 @@ var OpenGeniClient = class {
559
580
  void 0,
560
581
  {
561
582
  ...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {}
562
- }
583
+ },
584
+ { signal: options.signal }
563
585
  );
564
586
  }
565
587
  /**
@@ -659,23 +681,95 @@ var OpenGeniClient = class {
659
681
  }
660
682
  // --- Events: replay, send, stream ----------------------------------------
661
683
  /**
662
- * Replay durable events by sequence, ascending. `before` is exclusive and
663
- * returns the newest matching window. With `compact`, consecutive delta runs
664
- * may be coalesced; `payload.coalescedUntil` carries the run's last sequence
665
- * for resume cursors.
684
+ * Return the events from one bounded page. With no cursor, this uses the safe
685
+ * semantic monitoring tail; pass explicit forensic options and a cursor for
686
+ * retained audit replay. Use `listEventPage` when projection, coverage, or
687
+ * resume-cursor facts are required.
666
688
  */
667
689
  async listEvents(workspaceId, sessionId, options = {}) {
668
- return await this.requestJson(
669
- "GET",
670
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`,
671
- void 0,
690
+ return (await this.listEventPage(workspaceId, sessionId, options)).events;
691
+ }
692
+ async listEventPage(workspaceId, sessionId, options = {}) {
693
+ if (options.latest && ["includeTypes", "excludeTypes", "includeClasses", "excludeClasses"].some(
694
+ (name) => Object.prototype.hasOwnProperty.call(options, name)
695
+ )) {
696
+ throw new TypeError("latest cannot be combined with event filters");
697
+ }
698
+ if (options.resultMode === "compact" && !options.latest) {
699
+ throw new TypeError("resultMode=compact requires latest");
700
+ }
701
+ const listOptions = options.resultMode === "compact" ? null : options;
702
+ const response = await this.fetchImpl(
703
+ this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, {
704
+ ...listOptions?.after !== void 0 ? { after: String(listOptions.after) } : {},
705
+ ...listOptions?.before !== void 0 ? { before: String(listOptions.before) } : {},
706
+ ...listOptions?.limit !== void 0 ? { limit: String(listOptions.limit) } : {},
707
+ ...listOptions?.compact ? { compact: "1" } : {},
708
+ ...options.mode ? { mode: options.mode } : {},
709
+ ...listOptions?.direction ? { direction: listOptions.direction } : {},
710
+ ...options.payloadMode ? { payloadMode: options.payloadMode } : {},
711
+ ...options.resultMode ? { resultMode: options.resultMode } : {},
712
+ ...listOptions?.includeTypes?.length ? { includeTypes: listOptions.includeTypes.join(",") } : {},
713
+ ...listOptions?.excludeTypes?.length ? { excludeTypes: listOptions.excludeTypes.join(",") } : {},
714
+ ...listOptions?.includeClasses?.length ? { includeClasses: listOptions.includeClasses.join(",") } : {},
715
+ ...listOptions?.excludeClasses?.length ? { excludeClasses: listOptions.excludeClasses.join(",") } : {},
716
+ ...options.latest ? { latest: options.latest } : {}
717
+ }),
672
718
  {
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" } : {}
719
+ method: "GET",
720
+ headers: { ...this.headers(), Accept: "application/json" }
677
721
  }
678
722
  );
723
+ assertApiContractResponse(response);
724
+ if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
725
+ const body = await response.json();
726
+ if (options.resultMode === "compact") {
727
+ return body;
728
+ }
729
+ const events = body;
730
+ const integerHeader = (name) => {
731
+ const raw = response.headers.get(name);
732
+ if (raw === null) return null;
733
+ const value = Number(raw);
734
+ return Number.isSafeInteger(value) && value >= 0 ? value : null;
735
+ };
736
+ const mode = response.headers.get("X-OpenGeni-Event-Mode") === "forensic" ? "forensic" : "monitoring";
737
+ const direction = response.headers.get("X-OpenGeni-Event-Direction") === "after" ? "after" : "before";
738
+ const payloadHeader = response.headers.get("X-OpenGeni-Payload-Mode");
739
+ const payloadMode = payloadHeader === "none" || payloadHeader === "full" ? payloadHeader : "summary";
740
+ const first = integerHeader("X-OpenGeni-Covered-First");
741
+ const last = integerHeader("X-OpenGeni-Covered-Last");
742
+ const bytes = integerHeader("X-OpenGeni-Page-Bytes") ?? new TextEncoder().encode(JSON.stringify(events)).byteLength;
743
+ const maxBytes = integerHeader("X-OpenGeni-Page-Max-Bytes") ?? 1024 * 1024;
744
+ const truncatedByHeader = response.headers.get("X-OpenGeni-Truncated-By");
745
+ const truncatedBy = truncatedByHeader === "count" || truncatedByHeader === "bytes" || truncatedByHeader === "http_bytes" ? truncatedByHeader : null;
746
+ return {
747
+ events,
748
+ mode,
749
+ payloadMode,
750
+ direction,
751
+ bytes,
752
+ maxBytes,
753
+ truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
754
+ hasMore: response.headers.get("X-OpenGeni-Has-More") === "true",
755
+ truncatedBy,
756
+ coveredSequence: first === null || last === null ? null : { first, last },
757
+ nextAfter: integerHeader("X-OpenGeni-Next-After"),
758
+ nextBefore: integerHeader("X-OpenGeni-Next-Before"),
759
+ forensicExact: response.headers.get("X-OpenGeni-Forensic-Exact") === "true"
760
+ };
761
+ }
762
+ /**
763
+ * Fetch the authoritative newest-sequence semantic result directly. This is
764
+ * the callback-loss recovery path: it reads one compact durable result and
765
+ * never creates a model turn. `latest: "receipt"` aliases `tool_receipt`;
766
+ * turn generation remains scoped retry metadata.
767
+ */
768
+ async getLatestEventResult(workspaceId, sessionId, options = { latest: "terminal" }) {
769
+ return await this.listEventPage(workspaceId, sessionId, {
770
+ ...options,
771
+ resultMode: "compact"
772
+ });
679
773
  }
680
774
  /** POST a user/control event to the session. Returns the accepted event. */
681
775
  async sendEvent(workspaceId, sessionId, event) {
@@ -710,6 +804,28 @@ var OpenGeniClient = class {
710
804
  payload
711
805
  });
712
806
  }
807
+ async listHumanInputRequests(workspaceId, sessionId, options = {}) {
808
+ const result = await this.requestJson(
809
+ "GET",
810
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests`,
811
+ void 0,
812
+ options.status ? { status: options.status } : void 0
813
+ );
814
+ return result.requests;
815
+ }
816
+ async getHumanInputRequest(workspaceId, sessionId, requestId) {
817
+ return await this.requestJson(
818
+ "GET",
819
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests/${requestId}`
820
+ );
821
+ }
822
+ async submitHumanInputResponse(workspaceId, sessionId, requestId, response, options = {}) {
823
+ return await this.sendEvent(workspaceId, sessionId, {
824
+ type: "user.humanInputResponse",
825
+ ...options.clientEventId ? { clientEventId: options.clientEventId } : {},
826
+ payload: { requestId, response }
827
+ });
828
+ }
713
829
  /**
714
830
  * Live-stream a session's events with automatic reconnect, resume from the
715
831
  * last seen sequence, gap backfill, and duplicate suppression. See
@@ -818,15 +934,35 @@ var OpenGeniClient = class {
818
934
  );
819
935
  }
820
936
  async listWorkspaceControlEvents(workspaceId, options = {}) {
821
- return await this.requestJson(
822
- "GET",
823
- `/v1/workspaces/${workspaceId}/control-events`,
824
- void 0,
825
- {
937
+ return (await this.listWorkspaceControlEventPage(workspaceId, options)).events;
938
+ }
939
+ /** Count/byte-bounded page plus an explicit continuation cursor. */
940
+ async listWorkspaceControlEventPage(workspaceId, options = {}) {
941
+ const response = await this.fetchImpl(
942
+ this.url(`/v1/workspaces/${workspaceId}/control-events`, {
826
943
  ...options.after !== void 0 ? { after: String(options.after) } : {},
827
944
  ...options.limit !== void 0 ? { limit: String(options.limit) } : {}
945
+ }),
946
+ {
947
+ method: "GET",
948
+ headers: { ...this.headers(), Accept: "application/json" }
828
949
  }
829
950
  );
951
+ assertApiContractResponse(response);
952
+ if (!response.ok) {
953
+ throw new OpenGeniApiError(response.status, await safeText(response));
954
+ }
955
+ const events = await response.json();
956
+ const bytesHeader = response.headers.get("X-OpenGeni-Page-Bytes");
957
+ const nextHeader = response.headers.get("X-OpenGeni-Next-After");
958
+ const parsedBytes = bytesHeader === null ? Number.NaN : Number(bytesHeader);
959
+ const parsedNext = nextHeader === null ? null : Number(nextHeader);
960
+ return {
961
+ events,
962
+ bytes: Number.isSafeInteger(parsedBytes) && parsedBytes >= 0 ? parsedBytes : new TextEncoder().encode(JSON.stringify(events)).byteLength,
963
+ truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
964
+ nextAfter: parsedNext !== null && Number.isSafeInteger(parsedNext) && parsedNext >= 0 ? parsedNext : null
965
+ };
830
966
  }
831
967
  streamWorkspaceControlEvents(workspaceId, options = {}) {
832
968
  return streamWorkspaceControlEvents(this.workspaceControlStreamTransport(workspaceId), options);
@@ -926,19 +1062,23 @@ var OpenGeniClient = class {
926
1062
  // synchronous API-direct point query; the fs.changed/git.changed/terminal.pty.*
927
1063
  // notifications + the PTY output stream arrive on the existing event SSE.
928
1064
  /** FileSystem: list a directory tree (feeds the Pierre file tree). */
929
- async fsList(workspaceId, sessionId, request = {}) {
1065
+ async fsList(workspaceId, sessionId, request = {}, options = {}) {
930
1066
  return await this.requestJson(
931
1067
  "POST",
932
1068
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list`,
933
- request
1069
+ request,
1070
+ {},
1071
+ options
934
1072
  );
935
1073
  }
936
1074
  /** FileSystem: read a file (text or base64; binary-safe, size-capped). */
937
- async fsRead(workspaceId, sessionId, request) {
1075
+ async fsRead(workspaceId, sessionId, request, options = {}) {
938
1076
  return await this.requestJson(
939
1077
  "POST",
940
1078
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/read`,
941
- request
1079
+ request,
1080
+ {},
1081
+ options
942
1082
  );
943
1083
  }
944
1084
  /** FileSystem: write a file (last-writer-wins; emits fs.changed). */
@@ -974,19 +1114,23 @@ var OpenGeniClient = class {
974
1114
  );
975
1115
  }
976
1116
  /** Git: working-tree/index status (the Pierre file-status feed). */
977
- async gitStatus(workspaceId, sessionId, request = {}) {
1117
+ async gitStatus(workspaceId, sessionId, request = {}, options = {}) {
978
1118
  return await this.requestJson(
979
1119
  "POST",
980
1120
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/status`,
981
- request
1121
+ request,
1122
+ {},
1123
+ options
982
1124
  );
983
1125
  }
984
1126
  /** Git: structured diff hunks (the Pierre diff feed). */
985
- async gitDiff(workspaceId, sessionId, request = {}) {
1127
+ async gitDiff(workspaceId, sessionId, request = {}, options = {}) {
986
1128
  return await this.requestJson(
987
1129
  "POST",
988
1130
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/diff`,
989
- request
1131
+ request,
1132
+ {},
1133
+ options
990
1134
  );
991
1135
  }
992
1136
  /** Git: commit log. */
@@ -1009,23 +1153,27 @@ var OpenGeniClient = class {
1009
1153
  * (tree + per-repo diff + file after-image refs), served from durable storage
1010
1154
  * WITHOUT warming a machine — the workbench cold-paint source. Returns
1011
1155
  * `{available:false}` when no capture exists yet (fall back to the live path). */
1012
- async getWorkspaceCapture(workspaceId, sessionId) {
1156
+ async getWorkspaceCapture(workspaceId, sessionId, options = {}) {
1013
1157
  return await this.requestJson(
1014
1158
  "GET",
1015
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture`
1159
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture`,
1160
+ void 0,
1161
+ {},
1162
+ options
1016
1163
  );
1017
1164
  }
1018
1165
  /** Workspace capture: a single file's after-image from the capture (revision
1019
1166
  * pins a specific one; omitted → latest). Content is inline for small files,
1020
1167
  * else a short-TTL signed URL; a tooLarge file returns metadata only. */
1021
- async getWorkspaceCaptureFile(workspaceId, sessionId, path, revision) {
1168
+ async getWorkspaceCaptureFile(workspaceId, sessionId, path, revision, options = {}) {
1022
1169
  const query = { path };
1023
1170
  if (revision !== void 0) query.revision = String(revision);
1024
1171
  return await this.requestJson(
1025
1172
  "GET",
1026
1173
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture/file`,
1027
1174
  void 0,
1028
- query
1175
+ query,
1176
+ options
1029
1177
  );
1030
1178
  }
1031
1179
  /** Terminal: run a bounded command, returning buffered stdout/stderr inline. */
@@ -1080,10 +1228,13 @@ var OpenGeniClient = class {
1080
1228
  * liveness the client polls on while `cold`/`warming`. The desktop URL/token
1081
1229
  * are minted in-process only when the box is warm AND the principal has
1082
1230
  * acknowledged the un-redacted plane. */
1083
- async getStreamCapabilities(workspaceId, sessionId) {
1231
+ async getStreamCapabilities(workspaceId, sessionId, options = {}) {
1084
1232
  return await this.requestJson(
1085
1233
  "GET",
1086
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`
1234
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`,
1235
+ void 0,
1236
+ {},
1237
+ options
1087
1238
  );
1088
1239
  }
1089
1240
  /** Record the calling principal's acknowledgment of the un-redacted desktop
@@ -1146,6 +1297,13 @@ var OpenGeniClient = class {
1146
1297
  }
1147
1298
  return config;
1148
1299
  }
1300
+ /** Authenticated model definitions plus workspace-specific selectability. */
1301
+ async getWorkspaceModelCatalog(workspaceId) {
1302
+ return await this.requestJson(
1303
+ "GET",
1304
+ `/v1/workspaces/${workspaceId}/model-catalog`
1305
+ );
1306
+ }
1149
1307
  /** The caller's access context: subject, account + workspace grants, defaults. */
1150
1308
  async getAccessContext() {
1151
1309
  return await this.requestJson("GET", "/v1/access/me");
@@ -1476,6 +1634,71 @@ var OpenGeniClient = class {
1476
1634
  `/v1/workspaces/${workspaceId}/files/${fileId}`
1477
1635
  );
1478
1636
  }
1637
+ /** Read provider-neutral retained evidence metadata; never returns a storage location. */
1638
+ async getRetainedArtifact(workspaceId, artifactId) {
1639
+ return await this.requestJson(
1640
+ "GET",
1641
+ `/v1/workspaces/${workspaceId}/artifacts/${artifactId}`
1642
+ );
1643
+ }
1644
+ /**
1645
+ * Read at most one authenticated retained-evidence range from the API. This
1646
+ * deliberately does not use the ordinary signed file-download URL.
1647
+ */
1648
+ async getRetainedArtifactContent(workspaceId, artifactId, options = {}) {
1649
+ if (options.range && (options.range.length > 128 || /[^\x20-\x7e]/.test(options.range))) {
1650
+ throw new RangeError("retained artifact range must be at most 128 printable ASCII bytes");
1651
+ }
1652
+ const response = await this.fetchImpl(
1653
+ this.url(`/v1/workspaces/${workspaceId}/artifacts/${artifactId}/content`),
1654
+ {
1655
+ method: "GET",
1656
+ headers: {
1657
+ ...this.headers(),
1658
+ Accept: "application/octet-stream",
1659
+ ...options.range ? { Range: options.range } : {}
1660
+ },
1661
+ ...options.signal ? { signal: options.signal } : {}
1662
+ }
1663
+ );
1664
+ try {
1665
+ assertApiContractResponse(response);
1666
+ } catch (error) {
1667
+ await cancelResponseBody(response, "retained artifact API contract mismatch");
1668
+ throw error;
1669
+ }
1670
+ if (!response.ok) {
1671
+ throw new OpenGeniApiError(response.status, await safeBoundedText(response));
1672
+ }
1673
+ if (response.status !== 200 && response.status !== 206) {
1674
+ await cancelResponseBody(response, "unexpected retained artifact response status");
1675
+ throw new OpenGeniApiError(response.status, "unexpected retained artifact response status");
1676
+ }
1677
+ if (response.headers.get("accept-ranges") !== "bytes") {
1678
+ await cancelResponseBody(response, "retained artifact response omitted byte-range support");
1679
+ throw new OpenGeniApiError(502, "retained artifact response omitted byte-range support");
1680
+ }
1681
+ let declaredLength;
1682
+ try {
1683
+ declaredLength = parseBoundedContentLength(response.headers.get("content-length"));
1684
+ } catch (error) {
1685
+ await cancelResponseBody(response, "invalid retained artifact content-length");
1686
+ throw error;
1687
+ }
1688
+ const bytes = await readBoundedResponseBytes(
1689
+ response,
1690
+ RETAINED_OUTPUT_MAX_PAGE_BYTES,
1691
+ declaredLength
1692
+ );
1693
+ return {
1694
+ bytes,
1695
+ status: response.status,
1696
+ contentType: response.headers.get("content-type") ?? "application/octet-stream",
1697
+ contentLength: bytes.byteLength,
1698
+ contentRange: response.headers.get("content-range"),
1699
+ acceptRanges: "bytes"
1700
+ };
1701
+ }
1479
1702
  /** Mint a short-lived signed download URL for a ready file. */
1480
1703
  async createFileDownloadUrl(workspaceId, fileId) {
1481
1704
  return await this.requestJson(
@@ -1727,14 +1950,13 @@ var OpenGeniClient = class {
1727
1950
  return logoAssetPath ? `${this.baseUrl}/v1/${logoAssetPath}` : null;
1728
1951
  }
1729
1952
  // --- GitHub ----------------------------------------------------------------------------------
1730
- /** GitHub App configuration status + a signed install URL when configured. */
1953
+ /** GitHub App configuration status; install/link URLs are null while new binding is disabled. */
1731
1954
  async getGitHubApp(workspaceId) {
1732
1955
  return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/github/app`);
1733
1956
  }
1734
1957
  /**
1735
- * Browser entry point that plants the CSRF cookie and forwards to GitHub's
1736
- * install page. Open this in a browser (it redirects); `state` comes from
1737
- * `getGitHubApp().installUrl` or a github_connect_link tool.
1958
+ * Compatibility URL for previously issued state. New installation binding is
1959
+ * disabled, so the endpoint validates state and terminates with HTTP 410.
1738
1960
  */
1739
1961
  githubConnectUrl(workspaceId, state) {
1740
1962
  return this.url(`/v1/workspaces/${workspaceId}/github/connect`, { state });
@@ -1752,6 +1974,13 @@ var OpenGeniClient = class {
1752
1974
  `/v1/workspaces/${workspaceId}/github/repositories/sync`
1753
1975
  );
1754
1976
  }
1977
+ /** Remove one workspace binding without uninstalling the GitHub App itself. */
1978
+ async unlinkGitHubInstallation(workspaceId, installationId) {
1979
+ await this.requestVoid(
1980
+ "DELETE",
1981
+ `/v1/workspaces/${workspaceId}/github/installations/${installationId}`
1982
+ );
1983
+ }
1755
1984
  /** Build a GitHub App manifest + the GitHub URL to submit it to. */
1756
1985
  async createGitHubAppManifest(workspaceId, request = {}) {
1757
1986
  return await this.requestJson(
@@ -1863,6 +2092,13 @@ var OpenGeniClient = class {
1863
2092
  `/v1/workspaces/${workspaceId}/codex/usage/refresh`
1864
2093
  );
1865
2094
  }
2095
+ /** Live independently-settled quota + reset-credit overview for every account. */
2096
+ async codexOverview(workspaceId) {
2097
+ return await this.requestJson(
2098
+ "GET",
2099
+ `/v1/workspaces/${workspaceId}/codex/overview`
2100
+ );
2101
+ }
1866
2102
  /** Disconnect ALL accounts (legacy workspace-wide). Prefer `disconnectCodexAccount`. */
1867
2103
  async codexDisconnect(workspaceId) {
1868
2104
  return await this.requestJson(
@@ -1892,6 +2128,14 @@ var OpenGeniClient = class {
1892
2128
  patch
1893
2129
  );
1894
2130
  }
2131
+ /** Toggle only NEW automatic allocations under independent allocator OCC. */
2132
+ async setCodexAccountAllocator(workspaceId, accountId, input) {
2133
+ return await this.requestJson(
2134
+ "PATCH",
2135
+ `/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/allocator`,
2136
+ input
2137
+ );
2138
+ }
1895
2139
  /** Disconnect ONE Codex account by id (re-picks active when the removed one was active). */
1896
2140
  async disconnectCodexAccount(workspaceId, accountId) {
1897
2141
  return await this.requestJson(
@@ -1915,7 +2159,7 @@ var OpenGeniClient = class {
1915
2159
  { target }
1916
2160
  );
1917
2161
  }
1918
- async requestJson(method, path, body, query = {}) {
2162
+ async requestJson(method, path, body, query = {}, options = {}) {
1919
2163
  const response = await this.fetchImpl(this.url(path, query), {
1920
2164
  method,
1921
2165
  headers: {
@@ -1923,7 +2167,8 @@ var OpenGeniClient = class {
1923
2167
  Accept: "application/json",
1924
2168
  ...body !== void 0 ? { "Content-Type": "application/json" } : {}
1925
2169
  },
1926
- ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2170
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {},
2171
+ ...options.signal ? { signal: options.signal } : {}
1927
2172
  });
1928
2173
  assertApiContractResponse(response);
1929
2174
  if (!response.ok) {
@@ -1961,6 +2206,62 @@ async function safeText(response) {
1961
2206
  return "";
1962
2207
  }
1963
2208
  }
2209
+ async function safeBoundedText(response) {
2210
+ try {
2211
+ return new TextDecoder().decode(await readBoundedResponseBytes(response, 64 * 1024, null));
2212
+ } catch {
2213
+ return "";
2214
+ }
2215
+ }
2216
+ async function cancelResponseBody(response, reason) {
2217
+ await response.body?.cancel(reason).catch(() => void 0);
2218
+ }
2219
+ function parseBoundedContentLength(value) {
2220
+ if (value === null) return null;
2221
+ if (!/^\d+$/.test(value)) {
2222
+ throw new OpenGeniApiError(502, "invalid retained artifact content-length");
2223
+ }
2224
+ const length = Number(value);
2225
+ if (!Number.isSafeInteger(length) || length > RETAINED_OUTPUT_MAX_PAGE_BYTES) {
2226
+ throw new OpenGeniApiError(502, "retained artifact response exceeds the SDK byte limit");
2227
+ }
2228
+ return length;
2229
+ }
2230
+ async function readBoundedResponseBytes(response, maxBytes, expectedBytes) {
2231
+ if (!response.body) {
2232
+ if (expectedBytes !== null && expectedBytes !== 0) {
2233
+ throw new OpenGeniApiError(502, "retained artifact response length mismatch");
2234
+ }
2235
+ return new Uint8Array();
2236
+ }
2237
+ const reader = response.body.getReader();
2238
+ const chunks = [];
2239
+ let totalBytes = 0;
2240
+ try {
2241
+ while (true) {
2242
+ const { done, value } = await reader.read();
2243
+ if (done) break;
2244
+ totalBytes += value.byteLength;
2245
+ if (totalBytes > maxBytes) {
2246
+ await reader.cancel("retained artifact response exceeded the SDK byte limit").catch(() => void 0);
2247
+ throw new OpenGeniApiError(502, "retained artifact response exceeds the SDK byte limit");
2248
+ }
2249
+ chunks.push(value);
2250
+ }
2251
+ } finally {
2252
+ reader.releaseLock();
2253
+ }
2254
+ if (expectedBytes !== null && totalBytes !== expectedBytes) {
2255
+ throw new OpenGeniApiError(502, "retained artifact response length mismatch");
2256
+ }
2257
+ const bytes = new Uint8Array(totalBytes);
2258
+ let offset = 0;
2259
+ for (const chunk of chunks) {
2260
+ bytes.set(chunk, offset);
2261
+ offset += chunk.byteLength;
2262
+ }
2263
+ return bytes;
2264
+ }
1964
2265
 
1965
2266
  // src/proxy.ts
1966
2267
  function formatSseEvent(event) {
@@ -2144,7 +2445,221 @@ function ttydInputFrame(data) {
2144
2445
  function ttydResizeFrame(columns, rows) {
2145
2446
  return TtydClientCommand.RESIZE + JSON.stringify({ columns, rows });
2146
2447
  }
2448
+
2449
+ // src/transcription.ts
2450
+ var DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY = {
2451
+ enabled: false,
2452
+ acceptanceId: null,
2453
+ primary: null,
2454
+ language: null,
2455
+ autoDetectLanguage: false,
2456
+ diarization: { enabled: false, maxSpeakers: null },
2457
+ retention: { mode: "none", maxDays: null },
2458
+ privacy: { allowProviderLogging: false, allowProviderTraining: false },
2459
+ fallback: { mode: "disabled", targets: [] },
2460
+ cost: { currency: "USD", maxPerHour: null, maxPerMonth: null }
2461
+ };
2462
+ function resolveWorkspaceTranscriptionPolicy(settings) {
2463
+ if (!isRecord(settings)) return cloneDefaultPolicy();
2464
+ const candidate = settings.transcription;
2465
+ if (!isWorkspaceTranscriptionPolicy(candidate)) return cloneDefaultPolicy();
2466
+ return {
2467
+ ...candidate,
2468
+ primary: candidate.primary ? normalizeTarget(candidate.primary) : null,
2469
+ language: candidate.language?.trim() ?? null,
2470
+ diarization: { ...candidate.diarization },
2471
+ retention: { ...candidate.retention },
2472
+ privacy: { ...candidate.privacy },
2473
+ fallback: {
2474
+ mode: candidate.fallback.mode,
2475
+ targets: candidate.fallback.targets.map(normalizeTarget)
2476
+ },
2477
+ cost: { ...candidate.cost }
2478
+ };
2479
+ }
2480
+ function authorizeTranscriptionAdapter(policy, descriptor, selection = { kind: "primary" }) {
2481
+ if (!isWorkspaceTranscriptionPolicy(policy)) {
2482
+ return { authorized: false, reason: "unaccepted" };
2483
+ }
2484
+ if (!policy.enabled) return { authorized: false, reason: "disabled" };
2485
+ if (!policy.acceptanceId) return { authorized: false, reason: "unaccepted" };
2486
+ let target;
2487
+ if (selection.kind === "primary") {
2488
+ target = policy.primary;
2489
+ } else {
2490
+ if (policy.fallback.mode !== "explicit") {
2491
+ return { authorized: false, reason: "fallback_disabled" };
2492
+ }
2493
+ target = policy.fallback.targets[selection.index];
2494
+ if (!target) return { authorized: false, reason: "fallback_unaccepted" };
2495
+ }
2496
+ if (!target) return { authorized: false, reason: "target_missing" };
2497
+ const acceptedTarget = normalizeTarget(target);
2498
+ if (acceptedTarget.provider !== descriptor.provider) {
2499
+ return { authorized: false, reason: "provider_mismatch" };
2500
+ }
2501
+ if (acceptedTarget.model !== descriptor.model) {
2502
+ return { authorized: false, reason: "model_mismatch" };
2503
+ }
2504
+ if (acceptedTarget.credentialMode !== descriptor.credentialMode) {
2505
+ return { authorized: false, reason: "credential_mode_mismatch" };
2506
+ }
2507
+ if (acceptedTarget.region !== descriptor.region) {
2508
+ return { authorized: false, reason: "region_mismatch" };
2509
+ }
2510
+ return {
2511
+ authorized: true,
2512
+ acceptanceId: policy.acceptanceId,
2513
+ target: acceptedTarget,
2514
+ selection
2515
+ };
2516
+ }
2517
+ function createTranscriptionSessionRequest(input) {
2518
+ const sequenceFloor = input.sequenceFloor ?? 0;
2519
+ if (!Number.isSafeInteger(sequenceFloor) || sequenceFloor < 0) return null;
2520
+ const authorization = authorizeTranscriptionAdapter(
2521
+ input.policy,
2522
+ input.adapter.descriptor,
2523
+ input.selection
2524
+ );
2525
+ if (!authorization.authorized) return null;
2526
+ return {
2527
+ localSessionId: input.localSessionId,
2528
+ policyAcceptanceId: authorization.acceptanceId,
2529
+ selection: authorization.selection,
2530
+ target: { ...authorization.target },
2531
+ language: input.policy.language?.trim() ?? null,
2532
+ autoDetectLanguage: input.policy.autoDetectLanguage,
2533
+ diarization: { ...input.policy.diarization },
2534
+ retention: { ...input.policy.retention },
2535
+ privacy: { ...input.policy.privacy },
2536
+ cost: { ...input.policy.cost },
2537
+ sequenceFloor
2538
+ };
2539
+ }
2540
+ function cloneDefaultPolicy() {
2541
+ return {
2542
+ ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
2543
+ diarization: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.diarization },
2544
+ retention: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.retention },
2545
+ privacy: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.privacy },
2546
+ fallback: { mode: "disabled", targets: [] },
2547
+ cost: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.cost }
2548
+ };
2549
+ }
2550
+ function isWorkspaceTranscriptionPolicy(value) {
2551
+ if (!isRecord(value) || typeof value.enabled !== "boolean") return false;
2552
+ if (!hasOnlyKeys(value, [
2553
+ "enabled",
2554
+ "acceptanceId",
2555
+ "primary",
2556
+ "language",
2557
+ "autoDetectLanguage",
2558
+ "diarization",
2559
+ "retention",
2560
+ "privacy",
2561
+ "fallback",
2562
+ "cost"
2563
+ ])) {
2564
+ return false;
2565
+ }
2566
+ if (!(value.acceptanceId === null || isUuid(value.acceptanceId))) return false;
2567
+ if (!(value.primary === null || isTarget(value.primary))) return false;
2568
+ if (!(value.language === null || isBoundedString(value.language, 64))) return false;
2569
+ if (typeof value.autoDetectLanguage !== "boolean") return false;
2570
+ 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)) {
2571
+ return false;
2572
+ }
2573
+ if (!value.diarization.enabled && value.diarization.maxSpeakers !== null) return false;
2574
+ if (!isRecord(value.retention) || !hasOnlyKeys(value.retention, ["mode", "maxDays"])) {
2575
+ return false;
2576
+ }
2577
+ if (value.retention.mode !== "none" && value.retention.mode !== "provider-policy") return false;
2578
+ if (!(value.retention.maxDays === null || isBoundedInteger(value.retention.maxDays, 3650))) {
2579
+ return false;
2580
+ }
2581
+ if (!isRecord(value.privacy) || !hasOnlyKeys(value.privacy, ["allowProviderLogging", "allowProviderTraining"]) || typeof value.privacy.allowProviderLogging !== "boolean" || typeof value.privacy.allowProviderTraining !== "boolean") {
2582
+ return false;
2583
+ }
2584
+ if (!isRecord(value.fallback) || !hasOnlyKeys(value.fallback, ["mode", "targets"])) {
2585
+ return false;
2586
+ }
2587
+ if (value.fallback.mode !== "disabled" && value.fallback.mode !== "explicit") return false;
2588
+ if (!Array.isArray(value.fallback.targets) || value.fallback.targets.length > 8 || !value.fallback.targets.every(isTarget)) {
2589
+ return false;
2590
+ }
2591
+ if (value.fallback.mode === "disabled" && value.fallback.targets.length !== 0) return false;
2592
+ if (value.fallback.mode === "explicit" && value.fallback.targets.length === 0) return false;
2593
+ if (!isRecord(value.cost) || !hasOnlyKeys(value.cost, ["currency", "maxPerHour", "maxPerMonth"]) || value.cost.currency !== "USD") {
2594
+ return false;
2595
+ }
2596
+ if (!isNullableBoundedNumber(value.cost.maxPerHour, 1e4)) return false;
2597
+ if (!isNullableBoundedNumber(value.cost.maxPerMonth, 1e6)) return false;
2598
+ if (value.enabled && (!value.acceptanceId || !value.primary)) return false;
2599
+ if (value.enabled && !value.autoDetectLanguage && value.language === null) return false;
2600
+ if (value.autoDetectLanguage && value.language !== null) return false;
2601
+ const targets = [value.primary, ...value.fallback.targets].filter(
2602
+ (target) => target !== null
2603
+ );
2604
+ if (new Set(targets.map(targetKey)).size !== targets.length) return false;
2605
+ return true;
2606
+ }
2607
+ function targetKey(target) {
2608
+ return [
2609
+ target.provider.trim(),
2610
+ target.model?.trim() ?? "",
2611
+ target.credentialMode,
2612
+ target.credentialConnectionId ?? "",
2613
+ target.region?.trim() ?? ""
2614
+ ].join("\0");
2615
+ }
2616
+ function isTarget(value) {
2617
+ if (!isRecord(value)) return false;
2618
+ if (!hasOnlyKeys(value, ["provider", "model", "credentialMode", "credentialConnectionId", "region"])) {
2619
+ return false;
2620
+ }
2621
+ if (!isBoundedString(value.provider, 128)) return false;
2622
+ if (!(value.model === null || isBoundedString(value.model, 256))) return false;
2623
+ if (value.credentialMode !== "managed" && value.credentialMode !== "byok") return false;
2624
+ if (value.provider.trim() === "azure-speech" && value.credentialMode !== "byok") return false;
2625
+ if (!(value.credentialConnectionId === null || isUuid(value.credentialConnectionId))) {
2626
+ return false;
2627
+ }
2628
+ if (!(value.region === null || isBoundedString(value.region, 128))) return false;
2629
+ if (value.credentialMode === "byok" && value.credentialConnectionId === null) return false;
2630
+ if (value.credentialMode === "managed" && value.credentialConnectionId !== null) return false;
2631
+ return true;
2632
+ }
2633
+ function normalizeTarget(target) {
2634
+ return {
2635
+ provider: target.provider.trim(),
2636
+ model: target.model?.trim() ?? null,
2637
+ credentialMode: target.credentialMode,
2638
+ credentialConnectionId: target.credentialConnectionId,
2639
+ region: target.region?.trim() ?? null
2640
+ };
2641
+ }
2642
+ function isRecord(value) {
2643
+ return typeof value === "object" && value !== null;
2644
+ }
2645
+ function hasOnlyKeys(value, keys) {
2646
+ const accepted = new Set(keys);
2647
+ return Object.keys(value).every((key) => accepted.has(key));
2648
+ }
2649
+ function isBoundedString(value, maximum) {
2650
+ return typeof value === "string" && value.trim().length > 0 && value.length <= maximum;
2651
+ }
2652
+ function isUuid(value) {
2653
+ 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);
2654
+ }
2655
+ function isBoundedInteger(value, maximum) {
2656
+ return Number.isInteger(value) && value >= 0 && value <= maximum;
2657
+ }
2658
+ function isNullableBoundedNumber(value, maximum) {
2659
+ return value === null || typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= maximum;
2660
+ }
2147
2661
  export {
2662
+ DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
2148
2663
  KNOWN_PERMISSIONS,
2149
2664
  KNOWN_USAGE_EVENT_TYPES,
2150
2665
  OPENGENI_API_CONTRACT_HEADER,
@@ -2153,17 +2668,22 @@ export {
2153
2668
  OpenGeniApiError,
2154
2669
  OpenGeniClient,
2155
2670
  OpenGeniStreamError,
2671
+ RETAINED_OUTPUT_DEFAULT_PAGE_BYTES,
2672
+ RETAINED_OUTPUT_MAX_PAGE_BYTES,
2156
2673
  SESSION_EVENT_TYPES,
2157
2674
  TTYD_SUBPROTOCOL,
2158
2675
  TtydClientCommand,
2159
2676
  TtydServerCommand,
2160
2677
  applyUrlRotation,
2678
+ authorizeTranscriptionAdapter,
2679
+ createTranscriptionSessionRequest,
2161
2680
  desktopSocketUrl,
2162
2681
  formatSseEvent,
2163
2682
  isRetryableStreamError,
2164
2683
  nextDesktopState,
2165
2684
  parseSseStream,
2166
2685
  proxySessionEventStream,
2686
+ resolveWorkspaceTranscriptionPolicy,
2167
2687
  resumeSequenceFromRequest,
2168
2688
  sessionEventsToSseResponse,
2169
2689
  sessionEventsToSseStream,