@opengeni/sdk 0.25.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/README.md +52 -0
- package/dist/index.d.ts +82 -10
- package/dist/index.js +209 -64
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +200 -56
- package/src/errors.ts +75 -19
- package/src/index.ts +9 -0
- package/src/types.ts +63 -2
package/dist/index.js
CHANGED
|
@@ -2,30 +2,64 @@
|
|
|
2
2
|
var OpenGeniApiError = class extends Error {
|
|
3
3
|
status;
|
|
4
4
|
code;
|
|
5
|
+
retryable;
|
|
6
|
+
correlationId;
|
|
7
|
+
/** True only when an uncontrolled transport failed after a mutation may have been accepted. */
|
|
8
|
+
outcomeUnknown;
|
|
5
9
|
body;
|
|
6
|
-
constructor(status, body) {
|
|
10
|
+
constructor(status, body, options = {}) {
|
|
7
11
|
const decoded = decodeApiErrorBody(body);
|
|
8
|
-
|
|
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);
|
|
9
18
|
this.name = "OpenGeniApiError";
|
|
10
19
|
this.status = status;
|
|
11
|
-
this.code =
|
|
12
|
-
this.
|
|
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 : "";
|
|
13
25
|
}
|
|
14
26
|
};
|
|
15
27
|
function decodeApiErrorBody(body) {
|
|
16
|
-
if (!body) return
|
|
28
|
+
if (!body) return null;
|
|
17
29
|
try {
|
|
18
30
|
const decoded = JSON.parse(body);
|
|
19
|
-
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return
|
|
31
|
+
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return null;
|
|
20
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;
|
|
21
39
|
return {
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
code,
|
|
41
|
+
message,
|
|
42
|
+
requestId,
|
|
43
|
+
retryable
|
|
24
44
|
};
|
|
25
45
|
} catch {
|
|
26
|
-
return
|
|
46
|
+
return null;
|
|
27
47
|
}
|
|
28
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
|
+
}
|
|
29
63
|
var OpenGeniSessionListCursorError = class extends OpenGeniApiError {
|
|
30
64
|
};
|
|
31
65
|
var OpenGeniApiContractMismatchError = class extends Error {
|
|
@@ -48,9 +82,7 @@ function isAbortError(error) {
|
|
|
48
82
|
return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
|
|
49
83
|
}
|
|
50
84
|
function isRetryableStreamError(error) {
|
|
51
|
-
if (error instanceof OpenGeniApiError)
|
|
52
|
-
return error.status === 408 || error.status === 409 || error.status === 425 || error.status === 429 || error.status >= 500;
|
|
53
|
-
}
|
|
85
|
+
if (error instanceof OpenGeniApiError) return error.retryable;
|
|
54
86
|
return error instanceof TypeError;
|
|
55
87
|
}
|
|
56
88
|
|
|
@@ -391,6 +423,7 @@ var SESSION_EVENT_TYPES = [
|
|
|
391
423
|
"terminal.pty.exited",
|
|
392
424
|
"session.title_set",
|
|
393
425
|
"session.mcp.approval_policy.updated",
|
|
426
|
+
"session.tool_policy.updated",
|
|
394
427
|
// Multi-account Codex (P1): the session's inference account changed.
|
|
395
428
|
"codex.account.switched",
|
|
396
429
|
// credential allocator metadata-only per-turn credential selection audit.
|
|
@@ -472,6 +505,7 @@ var KNOWN_PERMISSIONS = [
|
|
|
472
505
|
];
|
|
473
506
|
var OPENGENI_API_CONTRACT_REVISION = "2026-07-turn-instructions-v1";
|
|
474
507
|
var OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract";
|
|
508
|
+
var OPENGENI_CORRELATION_HEADER = "x-opengeni-correlation-id";
|
|
475
509
|
var RETAINED_OUTPUT_DEFAULT_PAGE_BYTES = 256 * 1024;
|
|
476
510
|
var RETAINED_OUTPUT_MAX_PAGE_BYTES = 1024 * 1024;
|
|
477
511
|
var KNOWN_USAGE_EVENT_TYPES = [
|
|
@@ -540,6 +574,14 @@ var OpenGeniClient = class {
|
|
|
540
574
|
request
|
|
541
575
|
);
|
|
542
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
|
+
}
|
|
543
585
|
/**
|
|
544
586
|
* Replace one attached MCP server's approval policy. The change is captured
|
|
545
587
|
* by the next claimed attempt; already-claimed work keeps its immutable
|
|
@@ -583,7 +625,13 @@ var OpenGeniClient = class {
|
|
|
583
625
|
);
|
|
584
626
|
} catch (error) {
|
|
585
627
|
if (error instanceof OpenGeniApiError && error.status === 410) {
|
|
586
|
-
throw new OpenGeniSessionListCursorError(error.status, error.body
|
|
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
|
+
});
|
|
587
635
|
}
|
|
588
636
|
throw error;
|
|
589
637
|
}
|
|
@@ -758,6 +806,7 @@ var OpenGeniClient = class {
|
|
|
758
806
|
throw new TypeError("resultMode=compact requires latest");
|
|
759
807
|
}
|
|
760
808
|
const listOptions = options.resultMode === "compact" ? null : options;
|
|
809
|
+
const correlationId = crypto.randomUUID();
|
|
761
810
|
const response = await this.fetchImpl(
|
|
762
811
|
this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, {
|
|
763
812
|
...listOptions?.after !== void 0 ? { after: String(listOptions.after) } : {},
|
|
@@ -776,11 +825,14 @@ var OpenGeniClient = class {
|
|
|
776
825
|
}),
|
|
777
826
|
{
|
|
778
827
|
method: "GET",
|
|
779
|
-
headers: { ...this.headers(), Accept: "application/json" }
|
|
828
|
+
headers: { ...this.headers(correlationId), Accept: "application/json" }
|
|
780
829
|
}
|
|
781
830
|
);
|
|
782
831
|
assertApiContractResponse(response);
|
|
783
|
-
if (!response.ok)
|
|
832
|
+
if (!response.ok) {
|
|
833
|
+
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
834
|
+
}
|
|
835
|
+
await assertJsonResponse(response, { method: "GET", correlationId });
|
|
784
836
|
const body = await response.json();
|
|
785
837
|
if (options.resultMode === "compact") {
|
|
786
838
|
return body;
|
|
@@ -908,14 +960,15 @@ var OpenGeniClient = class {
|
|
|
908
960
|
const url = this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events/stream`, {
|
|
909
961
|
after: String(options.after ?? 0)
|
|
910
962
|
});
|
|
963
|
+
const correlationId = crypto.randomUUID();
|
|
911
964
|
const response = await this.fetchImpl(url, {
|
|
912
965
|
method: "GET",
|
|
913
|
-
headers: { ...this.headers(), Accept: "text/event-stream" },
|
|
966
|
+
headers: { ...this.headers(correlationId), Accept: "text/event-stream" },
|
|
914
967
|
...options.signal ? { signal: options.signal } : {}
|
|
915
968
|
});
|
|
916
969
|
assertApiContractResponse(response);
|
|
917
970
|
if (!response.ok) {
|
|
918
|
-
throw
|
|
971
|
+
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
919
972
|
}
|
|
920
973
|
if (!response.body) {
|
|
921
974
|
throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
|
|
@@ -997,6 +1050,7 @@ var OpenGeniClient = class {
|
|
|
997
1050
|
}
|
|
998
1051
|
/** Count/byte-bounded page plus an explicit continuation cursor. */
|
|
999
1052
|
async listWorkspaceControlEventPage(workspaceId, options = {}) {
|
|
1053
|
+
const correlationId = crypto.randomUUID();
|
|
1000
1054
|
const response = await this.fetchImpl(
|
|
1001
1055
|
this.url(`/v1/workspaces/${workspaceId}/control-events`, {
|
|
1002
1056
|
...options.after !== void 0 ? { after: String(options.after) } : {},
|
|
@@ -1004,13 +1058,14 @@ var OpenGeniClient = class {
|
|
|
1004
1058
|
}),
|
|
1005
1059
|
{
|
|
1006
1060
|
method: "GET",
|
|
1007
|
-
headers: { ...this.headers(), Accept: "application/json" }
|
|
1061
|
+
headers: { ...this.headers(correlationId), Accept: "application/json" }
|
|
1008
1062
|
}
|
|
1009
1063
|
);
|
|
1010
1064
|
assertApiContractResponse(response);
|
|
1011
1065
|
if (!response.ok) {
|
|
1012
|
-
throw
|
|
1066
|
+
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
1013
1067
|
}
|
|
1068
|
+
await assertJsonResponse(response, { method: "GET", correlationId });
|
|
1014
1069
|
const events = await response.json();
|
|
1015
1070
|
const bytesHeader = response.headers.get("X-OpenGeni-Page-Bytes");
|
|
1016
1071
|
const nextHeader = response.headers.get("X-OpenGeni-Next-After");
|
|
@@ -1035,18 +1090,21 @@ var OpenGeniClient = class {
|
|
|
1035
1090
|
};
|
|
1036
1091
|
}
|
|
1037
1092
|
async openWorkspaceControlEventStream(workspaceId, options = {}) {
|
|
1093
|
+
const correlationId = crypto.randomUUID();
|
|
1038
1094
|
const response = await this.fetchImpl(
|
|
1039
1095
|
this.url(`/v1/workspaces/${workspaceId}/control-events/stream`, {
|
|
1040
1096
|
after: String(options.after ?? 0)
|
|
1041
1097
|
}),
|
|
1042
1098
|
{
|
|
1043
1099
|
method: "GET",
|
|
1044
|
-
headers: { ...this.headers(), Accept: "text/event-stream" },
|
|
1100
|
+
headers: { ...this.headers(correlationId), Accept: "text/event-stream" },
|
|
1045
1101
|
...options.signal ? { signal: options.signal } : {}
|
|
1046
1102
|
}
|
|
1047
1103
|
);
|
|
1048
1104
|
assertApiContractResponse(response);
|
|
1049
|
-
if (!response.ok)
|
|
1105
|
+
if (!response.ok) {
|
|
1106
|
+
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
1107
|
+
}
|
|
1050
1108
|
if (!response.body) {
|
|
1051
1109
|
throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
|
|
1052
1110
|
}
|
|
@@ -1684,7 +1742,7 @@ var OpenGeniClient = class {
|
|
|
1684
1742
|
body
|
|
1685
1743
|
});
|
|
1686
1744
|
if (!putResponse.ok) {
|
|
1687
|
-
throw
|
|
1745
|
+
throw await apiErrorFromResponse(putResponse, { method: "PUT" });
|
|
1688
1746
|
}
|
|
1689
1747
|
return await this.completeFileUpload(workspaceId, upload.uploadId);
|
|
1690
1748
|
}
|
|
@@ -1709,12 +1767,13 @@ var OpenGeniClient = class {
|
|
|
1709
1767
|
if (options.range && (options.range.length > 128 || /[^\x20-\x7e]/.test(options.range))) {
|
|
1710
1768
|
throw new RangeError("retained artifact range must be at most 128 printable ASCII bytes");
|
|
1711
1769
|
}
|
|
1770
|
+
const correlationId = crypto.randomUUID();
|
|
1712
1771
|
const response = await this.fetchImpl(
|
|
1713
1772
|
this.url(`/v1/workspaces/${workspaceId}/artifacts/${artifactId}/content`),
|
|
1714
1773
|
{
|
|
1715
1774
|
method: "GET",
|
|
1716
1775
|
headers: {
|
|
1717
|
-
...this.headers(),
|
|
1776
|
+
...this.headers(correlationId),
|
|
1718
1777
|
Accept: "application/octet-stream",
|
|
1719
1778
|
...options.range ? { Range: options.range } : {}
|
|
1720
1779
|
},
|
|
@@ -1728,7 +1787,7 @@ var OpenGeniClient = class {
|
|
|
1728
1787
|
throw error;
|
|
1729
1788
|
}
|
|
1730
1789
|
if (!response.ok) {
|
|
1731
|
-
throw
|
|
1790
|
+
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
1732
1791
|
}
|
|
1733
1792
|
if (response.status !== 200 && response.status !== 206) {
|
|
1734
1793
|
await cancelResponseBody(response, "unexpected retained artifact response status");
|
|
@@ -1800,6 +1859,30 @@ var OpenGeniClient = class {
|
|
|
1800
1859
|
`/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`
|
|
1801
1860
|
);
|
|
1802
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
|
+
}
|
|
1803
1886
|
/** Retry indexing for a failed document. */
|
|
1804
1887
|
async reindexDocument(workspaceId, baseId, documentId) {
|
|
1805
1888
|
return await this.requestJson(
|
|
@@ -2010,14 +2093,11 @@ var OpenGeniClient = class {
|
|
|
2010
2093
|
return logoAssetPath ? `${this.baseUrl}/v1/${logoAssetPath}` : null;
|
|
2011
2094
|
}
|
|
2012
2095
|
// --- GitHub ----------------------------------------------------------------------------------
|
|
2013
|
-
/** GitHub App configuration
|
|
2096
|
+
/** GitHub App server configuration plus truthful workspace binding status. */
|
|
2014
2097
|
async getGitHubApp(workspaceId) {
|
|
2015
2098
|
return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/github/app`);
|
|
2016
2099
|
}
|
|
2017
|
-
/**
|
|
2018
|
-
* Compatibility URL for previously issued state. New installation binding is
|
|
2019
|
-
* disabled, so the endpoint validates state and terminates with HTTP 410.
|
|
2020
|
-
*/
|
|
2100
|
+
/** Build the GitHub owner-consent entry URL for fresh workspace-bound state. */
|
|
2021
2101
|
githubConnectUrl(workspaceId, state) {
|
|
2022
2102
|
return this.url(`/v1/workspaces/${workspaceId}/github/connect`, { state });
|
|
2023
2103
|
}
|
|
@@ -2099,12 +2179,13 @@ var OpenGeniClient = class {
|
|
|
2099
2179
|
return await this.requestJson("POST", "/v1/billing/checkout", request);
|
|
2100
2180
|
}
|
|
2101
2181
|
// --- Internals -------------------------------------------------------------
|
|
2102
|
-
headers() {
|
|
2182
|
+
headers(correlationId) {
|
|
2103
2183
|
const extra = typeof this.options.headers === "function" ? this.options.headers() : this.options.headers;
|
|
2104
2184
|
return {
|
|
2105
2185
|
...this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {},
|
|
2106
2186
|
...extra,
|
|
2107
|
-
[OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION
|
|
2187
|
+
[OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION,
|
|
2188
|
+
...correlationId ? { [OPENGENI_CORRELATION_HEADER]: correlationId } : {}
|
|
2108
2189
|
};
|
|
2109
2190
|
}
|
|
2110
2191
|
url(path, query = {}) {
|
|
@@ -2220,36 +2301,62 @@ var OpenGeniClient = class {
|
|
|
2220
2301
|
);
|
|
2221
2302
|
}
|
|
2222
2303
|
async requestJson(method, path, body, query = {}, options = {}) {
|
|
2223
|
-
const
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
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
|
+
}
|
|
2233
2323
|
assertApiContractResponse(response);
|
|
2234
2324
|
if (!response.ok) {
|
|
2235
|
-
throw
|
|
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;
|
|
2236
2335
|
}
|
|
2237
|
-
return await response.json();
|
|
2238
2336
|
}
|
|
2239
2337
|
/** Like `requestJson` for endpoints that respond with no body (204). */
|
|
2240
2338
|
async requestVoid(method, path, body) {
|
|
2241
|
-
const
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
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
|
+
}
|
|
2250
2357
|
assertApiContractResponse(response);
|
|
2251
2358
|
if (!response.ok) {
|
|
2252
|
-
throw
|
|
2359
|
+
throw await apiErrorFromResponse(response, { method, correlationId });
|
|
2253
2360
|
}
|
|
2254
2361
|
}
|
|
2255
2362
|
};
|
|
@@ -2259,25 +2366,62 @@ function assertApiContractResponse(response) {
|
|
|
2259
2366
|
throw new OpenGeniApiContractMismatchError(OPENGENI_API_CONTRACT_REVISION, actual);
|
|
2260
2367
|
}
|
|
2261
2368
|
}
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
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
|
+
});
|
|
2266
2375
|
}
|
|
2267
|
-
async function
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
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");
|
|
2271
2394
|
return "";
|
|
2272
2395
|
}
|
|
2273
|
-
}
|
|
2274
|
-
async function safeBoundedText(response) {
|
|
2275
2396
|
try {
|
|
2276
|
-
return new TextDecoder().decode(
|
|
2397
|
+
return new TextDecoder().decode(
|
|
2398
|
+
await readBoundedResponseBytes(response, API_ERROR_MAX_BYTES, null)
|
|
2399
|
+
);
|
|
2277
2400
|
} catch {
|
|
2278
2401
|
return "";
|
|
2279
2402
|
}
|
|
2280
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
|
+
}
|
|
2281
2425
|
async function cancelResponseBody(response, reason) {
|
|
2282
2426
|
await response.body?.cancel(reason).catch(() => void 0);
|
|
2283
2427
|
}
|
|
@@ -2729,6 +2873,7 @@ export {
|
|
|
2729
2873
|
KNOWN_USAGE_EVENT_TYPES,
|
|
2730
2874
|
OPENGENI_API_CONTRACT_HEADER,
|
|
2731
2875
|
OPENGENI_API_CONTRACT_REVISION,
|
|
2876
|
+
OPENGENI_CORRELATION_HEADER,
|
|
2732
2877
|
OpenGeniApiContractMismatchError,
|
|
2733
2878
|
OpenGeniApiError,
|
|
2734
2879
|
OpenGeniClient,
|