@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/sdk",
3
- "version": "0.25.0",
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) throw new OpenGeniApiError(response.status, await safeText(response));
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 new OpenGeniApiError(response.status, await safeText(response));
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 new OpenGeniApiError(response.status, await safeText(response));
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) throw new OpenGeniApiError(response.status, await safeText(response));
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 new OpenGeniApiError(putResponse.status, await safeText(putResponse));
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 new OpenGeniApiError(response.status, await safeBoundedText(response));
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 status; install/link URLs are null while new binding is disabled. */
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 response = await this.fetchImpl(this.url(path, query), {
2748
- method,
2749
- headers: {
2750
- ...this.headers(),
2751
- Accept: "application/json",
2752
- ...(body !== undefined ? { "Content-Type": "application/json" } : {}),
2753
- },
2754
- ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
2755
- ...(options.signal ? { signal: options.signal } : {}),
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 new OpenGeniApiError(response.status, await safeText(response));
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 response = await this.fetchImpl(this.url(path), {
2767
- method,
2768
- headers: {
2769
- ...this.headers(),
2770
- Accept: "application/json",
2771
- ...(body !== undefined ? { "Content-Type": "application/json" } : {}),
2772
- },
2773
- ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
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 new OpenGeniApiError(response.status, await safeText(response));
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(status: number, body: string) {
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
- super(`OpenGeni API ${status}: ${decoded.message ?? (body || "(empty body)")}`);
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 = decoded.code;
13
- this.body = body;
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): { code?: string; message?: string } {
18
- if (!body) return {};
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
- ...(typeof record.code === "string" && record.code ? { code: record.code } : {}),
25
- ...(typeof record.message === "string" && record.message ? { message: record.message } : {}),
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,