@opengeni/sdk 0.23.0 → 0.25.5

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
@@ -1,14 +1,67 @@
1
1
  // src/errors.ts
2
2
  var OpenGeniApiError = class extends Error {
3
3
  status;
4
+ code;
5
+ retryable;
6
+ correlationId;
7
+ /** True only when an uncontrolled transport failed after a mutation may have been accepted. */
8
+ outcomeUnknown;
4
9
  body;
5
- constructor(status, body) {
6
- super(`OpenGeni API ${status}: ${body || "(empty body)"}`);
10
+ constructor(status, body, options = {}) {
11
+ const decoded = decodeApiErrorBody(body);
12
+ const correlationId = decoded?.requestId ?? boundedCorrelationId(options.correlationId);
13
+ const gatewayFailure = status >= 502 && status <= 504;
14
+ const fromResponse = options.mutation !== void 0;
15
+ const message = decoded?.message ?? (fromResponse ? "Request failed." : body || "(empty body)");
16
+ const displayMessage = options.displayMessage ?? (gatewayFailure && fromResponse ? "OpenGeni is temporarily unavailable \u2014 retry." : `OpenGeni API ${status}: ${message}`);
17
+ super(correlationId ? `${displayMessage} Reference: ${correlationId}.` : displayMessage);
7
18
  this.name = "OpenGeniApiError";
8
19
  this.status = status;
9
- this.body = body;
20
+ this.code = options.code ?? decoded?.code ?? (gatewayFailure && fromResponse ? "upstream_unavailable" : void 0);
21
+ this.retryable = options.retryable ?? decoded?.retryable ?? retryableApiStatus(status);
22
+ this.correlationId = correlationId;
23
+ this.outcomeUnknown = options.outcomeUnknown ?? (gatewayFailure && !!options.mutation && !decoded);
24
+ this.body = !fromResponse || decoded ? body : "";
10
25
  }
11
26
  };
27
+ function decodeApiErrorBody(body) {
28
+ if (!body) return null;
29
+ try {
30
+ const decoded = JSON.parse(body);
31
+ if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return null;
32
+ const record = decoded;
33
+ const nested = record.error && typeof record.error === "object" && !Array.isArray(record.error) ? record.error : record;
34
+ const code = boundedApiField(nested.code);
35
+ const message = boundedApiField(nested.message);
36
+ const requestId = boundedCorrelationId(nested.requestId);
37
+ const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
38
+ if (!code && !message && !requestId && retryable === void 0) return null;
39
+ return {
40
+ code,
41
+ message,
42
+ requestId,
43
+ retryable
44
+ };
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+ function boundedApiField(value) {
50
+ if (typeof value !== "string") return;
51
+ const bytes = new TextEncoder().encode(value);
52
+ return bytes.byteLength <= 512 ? value : new TextDecoder().decode(bytes.slice(0, 512));
53
+ }
54
+ function retryableApiStatus(status) {
55
+ return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
56
+ }
57
+ function boundedCorrelationId(value) {
58
+ if (typeof value !== "string" || value.length > 128 || !/^[\w.:-]+$/.test(value)) {
59
+ return;
60
+ }
61
+ return value;
62
+ }
63
+ var OpenGeniSessionListCursorError = class extends OpenGeniApiError {
64
+ };
12
65
  var OpenGeniApiContractMismatchError = class extends Error {
13
66
  expected;
14
67
  actual;
@@ -29,9 +82,7 @@ function isAbortError(error) {
29
82
  return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
30
83
  }
31
84
  function isRetryableStreamError(error) {
32
- if (error instanceof OpenGeniApiError) {
33
- return error.status === 408 || error.status === 409 || error.status === 425 || error.status === 429 || error.status >= 500;
34
- }
85
+ if (error instanceof OpenGeniApiError) return error.retryable;
35
86
  return error instanceof TypeError;
36
87
  }
37
88
 
@@ -372,10 +423,13 @@ var SESSION_EVENT_TYPES = [
372
423
  "terminal.pty.exited",
373
424
  "session.title_set",
374
425
  "session.mcp.approval_policy.updated",
426
+ "session.tool_policy.updated",
375
427
  // Multi-account Codex (P1): the session's inference account changed.
376
428
  "codex.account.switched",
377
429
  // credential allocator metadata-only per-turn credential selection audit.
378
430
  "codex.credential.selected",
431
+ // Bounded, identity-free deterministic shadow/replay decision.
432
+ "codex.fleet.decision",
379
433
  // credential allocator durable zero-capacity wait lifecycle. These are system/runtime
380
434
  // events, never synthetic user messages.
381
435
  "codex.capacity.waiting",
@@ -451,6 +505,7 @@ var KNOWN_PERMISSIONS = [
451
505
  ];
452
506
  var OPENGENI_API_CONTRACT_REVISION = "2026-07-turn-instructions-v1";
453
507
  var OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract";
508
+ var OPENGENI_CORRELATION_HEADER = "x-opengeni-correlation-id";
454
509
  var RETAINED_OUTPUT_DEFAULT_PAGE_BYTES = 256 * 1024;
455
510
  var RETAINED_OUTPUT_MAX_PAGE_BYTES = 1024 * 1024;
456
511
  var KNOWN_USAGE_EVENT_TYPES = [
@@ -469,6 +524,13 @@ var KNOWN_USAGE_EVENT_TYPES = [
469
524
  ];
470
525
 
471
526
  // src/client.ts
527
+ function sessionListQuery(options) {
528
+ const { limit, parentSessionId } = options;
529
+ return {
530
+ ...limit === void 0 ? {} : { limit: String(limit) },
531
+ ...parentSessionId === void 0 ? {} : { parentSessionId: parentSessionId ?? "null" }
532
+ };
533
+ }
472
534
  var OpenGeniClient = class {
473
535
  baseUrl;
474
536
  options;
@@ -486,6 +548,19 @@ var OpenGeniClient = class {
486
548
  request
487
549
  );
488
550
  }
551
+ async getNewSessionDraft(workspaceId) {
552
+ return await this.requestJson(
553
+ "GET",
554
+ `/v1/workspaces/${workspaceId}/new-session-draft`
555
+ );
556
+ }
557
+ async saveNewSessionDraft(workspaceId, request) {
558
+ return await this.requestJson(
559
+ "PUT",
560
+ `/v1/workspaces/${workspaceId}/new-session-draft`,
561
+ request
562
+ );
563
+ }
489
564
  async getSession(workspaceId, sessionId) {
490
565
  return await this.requestJson(
491
566
  "GET",
@@ -499,6 +574,14 @@ var OpenGeniClient = class {
499
574
  request
500
575
  );
501
576
  }
577
+ /** Replace the durable tool policy or explicitly adopt workspace defaults. */
578
+ async updateSessionToolPolicy(workspaceId, sessionId, request) {
579
+ return await this.requestJson(
580
+ "PUT",
581
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/tool-policy`,
582
+ request
583
+ );
584
+ }
502
585
  /**
503
586
  * Replace one attached MCP server's approval policy. The change is captured
504
587
  * by the next claimed attempt; already-claimed work keeps its immutable
@@ -512,35 +595,59 @@ var OpenGeniClient = class {
512
595
  );
513
596
  }
514
597
  async listSessions(workspaceId, options = {}) {
598
+ if (options.search?.trim()) {
599
+ const page = await this.listSessionPage(workspaceId, options);
600
+ return [...page.pinned, ...page.sessions];
601
+ }
515
602
  return await this.requestJson(
516
603
  "GET",
517
604
  `/v1/workspaces/${workspaceId}/sessions`,
518
605
  void 0,
519
- {
520
- ...options.limit !== void 0 ? { limit: String(options.limit) } : {},
521
- ...options.search?.trim() ? { search: options.search.trim() } : {},
522
- ...Object.prototype.hasOwnProperty.call(options, "parentSessionId") && options.parentSessionId !== void 0 ? {
523
- parentSessionId: options.parentSessionId === null ? "null" : String(options.parentSessionId)
524
- } : {}
525
- }
606
+ sessionListQuery(options)
526
607
  );
527
608
  }
528
609
  /** Pin-aware ordinary-session page with a stable keyset cursor. */
529
610
  async listSessionPage(workspaceId, options = {}) {
530
- return await this.requestJson(
531
- "GET",
532
- `/v1/workspaces/${workspaceId}/sessions`,
533
- void 0,
534
- {
535
- view: "page",
536
- ...options.limit !== void 0 ? { limit: String(options.limit) } : {},
537
- ...options.cursor !== void 0 ? { cursor: options.cursor } : {},
538
- ...options.search?.trim() ? { search: options.search.trim() } : {},
539
- ...Object.prototype.hasOwnProperty.call(options, "parentSessionId") && options.parentSessionId !== void 0 ? {
540
- parentSessionId: options.parentSessionId === null ? "null" : String(options.parentSessionId)
541
- } : {}
611
+ const search = options.search?.trim();
612
+ let response;
613
+ try {
614
+ response = await this.requestJson(
615
+ "GET",
616
+ `/v1/workspaces/${workspaceId}/sessions`,
617
+ void 0,
618
+ {
619
+ view: "page",
620
+ ...sessionListQuery(options),
621
+ ...options.cursor !== void 0 ? { cursor: options.cursor } : {},
622
+ ...search ? { search } : {},
623
+ ...options.pinsOnly ? { pinsOnly: "true" } : {}
624
+ }
625
+ );
626
+ } catch (error) {
627
+ if (error instanceof OpenGeniApiError && error.status === 410) {
628
+ throw new OpenGeniSessionListCursorError(error.status, error.body, {
629
+ ...error.code ? { code: error.code } : {},
630
+ retryable: error.retryable,
631
+ ...error.correlationId ? { correlationId: error.correlationId } : {},
632
+ outcomeUnknown: error.outcomeUnknown,
633
+ displayMessage: "The session list changed \u2014 refresh and try again."
634
+ });
542
635
  }
543
- );
636
+ throw error;
637
+ }
638
+ if (Array.isArray(response)) {
639
+ if (options.cursor) {
640
+ throw new Error("The connected OpenGeni API does not support stable session-page cursors");
641
+ }
642
+ if (search) {
643
+ throw new Error("The connected OpenGeni API does not support session search");
644
+ }
645
+ if (options.pinsOnly) {
646
+ throw new Error("The connected OpenGeni API does not support pins-only session lists");
647
+ }
648
+ return { pinned: [], sessions: response, nextCursor: null };
649
+ }
650
+ return response;
544
651
  }
545
652
  /** Set this authenticated member's personal workspace pin for a session. */
546
653
  async updateSessionPin(workspaceId, sessionId, request) {
@@ -699,6 +806,7 @@ var OpenGeniClient = class {
699
806
  throw new TypeError("resultMode=compact requires latest");
700
807
  }
701
808
  const listOptions = options.resultMode === "compact" ? null : options;
809
+ const correlationId = crypto.randomUUID();
702
810
  const response = await this.fetchImpl(
703
811
  this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, {
704
812
  ...listOptions?.after !== void 0 ? { after: String(listOptions.after) } : {},
@@ -717,11 +825,14 @@ var OpenGeniClient = class {
717
825
  }),
718
826
  {
719
827
  method: "GET",
720
- headers: { ...this.headers(), Accept: "application/json" }
828
+ headers: { ...this.headers(correlationId), Accept: "application/json" }
721
829
  }
722
830
  );
723
831
  assertApiContractResponse(response);
724
- if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
832
+ if (!response.ok) {
833
+ throw await apiErrorFromResponse(response, { method: "GET", correlationId });
834
+ }
835
+ await assertJsonResponse(response, { method: "GET", correlationId });
725
836
  const body = await response.json();
726
837
  if (options.resultMode === "compact") {
727
838
  return body;
@@ -849,14 +960,15 @@ var OpenGeniClient = class {
849
960
  const url = this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events/stream`, {
850
961
  after: String(options.after ?? 0)
851
962
  });
963
+ const correlationId = crypto.randomUUID();
852
964
  const response = await this.fetchImpl(url, {
853
965
  method: "GET",
854
- headers: { ...this.headers(), Accept: "text/event-stream" },
966
+ headers: { ...this.headers(correlationId), Accept: "text/event-stream" },
855
967
  ...options.signal ? { signal: options.signal } : {}
856
968
  });
857
969
  assertApiContractResponse(response);
858
970
  if (!response.ok) {
859
- throw new OpenGeniApiError(response.status, await safeText(response));
971
+ throw await apiErrorFromResponse(response, { method: "GET", correlationId });
860
972
  }
861
973
  if (!response.body) {
862
974
  throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
@@ -938,6 +1050,7 @@ var OpenGeniClient = class {
938
1050
  }
939
1051
  /** Count/byte-bounded page plus an explicit continuation cursor. */
940
1052
  async listWorkspaceControlEventPage(workspaceId, options = {}) {
1053
+ const correlationId = crypto.randomUUID();
941
1054
  const response = await this.fetchImpl(
942
1055
  this.url(`/v1/workspaces/${workspaceId}/control-events`, {
943
1056
  ...options.after !== void 0 ? { after: String(options.after) } : {},
@@ -945,13 +1058,14 @@ var OpenGeniClient = class {
945
1058
  }),
946
1059
  {
947
1060
  method: "GET",
948
- headers: { ...this.headers(), Accept: "application/json" }
1061
+ headers: { ...this.headers(correlationId), Accept: "application/json" }
949
1062
  }
950
1063
  );
951
1064
  assertApiContractResponse(response);
952
1065
  if (!response.ok) {
953
- throw new OpenGeniApiError(response.status, await safeText(response));
1066
+ throw await apiErrorFromResponse(response, { method: "GET", correlationId });
954
1067
  }
1068
+ await assertJsonResponse(response, { method: "GET", correlationId });
955
1069
  const events = await response.json();
956
1070
  const bytesHeader = response.headers.get("X-OpenGeni-Page-Bytes");
957
1071
  const nextHeader = response.headers.get("X-OpenGeni-Next-After");
@@ -976,18 +1090,21 @@ var OpenGeniClient = class {
976
1090
  };
977
1091
  }
978
1092
  async openWorkspaceControlEventStream(workspaceId, options = {}) {
1093
+ const correlationId = crypto.randomUUID();
979
1094
  const response = await this.fetchImpl(
980
1095
  this.url(`/v1/workspaces/${workspaceId}/control-events/stream`, {
981
1096
  after: String(options.after ?? 0)
982
1097
  }),
983
1098
  {
984
1099
  method: "GET",
985
- headers: { ...this.headers(), Accept: "text/event-stream" },
1100
+ headers: { ...this.headers(correlationId), Accept: "text/event-stream" },
986
1101
  ...options.signal ? { signal: options.signal } : {}
987
1102
  }
988
1103
  );
989
1104
  assertApiContractResponse(response);
990
- if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
1105
+ if (!response.ok) {
1106
+ throw await apiErrorFromResponse(response, { method: "GET", correlationId });
1107
+ }
991
1108
  if (!response.body) {
992
1109
  throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
993
1110
  }
@@ -1604,13 +1721,14 @@ var OpenGeniClient = class {
1604
1721
  * -> complete. Returns the ready `FileAsset`.
1605
1722
  */
1606
1723
  async uploadFile(workspaceId, input) {
1607
- const body = input.data instanceof Uint8Array ? new Blob([input.data.slice()]) : input.data;
1724
+ const body = input.data instanceof Uint8Array ? new Blob([input.data.slice()]) : input.data instanceof ArrayBuffer ? input.data.slice(0) : input.data;
1608
1725
  const sizeBytes = typeof body === "string" ? new TextEncoder().encode(body).byteLength : body instanceof Blob ? body.size : body.byteLength;
1726
+ const sha256 = input.sha256 ?? await sha256ForUpload(body);
1609
1727
  const upload = await this.beginFileUpload(workspaceId, {
1610
1728
  filename: input.filename,
1611
1729
  contentType: input.contentType,
1612
1730
  sizeBytes,
1613
- ...input.sha256 !== void 0 ? { sha256: input.sha256 } : {}
1731
+ sha256
1614
1732
  });
1615
1733
  const putResponse = await this.fetchImpl(upload.putUrl, {
1616
1734
  method: "PUT",
@@ -1624,7 +1742,7 @@ var OpenGeniClient = class {
1624
1742
  body
1625
1743
  });
1626
1744
  if (!putResponse.ok) {
1627
- throw new OpenGeniApiError(putResponse.status, await safeText(putResponse));
1745
+ throw await apiErrorFromResponse(putResponse, { method: "PUT" });
1628
1746
  }
1629
1747
  return await this.completeFileUpload(workspaceId, upload.uploadId);
1630
1748
  }
@@ -1649,12 +1767,13 @@ var OpenGeniClient = class {
1649
1767
  if (options.range && (options.range.length > 128 || /[^\x20-\x7e]/.test(options.range))) {
1650
1768
  throw new RangeError("retained artifact range must be at most 128 printable ASCII bytes");
1651
1769
  }
1770
+ const correlationId = crypto.randomUUID();
1652
1771
  const response = await this.fetchImpl(
1653
1772
  this.url(`/v1/workspaces/${workspaceId}/artifacts/${artifactId}/content`),
1654
1773
  {
1655
1774
  method: "GET",
1656
1775
  headers: {
1657
- ...this.headers(),
1776
+ ...this.headers(correlationId),
1658
1777
  Accept: "application/octet-stream",
1659
1778
  ...options.range ? { Range: options.range } : {}
1660
1779
  },
@@ -1668,7 +1787,7 @@ var OpenGeniClient = class {
1668
1787
  throw error;
1669
1788
  }
1670
1789
  if (!response.ok) {
1671
- throw new OpenGeniApiError(response.status, await safeBoundedText(response));
1790
+ throw await apiErrorFromResponse(response, { method: "GET", correlationId });
1672
1791
  }
1673
1792
  if (response.status !== 200 && response.status !== 206) {
1674
1793
  await cancelResponseBody(response, "unexpected retained artifact response status");
@@ -1740,6 +1859,30 @@ var OpenGeniClient = class {
1740
1859
  `/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`
1741
1860
  );
1742
1861
  }
1862
+ /**
1863
+ * Drop raw text or an already-uploaded file into the workspace's Default
1864
+ * base. When curation is enabled, it may name, summarize, categorize, and
1865
+ * (confidence permitting) file the document into the best-matching base;
1866
+ * provider=none leaves caller metadata and Default placement unchanged.
1867
+ */
1868
+ async createKnowledgeDrop(workspaceId, request) {
1869
+ return await this.requestJson(
1870
+ "POST",
1871
+ `/v1/workspaces/${workspaceId}/knowledge/drops`,
1872
+ request
1873
+ );
1874
+ }
1875
+ /**
1876
+ * Move a document (and its indexed chunks) to another base. With no
1877
+ * targetBaseId, applies the document's stored curation suggestion.
1878
+ */
1879
+ async moveDocument(workspaceId, documentId, request = {}) {
1880
+ return await this.requestJson(
1881
+ "POST",
1882
+ `/v1/workspaces/${workspaceId}/documents/${documentId}/move`,
1883
+ request
1884
+ );
1885
+ }
1743
1886
  /** Retry indexing for a failed document. */
1744
1887
  async reindexDocument(workspaceId, baseId, documentId) {
1745
1888
  return await this.requestJson(
@@ -1950,14 +2093,11 @@ var OpenGeniClient = class {
1950
2093
  return logoAssetPath ? `${this.baseUrl}/v1/${logoAssetPath}` : null;
1951
2094
  }
1952
2095
  // --- GitHub ----------------------------------------------------------------------------------
1953
- /** GitHub App configuration status; install/link URLs are null while new binding is disabled. */
2096
+ /** GitHub App server configuration plus truthful workspace binding status. */
1954
2097
  async getGitHubApp(workspaceId) {
1955
2098
  return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/github/app`);
1956
2099
  }
1957
- /**
1958
- * Compatibility URL for previously issued state. New installation binding is
1959
- * disabled, so the endpoint validates state and terminates with HTTP 410.
1960
- */
2100
+ /** Build the GitHub owner-consent entry URL for fresh workspace-bound state. */
1961
2101
  githubConnectUrl(workspaceId, state) {
1962
2102
  return this.url(`/v1/workspaces/${workspaceId}/github/connect`, { state });
1963
2103
  }
@@ -2039,12 +2179,13 @@ var OpenGeniClient = class {
2039
2179
  return await this.requestJson("POST", "/v1/billing/checkout", request);
2040
2180
  }
2041
2181
  // --- Internals -------------------------------------------------------------
2042
- headers() {
2182
+ headers(correlationId) {
2043
2183
  const extra = typeof this.options.headers === "function" ? this.options.headers() : this.options.headers;
2044
2184
  return {
2045
2185
  ...this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {},
2046
2186
  ...extra,
2047
- [OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION
2187
+ [OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION,
2188
+ ...correlationId ? { [OPENGENI_CORRELATION_HEADER]: correlationId } : {}
2048
2189
  };
2049
2190
  }
2050
2191
  url(path, query = {}) {
@@ -2160,36 +2301,62 @@ var OpenGeniClient = class {
2160
2301
  );
2161
2302
  }
2162
2303
  async requestJson(method, path, body, query = {}, options = {}) {
2163
- const response = await this.fetchImpl(this.url(path, query), {
2164
- method,
2165
- headers: {
2166
- ...this.headers(),
2167
- Accept: "application/json",
2168
- ...body !== void 0 ? { "Content-Type": "application/json" } : {}
2169
- },
2170
- ...body !== void 0 ? { body: JSON.stringify(body) } : {},
2171
- ...options.signal ? { signal: options.signal } : {}
2172
- });
2304
+ const correlationId = crypto.randomUUID();
2305
+ let response;
2306
+ try {
2307
+ response = await this.fetchImpl(this.url(path, query), {
2308
+ method,
2309
+ headers: {
2310
+ ...this.headers(correlationId),
2311
+ Accept: "application/json",
2312
+ ...body !== void 0 ? { "Content-Type": "application/json" } : {}
2313
+ },
2314
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {},
2315
+ ...options.signal ? { signal: options.signal } : {}
2316
+ });
2317
+ } catch (error) {
2318
+ if (isMutationMethod(method)) {
2319
+ throw mutationTransportError(correlationId);
2320
+ }
2321
+ throw error;
2322
+ }
2173
2323
  assertApiContractResponse(response);
2174
2324
  if (!response.ok) {
2175
- throw new OpenGeniApiError(response.status, await safeText(response));
2325
+ throw await apiErrorFromResponse(response, { method, correlationId });
2326
+ }
2327
+ await assertJsonResponse(response, { method, correlationId });
2328
+ try {
2329
+ return await response.json();
2330
+ } catch (error) {
2331
+ if (isMutationMethod(method)) {
2332
+ throw mutationTransportError(correlationId);
2333
+ }
2334
+ throw error;
2176
2335
  }
2177
- return await response.json();
2178
2336
  }
2179
2337
  /** Like `requestJson` for endpoints that respond with no body (204). */
2180
2338
  async requestVoid(method, path, body) {
2181
- const response = await this.fetchImpl(this.url(path), {
2182
- method,
2183
- headers: {
2184
- ...this.headers(),
2185
- Accept: "application/json",
2186
- ...body !== void 0 ? { "Content-Type": "application/json" } : {}
2187
- },
2188
- ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2189
- });
2339
+ const correlationId = crypto.randomUUID();
2340
+ let response;
2341
+ try {
2342
+ response = await this.fetchImpl(this.url(path), {
2343
+ method,
2344
+ headers: {
2345
+ ...this.headers(correlationId),
2346
+ Accept: "application/json",
2347
+ ...body !== void 0 ? { "Content-Type": "application/json" } : {}
2348
+ },
2349
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2350
+ });
2351
+ } catch (error) {
2352
+ if (isMutationMethod(method)) {
2353
+ throw mutationTransportError(correlationId);
2354
+ }
2355
+ throw error;
2356
+ }
2190
2357
  assertApiContractResponse(response);
2191
2358
  if (!response.ok) {
2192
- throw new OpenGeniApiError(response.status, await safeText(response));
2359
+ throw await apiErrorFromResponse(response, { method, correlationId });
2193
2360
  }
2194
2361
  }
2195
2362
  };
@@ -2199,20 +2366,62 @@ function assertApiContractResponse(response) {
2199
2366
  throw new OpenGeniApiContractMismatchError(OPENGENI_API_CONTRACT_REVISION, actual);
2200
2367
  }
2201
2368
  }
2202
- async function safeText(response) {
2203
- try {
2204
- return await response.text();
2205
- } catch {
2369
+ var API_ERROR_MAX_BYTES = 16 * 1024;
2370
+ async function apiErrorFromResponse(response, context) {
2371
+ return new OpenGeniApiError(response.status, await readBoundedJsonErrorBody(response), {
2372
+ correlationId: response.headers.get(OPENGENI_CORRELATION_HEADER) ?? context.correlationId,
2373
+ mutation: isMutationMethod(context.method)
2374
+ });
2375
+ }
2376
+ async function assertJsonResponse(response, context) {
2377
+ if (isJsonContentType(response.headers.get("content-type"))) return;
2378
+ await cancelResponseBody(response, "unexpected non-JSON API response");
2379
+ throw new OpenGeniApiError(502, "", {
2380
+ code: "upstream_unavailable",
2381
+ retryable: true,
2382
+ correlationId: response.headers.get(OPENGENI_CORRELATION_HEADER) ?? context.correlationId,
2383
+ outcomeUnknown: isMutationMethod(context.method),
2384
+ displayMessage: "OpenGeni is temporarily unavailable \u2014 retry."
2385
+ });
2386
+ }
2387
+ async function readBoundedJsonErrorBody(response) {
2388
+ if (!isJsonContentType(response.headers.get("content-type"))) {
2389
+ await cancelResponseBody(response, "discarding API error body");
2390
+ return "";
2391
+ }
2392
+ if (Number(response.headers.get("content-length")) > API_ERROR_MAX_BYTES) {
2393
+ await cancelResponseBody(response, "discarding API error body");
2206
2394
  return "";
2207
2395
  }
2208
- }
2209
- async function safeBoundedText(response) {
2210
2396
  try {
2211
- return new TextDecoder().decode(await readBoundedResponseBytes(response, 64 * 1024, null));
2397
+ return new TextDecoder().decode(
2398
+ await readBoundedResponseBytes(response, API_ERROR_MAX_BYTES, null)
2399
+ );
2212
2400
  } catch {
2213
2401
  return "";
2214
2402
  }
2215
2403
  }
2404
+ function isJsonContentType(value) {
2405
+ return /^(application\/json|[^;]+\+json)\s*(;|$)/i.test(value ?? "");
2406
+ }
2407
+ function isMutationMethod(method) {
2408
+ return method !== "GET" && method !== "HEAD" && method !== "OPTIONS";
2409
+ }
2410
+ function mutationTransportError(correlationId) {
2411
+ return new OpenGeniApiError(0, "", {
2412
+ code: "network_error",
2413
+ retryable: true,
2414
+ correlationId,
2415
+ outcomeUnknown: true,
2416
+ mutation: true,
2417
+ displayMessage: "OpenGeni could not confirm the request \u2014 reconcile before retrying."
2418
+ });
2419
+ }
2420
+ async function sha256ForUpload(body) {
2421
+ const bytes = typeof body === "string" ? new TextEncoder().encode(body) : body instanceof Blob ? new Uint8Array(await body.arrayBuffer()) : new Uint8Array(body);
2422
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
2423
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2424
+ }
2216
2425
  async function cancelResponseBody(response, reason) {
2217
2426
  await response.body?.cancel(reason).catch(() => void 0);
2218
2427
  }
@@ -2664,9 +2873,11 @@ export {
2664
2873
  KNOWN_USAGE_EVENT_TYPES,
2665
2874
  OPENGENI_API_CONTRACT_HEADER,
2666
2875
  OPENGENI_API_CONTRACT_REVISION,
2876
+ OPENGENI_CORRELATION_HEADER,
2667
2877
  OpenGeniApiContractMismatchError,
2668
2878
  OpenGeniApiError,
2669
2879
  OpenGeniClient,
2880
+ OpenGeniSessionListCursorError,
2670
2881
  OpenGeniStreamError,
2671
2882
  RETAINED_OUTPUT_DEFAULT_PAGE_BYTES,
2672
2883
  RETAINED_OUTPUT_MAX_PAGE_BYTES,