@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/README.md +52 -0
- package/dist/index.d.ts +206 -14
- package/dist/index.js +286 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +302 -81
- package/src/errors.ts +89 -12
- package/src/index.ts +18 -0
- package/src/types.ts +236 -6
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
|
-
|
|
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.
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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)
|
|
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
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
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
|
|
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
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
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
|
|
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
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
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(
|
|
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,
|