@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/sdk",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.5",
|
|
4
4
|
"description": "Framework-agnostic TypeScript SDK for the OpenGeni API: typed client, session lifecycle, SSE event streaming with reconnect + replay-by-sequence, and proxy re-streaming helpers.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
package/src/client.ts
CHANGED
|
@@ -33,6 +33,8 @@ import type {
|
|
|
33
33
|
CapabilityCatalogResponse,
|
|
34
34
|
CapabilityInstallation,
|
|
35
35
|
AddDocumentRequest,
|
|
36
|
+
CreateKnowledgeDropRequest,
|
|
37
|
+
MoveDocumentRequest,
|
|
36
38
|
ClientConfig,
|
|
37
39
|
WorkspaceModelCatalogResponse,
|
|
38
40
|
ClientSessionEventInput,
|
|
@@ -170,6 +172,7 @@ import type {
|
|
|
170
172
|
UpdateScheduledTaskRequest,
|
|
171
173
|
UpdateSessionGoalRequest,
|
|
172
174
|
UpdateSessionRequest,
|
|
175
|
+
UpdateSessionToolPolicyRequest,
|
|
173
176
|
UpdateVariableSetRequest,
|
|
174
177
|
UpdateRigRequest,
|
|
175
178
|
UpdateWorkspaceMemberRequest,
|
|
@@ -196,6 +199,7 @@ import type {
|
|
|
196
199
|
import {
|
|
197
200
|
OPENGENI_API_CONTRACT_HEADER,
|
|
198
201
|
OPENGENI_API_CONTRACT_REVISION,
|
|
202
|
+
OPENGENI_CORRELATION_HEADER,
|
|
199
203
|
RETAINED_OUTPUT_MAX_PAGE_BYTES,
|
|
200
204
|
} from "./types";
|
|
201
205
|
|
|
@@ -323,6 +327,19 @@ export class OpenGeniClient {
|
|
|
323
327
|
);
|
|
324
328
|
}
|
|
325
329
|
|
|
330
|
+
/** Replace the durable tool policy or explicitly adopt workspace defaults. */
|
|
331
|
+
async updateSessionToolPolicy(
|
|
332
|
+
workspaceId: string,
|
|
333
|
+
sessionId: string,
|
|
334
|
+
request: UpdateSessionToolPolicyRequest,
|
|
335
|
+
): Promise<Session> {
|
|
336
|
+
return await this.requestJson<Session>(
|
|
337
|
+
"PUT",
|
|
338
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/tool-policy`,
|
|
339
|
+
request,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
326
343
|
/**
|
|
327
344
|
* Replace one attached MCP server's approval policy. The change is captured
|
|
328
345
|
* by the next claimed attempt; already-claimed work keeps its immutable
|
|
@@ -396,7 +413,13 @@ export class OpenGeniClient {
|
|
|
396
413
|
);
|
|
397
414
|
} catch (error) {
|
|
398
415
|
if (error instanceof OpenGeniApiError && error.status === 410) {
|
|
399
|
-
throw new OpenGeniSessionListCursorError(error.status, error.body
|
|
416
|
+
throw new OpenGeniSessionListCursorError(error.status, error.body, {
|
|
417
|
+
...(error.code ? { code: error.code } : {}),
|
|
418
|
+
retryable: error.retryable,
|
|
419
|
+
...(error.correlationId ? { correlationId: error.correlationId } : {}),
|
|
420
|
+
outcomeUnknown: error.outcomeUnknown,
|
|
421
|
+
displayMessage: "The session list changed — refresh and try again.",
|
|
422
|
+
});
|
|
400
423
|
}
|
|
401
424
|
throw error;
|
|
402
425
|
}
|
|
@@ -650,6 +673,7 @@ export class OpenGeniClient {
|
|
|
650
673
|
}
|
|
651
674
|
const listOptions: SessionEventListOptions | null =
|
|
652
675
|
options.resultMode === "compact" ? null : options;
|
|
676
|
+
const correlationId = crypto.randomUUID();
|
|
653
677
|
const response = await this.fetchImpl(
|
|
654
678
|
this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, {
|
|
655
679
|
...(listOptions?.after !== undefined ? { after: String(listOptions.after) } : {}),
|
|
@@ -676,11 +700,14 @@ export class OpenGeniClient {
|
|
|
676
700
|
}),
|
|
677
701
|
{
|
|
678
702
|
method: "GET",
|
|
679
|
-
headers: { ...this.headers(), Accept: "application/json" },
|
|
703
|
+
headers: { ...this.headers(correlationId), Accept: "application/json" },
|
|
680
704
|
},
|
|
681
705
|
);
|
|
682
706
|
assertApiContractResponse(response);
|
|
683
|
-
if (!response.ok)
|
|
707
|
+
if (!response.ok) {
|
|
708
|
+
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
709
|
+
}
|
|
710
|
+
await assertJsonResponse(response, { method: "GET", correlationId });
|
|
684
711
|
const body = await response.json();
|
|
685
712
|
if (options.resultMode === "compact") {
|
|
686
713
|
return body as SessionEventCompactResult;
|
|
@@ -880,14 +907,15 @@ export class OpenGeniClient {
|
|
|
880
907
|
const url = this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events/stream`, {
|
|
881
908
|
after: String(options.after ?? 0),
|
|
882
909
|
});
|
|
910
|
+
const correlationId = crypto.randomUUID();
|
|
883
911
|
const response = await this.fetchImpl(url, {
|
|
884
912
|
method: "GET",
|
|
885
|
-
headers: { ...this.headers(), Accept: "text/event-stream" },
|
|
913
|
+
headers: { ...this.headers(correlationId), Accept: "text/event-stream" },
|
|
886
914
|
...(options.signal ? { signal: options.signal } : {}),
|
|
887
915
|
});
|
|
888
916
|
assertApiContractResponse(response);
|
|
889
917
|
if (!response.ok) {
|
|
890
|
-
throw
|
|
918
|
+
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
891
919
|
}
|
|
892
920
|
if (!response.body) {
|
|
893
921
|
throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
|
|
@@ -1033,6 +1061,7 @@ export class OpenGeniClient {
|
|
|
1033
1061
|
workspaceId: string,
|
|
1034
1062
|
options: { after?: number; limit?: number } = {},
|
|
1035
1063
|
): Promise<WorkspaceControlEventPage> {
|
|
1064
|
+
const correlationId = crypto.randomUUID();
|
|
1036
1065
|
const response = await this.fetchImpl(
|
|
1037
1066
|
this.url(`/v1/workspaces/${workspaceId}/control-events`, {
|
|
1038
1067
|
...(options.after !== undefined ? { after: String(options.after) } : {}),
|
|
@@ -1040,13 +1069,14 @@ export class OpenGeniClient {
|
|
|
1040
1069
|
}),
|
|
1041
1070
|
{
|
|
1042
1071
|
method: "GET",
|
|
1043
|
-
headers: { ...this.headers(), Accept: "application/json" },
|
|
1072
|
+
headers: { ...this.headers(correlationId), Accept: "application/json" },
|
|
1044
1073
|
},
|
|
1045
1074
|
);
|
|
1046
1075
|
assertApiContractResponse(response);
|
|
1047
1076
|
if (!response.ok) {
|
|
1048
|
-
throw
|
|
1077
|
+
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
1049
1078
|
}
|
|
1079
|
+
await assertJsonResponse(response, { method: "GET", correlationId });
|
|
1050
1080
|
const events = (await response.json()) as WorkspaceControlEvent[];
|
|
1051
1081
|
const bytesHeader = response.headers.get("X-OpenGeni-Page-Bytes");
|
|
1052
1082
|
const nextHeader = response.headers.get("X-OpenGeni-Next-After");
|
|
@@ -1087,18 +1117,21 @@ export class OpenGeniClient {
|
|
|
1087
1117
|
workspaceId: string,
|
|
1088
1118
|
options: { after?: number; signal?: AbortSignal } = {},
|
|
1089
1119
|
): Promise<ReadableStream<Uint8Array>> {
|
|
1120
|
+
const correlationId = crypto.randomUUID();
|
|
1090
1121
|
const response = await this.fetchImpl(
|
|
1091
1122
|
this.url(`/v1/workspaces/${workspaceId}/control-events/stream`, {
|
|
1092
1123
|
after: String(options.after ?? 0),
|
|
1093
1124
|
}),
|
|
1094
1125
|
{
|
|
1095
1126
|
method: "GET",
|
|
1096
|
-
headers: { ...this.headers(), Accept: "text/event-stream" },
|
|
1127
|
+
headers: { ...this.headers(correlationId), Accept: "text/event-stream" },
|
|
1097
1128
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1098
1129
|
},
|
|
1099
1130
|
);
|
|
1100
1131
|
assertApiContractResponse(response);
|
|
1101
|
-
if (!response.ok)
|
|
1132
|
+
if (!response.ok) {
|
|
1133
|
+
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
1134
|
+
}
|
|
1102
1135
|
if (!response.body) {
|
|
1103
1136
|
throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
|
|
1104
1137
|
}
|
|
@@ -2013,7 +2046,7 @@ export class OpenGeniClient {
|
|
|
2013
2046
|
body,
|
|
2014
2047
|
});
|
|
2015
2048
|
if (!putResponse.ok) {
|
|
2016
|
-
throw
|
|
2049
|
+
throw await apiErrorFromResponse(putResponse, { method: "PUT" });
|
|
2017
2050
|
}
|
|
2018
2051
|
return await this.completeFileUpload(workspaceId, upload.uploadId);
|
|
2019
2052
|
}
|
|
@@ -2048,12 +2081,13 @@ export class OpenGeniClient {
|
|
|
2048
2081
|
if (options.range && (options.range.length > 128 || /[^\x20-\x7e]/.test(options.range))) {
|
|
2049
2082
|
throw new RangeError("retained artifact range must be at most 128 printable ASCII bytes");
|
|
2050
2083
|
}
|
|
2084
|
+
const correlationId = crypto.randomUUID();
|
|
2051
2085
|
const response = await this.fetchImpl(
|
|
2052
2086
|
this.url(`/v1/workspaces/${workspaceId}/artifacts/${artifactId}/content`),
|
|
2053
2087
|
{
|
|
2054
2088
|
method: "GET",
|
|
2055
2089
|
headers: {
|
|
2056
|
-
...this.headers(),
|
|
2090
|
+
...this.headers(correlationId),
|
|
2057
2091
|
Accept: "application/octet-stream",
|
|
2058
2092
|
...(options.range ? { Range: options.range } : {}),
|
|
2059
2093
|
},
|
|
@@ -2067,7 +2101,7 @@ export class OpenGeniClient {
|
|
|
2067
2101
|
throw error;
|
|
2068
2102
|
}
|
|
2069
2103
|
if (!response.ok) {
|
|
2070
|
-
throw
|
|
2104
|
+
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
2071
2105
|
}
|
|
2072
2106
|
if (response.status !== 200 && response.status !== 206) {
|
|
2073
2107
|
await cancelResponseBody(response, "unexpected retained artifact response status");
|
|
@@ -2157,6 +2191,39 @@ export class OpenGeniClient {
|
|
|
2157
2191
|
);
|
|
2158
2192
|
}
|
|
2159
2193
|
|
|
2194
|
+
/**
|
|
2195
|
+
* Drop raw text or an already-uploaded file into the workspace's Default
|
|
2196
|
+
* base. When curation is enabled, it may name, summarize, categorize, and
|
|
2197
|
+
* (confidence permitting) file the document into the best-matching base;
|
|
2198
|
+
* provider=none leaves caller metadata and Default placement unchanged.
|
|
2199
|
+
*/
|
|
2200
|
+
async createKnowledgeDrop(
|
|
2201
|
+
workspaceId: string,
|
|
2202
|
+
request: CreateKnowledgeDropRequest,
|
|
2203
|
+
): Promise<Document> {
|
|
2204
|
+
return await this.requestJson<Document>(
|
|
2205
|
+
"POST",
|
|
2206
|
+
`/v1/workspaces/${workspaceId}/knowledge/drops`,
|
|
2207
|
+
request,
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
/**
|
|
2212
|
+
* Move a document (and its indexed chunks) to another base. With no
|
|
2213
|
+
* targetBaseId, applies the document's stored curation suggestion.
|
|
2214
|
+
*/
|
|
2215
|
+
async moveDocument(
|
|
2216
|
+
workspaceId: string,
|
|
2217
|
+
documentId: string,
|
|
2218
|
+
request: MoveDocumentRequest = {},
|
|
2219
|
+
): Promise<Document> {
|
|
2220
|
+
return await this.requestJson<Document>(
|
|
2221
|
+
"POST",
|
|
2222
|
+
`/v1/workspaces/${workspaceId}/documents/${documentId}/move`,
|
|
2223
|
+
request,
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2160
2227
|
/** Retry indexing for a failed document. */
|
|
2161
2228
|
async reindexDocument(
|
|
2162
2229
|
workspaceId: string,
|
|
@@ -2459,15 +2526,12 @@ export class OpenGeniClient {
|
|
|
2459
2526
|
|
|
2460
2527
|
// --- GitHub ----------------------------------------------------------------------------------
|
|
2461
2528
|
|
|
2462
|
-
/** GitHub App configuration
|
|
2529
|
+
/** GitHub App server configuration plus truthful workspace binding status. */
|
|
2463
2530
|
async getGitHubApp(workspaceId: string): Promise<GitHubAppInfo> {
|
|
2464
2531
|
return await this.requestJson<GitHubAppInfo>("GET", `/v1/workspaces/${workspaceId}/github/app`);
|
|
2465
2532
|
}
|
|
2466
2533
|
|
|
2467
|
-
/**
|
|
2468
|
-
* Compatibility URL for previously issued state. New installation binding is
|
|
2469
|
-
* disabled, so the endpoint validates state and terminates with HTTP 410.
|
|
2470
|
-
*/
|
|
2534
|
+
/** Build the GitHub owner-consent entry URL for fresh workspace-bound state. */
|
|
2471
2535
|
githubConnectUrl(workspaceId: string, state: string): string {
|
|
2472
2536
|
return this.url(`/v1/workspaces/${workspaceId}/github/connect`, { state });
|
|
2473
2537
|
}
|
|
@@ -2574,13 +2638,14 @@ export class OpenGeniClient {
|
|
|
2574
2638
|
|
|
2575
2639
|
// --- Internals -------------------------------------------------------------
|
|
2576
2640
|
|
|
2577
|
-
private headers(): Record<string, string> {
|
|
2641
|
+
private headers(correlationId?: string): Record<string, string> {
|
|
2578
2642
|
const extra =
|
|
2579
2643
|
typeof this.options.headers === "function" ? this.options.headers() : this.options.headers;
|
|
2580
2644
|
return {
|
|
2581
2645
|
...(this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {}),
|
|
2582
2646
|
...extra,
|
|
2583
2647
|
[OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION,
|
|
2648
|
+
...(correlationId ? { [OPENGENI_CORRELATION_HEADER]: correlationId } : {}),
|
|
2584
2649
|
};
|
|
2585
2650
|
}
|
|
2586
2651
|
|
|
@@ -2744,37 +2809,63 @@ export class OpenGeniClient {
|
|
|
2744
2809
|
query: Record<string, string> = {},
|
|
2745
2810
|
options: OpenGeniRequestOptions = {},
|
|
2746
2811
|
): Promise<T> {
|
|
2747
|
-
const
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2812
|
+
const correlationId = crypto.randomUUID();
|
|
2813
|
+
let response: Response;
|
|
2814
|
+
try {
|
|
2815
|
+
response = await this.fetchImpl(this.url(path, query), {
|
|
2816
|
+
method,
|
|
2817
|
+
headers: {
|
|
2818
|
+
...this.headers(correlationId),
|
|
2819
|
+
Accept: "application/json",
|
|
2820
|
+
...(body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
2821
|
+
},
|
|
2822
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
2823
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
2824
|
+
});
|
|
2825
|
+
} catch (error) {
|
|
2826
|
+
if (isMutationMethod(method)) {
|
|
2827
|
+
throw mutationTransportError(correlationId);
|
|
2828
|
+
}
|
|
2829
|
+
throw error;
|
|
2830
|
+
}
|
|
2757
2831
|
assertApiContractResponse(response);
|
|
2758
2832
|
if (!response.ok) {
|
|
2759
|
-
throw
|
|
2833
|
+
throw await apiErrorFromResponse(response, { method, correlationId });
|
|
2834
|
+
}
|
|
2835
|
+
await assertJsonResponse(response, { method, correlationId });
|
|
2836
|
+
try {
|
|
2837
|
+
return (await response.json()) as T;
|
|
2838
|
+
} catch (error) {
|
|
2839
|
+
if (isMutationMethod(method)) {
|
|
2840
|
+
throw mutationTransportError(correlationId);
|
|
2841
|
+
}
|
|
2842
|
+
throw error;
|
|
2760
2843
|
}
|
|
2761
|
-
return (await response.json()) as T;
|
|
2762
2844
|
}
|
|
2763
2845
|
|
|
2764
2846
|
/** Like `requestJson` for endpoints that respond with no body (204). */
|
|
2765
2847
|
private async requestVoid(method: string, path: string, body?: unknown): Promise<void> {
|
|
2766
|
-
const
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2848
|
+
const correlationId = crypto.randomUUID();
|
|
2849
|
+
let response: Response;
|
|
2850
|
+
try {
|
|
2851
|
+
response = await this.fetchImpl(this.url(path), {
|
|
2852
|
+
method,
|
|
2853
|
+
headers: {
|
|
2854
|
+
...this.headers(correlationId),
|
|
2855
|
+
Accept: "application/json",
|
|
2856
|
+
...(body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
2857
|
+
},
|
|
2858
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
2859
|
+
});
|
|
2860
|
+
} catch (error) {
|
|
2861
|
+
if (isMutationMethod(method)) {
|
|
2862
|
+
throw mutationTransportError(correlationId);
|
|
2863
|
+
}
|
|
2864
|
+
throw error;
|
|
2865
|
+
}
|
|
2775
2866
|
assertApiContractResponse(response);
|
|
2776
2867
|
if (!response.ok) {
|
|
2777
|
-
throw
|
|
2868
|
+
throw await apiErrorFromResponse(response, { method, correlationId });
|
|
2778
2869
|
}
|
|
2779
2870
|
}
|
|
2780
2871
|
}
|
|
@@ -2786,6 +2877,75 @@ function assertApiContractResponse(response: Response): void {
|
|
|
2786
2877
|
}
|
|
2787
2878
|
}
|
|
2788
2879
|
|
|
2880
|
+
const API_ERROR_MAX_BYTES = 16 * 1024;
|
|
2881
|
+
|
|
2882
|
+
type ApiErrorRequestContext = {
|
|
2883
|
+
method: string;
|
|
2884
|
+
correlationId?: string | undefined;
|
|
2885
|
+
};
|
|
2886
|
+
|
|
2887
|
+
async function apiErrorFromResponse(
|
|
2888
|
+
response: Response,
|
|
2889
|
+
context: ApiErrorRequestContext,
|
|
2890
|
+
): Promise<OpenGeniApiError> {
|
|
2891
|
+
return new OpenGeniApiError(response.status, await readBoundedJsonErrorBody(response), {
|
|
2892
|
+
correlationId: response.headers.get(OPENGENI_CORRELATION_HEADER) ?? context.correlationId,
|
|
2893
|
+
mutation: isMutationMethod(context.method),
|
|
2894
|
+
});
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
async function assertJsonResponse(
|
|
2898
|
+
response: Response,
|
|
2899
|
+
context: ApiErrorRequestContext,
|
|
2900
|
+
): Promise<void> {
|
|
2901
|
+
if (isJsonContentType(response.headers.get("content-type"))) return;
|
|
2902
|
+
await cancelResponseBody(response, "unexpected non-JSON API response");
|
|
2903
|
+
throw new OpenGeniApiError(502, "", {
|
|
2904
|
+
code: "upstream_unavailable",
|
|
2905
|
+
retryable: true,
|
|
2906
|
+
correlationId: response.headers.get(OPENGENI_CORRELATION_HEADER) ?? context.correlationId,
|
|
2907
|
+
outcomeUnknown: isMutationMethod(context.method),
|
|
2908
|
+
displayMessage: "OpenGeni is temporarily unavailable — retry.",
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
async function readBoundedJsonErrorBody(response: Response): Promise<string> {
|
|
2913
|
+
if (!isJsonContentType(response.headers.get("content-type"))) {
|
|
2914
|
+
await cancelResponseBody(response, "discarding API error body");
|
|
2915
|
+
return "";
|
|
2916
|
+
}
|
|
2917
|
+
if (Number(response.headers.get("content-length")) > API_ERROR_MAX_BYTES) {
|
|
2918
|
+
await cancelResponseBody(response, "discarding API error body");
|
|
2919
|
+
return "";
|
|
2920
|
+
}
|
|
2921
|
+
try {
|
|
2922
|
+
return new TextDecoder().decode(
|
|
2923
|
+
await readBoundedResponseBytes(response, API_ERROR_MAX_BYTES, null),
|
|
2924
|
+
);
|
|
2925
|
+
} catch {
|
|
2926
|
+
return "";
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
function isJsonContentType(value: string | null): boolean {
|
|
2931
|
+
return /^(application\/json|[^;]+\+json)\s*(;|$)/i.test(value ?? "");
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
function isMutationMethod(method: string): boolean {
|
|
2935
|
+
return method !== "GET" && method !== "HEAD" && method !== "OPTIONS";
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
function mutationTransportError(correlationId: string): OpenGeniApiError {
|
|
2939
|
+
return new OpenGeniApiError(0, "", {
|
|
2940
|
+
code: "network_error",
|
|
2941
|
+
retryable: true,
|
|
2942
|
+
correlationId,
|
|
2943
|
+
outcomeUnknown: true,
|
|
2944
|
+
mutation: true,
|
|
2945
|
+
displayMessage: "OpenGeni could not confirm the request — reconcile before retrying.",
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2789
2949
|
async function sha256ForUpload(body: Blob | ArrayBuffer | string): Promise<string> {
|
|
2790
2950
|
const bytes =
|
|
2791
2951
|
typeof body === "string"
|
|
@@ -2797,22 +2957,6 @@ async function sha256ForUpload(body: Blob | ArrayBuffer | string): Promise<strin
|
|
|
2797
2957
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2798
2958
|
}
|
|
2799
2959
|
|
|
2800
|
-
async function safeText(response: Response): Promise<string> {
|
|
2801
|
-
try {
|
|
2802
|
-
return await response.text();
|
|
2803
|
-
} catch {
|
|
2804
|
-
return "";
|
|
2805
|
-
}
|
|
2806
|
-
}
|
|
2807
|
-
|
|
2808
|
-
async function safeBoundedText(response: Response): Promise<string> {
|
|
2809
|
-
try {
|
|
2810
|
-
return new TextDecoder().decode(await readBoundedResponseBytes(response, 64 * 1024, null));
|
|
2811
|
-
} catch {
|
|
2812
|
-
return "";
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
2960
|
async function cancelResponseBody(response: Response, reason: string): Promise<void> {
|
|
2817
2961
|
await response.body?.cancel(reason).catch(() => undefined);
|
|
2818
2962
|
}
|
package/src/errors.ts
CHANGED
|
@@ -2,33 +2,97 @@
|
|
|
2
2
|
export class OpenGeniApiError extends Error {
|
|
3
3
|
readonly status: number;
|
|
4
4
|
readonly code: string | undefined;
|
|
5
|
+
readonly retryable: boolean;
|
|
6
|
+
readonly correlationId: string | undefined;
|
|
7
|
+
/** True only when an uncontrolled transport failed after a mutation may have been accepted. */
|
|
8
|
+
readonly outcomeUnknown: boolean;
|
|
5
9
|
readonly body: string;
|
|
6
10
|
|
|
7
|
-
constructor(
|
|
11
|
+
constructor(
|
|
12
|
+
status: number,
|
|
13
|
+
body: string,
|
|
14
|
+
options: {
|
|
15
|
+
code?: string | undefined;
|
|
16
|
+
retryable?: boolean | undefined;
|
|
17
|
+
correlationId?: string | undefined;
|
|
18
|
+
outcomeUnknown?: boolean | undefined;
|
|
19
|
+
displayMessage?: string | undefined;
|
|
20
|
+
mutation?: boolean | undefined;
|
|
21
|
+
} = {},
|
|
22
|
+
) {
|
|
8
23
|
const decoded = decodeApiErrorBody(body);
|
|
9
|
-
|
|
24
|
+
const correlationId = decoded?.requestId ?? boundedCorrelationId(options.correlationId);
|
|
25
|
+
const gatewayFailure = status >= 502 && status <= 504;
|
|
26
|
+
const fromResponse = options.mutation !== undefined;
|
|
27
|
+
const message = decoded?.message ?? (fromResponse ? "Request failed." : body || "(empty body)");
|
|
28
|
+
const displayMessage =
|
|
29
|
+
options.displayMessage ??
|
|
30
|
+
(gatewayFailure && fromResponse
|
|
31
|
+
? "OpenGeni is temporarily unavailable — retry."
|
|
32
|
+
: `OpenGeni API ${status}: ${message}`);
|
|
33
|
+
super(correlationId ? `${displayMessage} Reference: ${correlationId}.` : displayMessage);
|
|
10
34
|
this.name = "OpenGeniApiError";
|
|
11
35
|
this.status = status;
|
|
12
|
-
this.code =
|
|
13
|
-
|
|
36
|
+
this.code =
|
|
37
|
+
options.code ??
|
|
38
|
+
decoded?.code ??
|
|
39
|
+
(gatewayFailure && fromResponse ? "upstream_unavailable" : undefined);
|
|
40
|
+
this.retryable = options.retryable ?? decoded?.retryable ?? retryableApiStatus(status);
|
|
41
|
+
this.correlationId = correlationId;
|
|
42
|
+
this.outcomeUnknown =
|
|
43
|
+
options.outcomeUnknown ?? (gatewayFailure && !!options.mutation && !decoded);
|
|
44
|
+
this.body = !fromResponse || decoded ? body : "";
|
|
14
45
|
}
|
|
15
46
|
}
|
|
16
47
|
|
|
17
|
-
function decodeApiErrorBody(body: string): {
|
|
18
|
-
|
|
48
|
+
function decodeApiErrorBody(body: string): {
|
|
49
|
+
code: string | undefined;
|
|
50
|
+
message: string | undefined;
|
|
51
|
+
requestId: string | undefined;
|
|
52
|
+
retryable: boolean | undefined;
|
|
53
|
+
} | null {
|
|
54
|
+
if (!body) return null;
|
|
19
55
|
try {
|
|
20
56
|
const decoded: unknown = JSON.parse(body);
|
|
21
|
-
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return
|
|
57
|
+
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return null;
|
|
22
58
|
const record = decoded as Record<string, unknown>;
|
|
59
|
+
const nested =
|
|
60
|
+
record.error && typeof record.error === "object" && !Array.isArray(record.error)
|
|
61
|
+
? (record.error as Record<string, unknown>)
|
|
62
|
+
: record;
|
|
63
|
+
const code = boundedApiField(nested.code);
|
|
64
|
+
const message = boundedApiField(nested.message);
|
|
65
|
+
const requestId = boundedCorrelationId(nested.requestId);
|
|
66
|
+
const retryable = typeof nested.retryable === "boolean" ? nested.retryable : undefined;
|
|
67
|
+
if (!code && !message && !requestId && retryable === undefined) return null;
|
|
23
68
|
return {
|
|
24
|
-
|
|
25
|
-
|
|
69
|
+
code,
|
|
70
|
+
message,
|
|
71
|
+
requestId,
|
|
72
|
+
retryable,
|
|
26
73
|
};
|
|
27
74
|
} catch {
|
|
28
|
-
return
|
|
75
|
+
return null;
|
|
29
76
|
}
|
|
30
77
|
}
|
|
31
78
|
|
|
79
|
+
function boundedApiField(value: unknown): string | undefined {
|
|
80
|
+
if (typeof value !== "string") return;
|
|
81
|
+
const bytes = new TextEncoder().encode(value);
|
|
82
|
+
return bytes.byteLength <= 512 ? value : new TextDecoder().decode(bytes.slice(0, 512));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function retryableApiStatus(status: number): boolean {
|
|
86
|
+
return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function boundedCorrelationId(value: unknown): string | undefined {
|
|
90
|
+
if (typeof value !== "string" || value.length > 128 || !/^[\w.:-]+$/.test(value)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
|
|
32
96
|
/** A short-lived session-list snapshot cursor can no longer be continued. */
|
|
33
97
|
export class OpenGeniSessionListCursorError extends OpenGeniApiError {}
|
|
34
98
|
|
|
@@ -67,14 +131,6 @@ export function isAbortError(error: unknown): boolean {
|
|
|
67
131
|
* permanent and surface to the caller instead.
|
|
68
132
|
*/
|
|
69
133
|
export function isRetryableStreamError(error: unknown): boolean {
|
|
70
|
-
if (error instanceof OpenGeniApiError)
|
|
71
|
-
return (
|
|
72
|
-
error.status === 408 ||
|
|
73
|
-
error.status === 409 ||
|
|
74
|
-
error.status === 425 ||
|
|
75
|
-
error.status === 429 ||
|
|
76
|
-
error.status >= 500
|
|
77
|
-
);
|
|
78
|
-
}
|
|
134
|
+
if (error instanceof OpenGeniApiError) return error.retryable;
|
|
79
135
|
return error instanceof TypeError;
|
|
80
136
|
}
|
package/src/index.ts
CHANGED
|
@@ -86,6 +86,7 @@ export {
|
|
|
86
86
|
KNOWN_USAGE_EVENT_TYPES,
|
|
87
87
|
OPENGENI_API_CONTRACT_HEADER,
|
|
88
88
|
OPENGENI_API_CONTRACT_REVISION,
|
|
89
|
+
OPENGENI_CORRELATION_HEADER,
|
|
89
90
|
RETAINED_OUTPUT_DEFAULT_PAGE_BYTES,
|
|
90
91
|
RETAINED_OUTPUT_MAX_PAGE_BYTES,
|
|
91
92
|
SESSION_EVENT_TYPES,
|
|
@@ -175,6 +176,7 @@ export type {
|
|
|
175
176
|
CreateFileUploadResponse,
|
|
176
177
|
CreateGitHubAppManifestRequest,
|
|
177
178
|
CreateGitHubAppManifestResponse,
|
|
179
|
+
CreateKnowledgeDropRequest,
|
|
178
180
|
CreateKnowledgeMemoryRequest,
|
|
179
181
|
CreateScheduledTaskRequest,
|
|
180
182
|
CreateSessionRequest,
|
|
@@ -184,11 +186,15 @@ export type {
|
|
|
184
186
|
DiscoverMcpCapabilitiesResponse,
|
|
185
187
|
Document,
|
|
186
188
|
DocumentBase,
|
|
189
|
+
DocumentCuration,
|
|
190
|
+
DocumentCurationStatus,
|
|
187
191
|
DocumentSearchMode,
|
|
188
192
|
DocumentSearchRequest,
|
|
189
193
|
DocumentSearchResponse,
|
|
190
194
|
DocumentSearchResult,
|
|
191
195
|
DocumentStatus,
|
|
196
|
+
DocumentVisibility,
|
|
197
|
+
MoveDocumentRequest,
|
|
192
198
|
EnableCapabilityRequest,
|
|
193
199
|
EnablePackRequest,
|
|
194
200
|
Entitlements,
|
|
@@ -206,7 +212,9 @@ export type {
|
|
|
206
212
|
FileUploadData,
|
|
207
213
|
GetPackResponse,
|
|
208
214
|
GitHubAppInfo,
|
|
215
|
+
GitHubBindingStatus,
|
|
209
216
|
GitHubInstallationBinding,
|
|
217
|
+
GitHubInstallationLifecycle,
|
|
210
218
|
GitHubRepositoriesResponse,
|
|
211
219
|
GitHubRepository,
|
|
212
220
|
GitHubRepositoryScope,
|
|
@@ -411,6 +419,7 @@ export type {
|
|
|
411
419
|
UpdateSessionMcpApprovalPolicyResponse,
|
|
412
420
|
UpdateSessionPinRequest,
|
|
413
421
|
UpdateSessionRequest,
|
|
422
|
+
UpdateSessionToolPolicyRequest,
|
|
414
423
|
UpdateVariableSetRequest,
|
|
415
424
|
UpdateWorkspaceEnvironmentRequest,
|
|
416
425
|
UpdateWorkspaceMemberRequest,
|