@gajae-code/ai 0.15.6 → 0.16.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/types/adapter-internals/aws-region.d.ts +7 -0
  3. package/dist/types/core.d.ts +1 -0
  4. package/dist/types/index.d.ts +1 -0
  5. package/dist/types/providers/anthropic.d.ts +1 -1
  6. package/dist/types/providers/google-gemini-headers.d.ts +1 -1
  7. package/dist/types/providers/openai-codex-responses.d.ts +6 -0
  8. package/dist/types/utils/schema/normalize.d.ts +0 -5
  9. package/dist/types/utils/sqlite-errors.d.ts +4 -0
  10. package/package.json +3 -3
  11. package/src/adapter-internals/aws-region.d.ts +7 -0
  12. package/src/adapter-internals/aws-region.ts +14 -0
  13. package/src/auth-storage.ts +14 -14
  14. package/src/core.ts +1 -0
  15. package/src/index.ts +1 -0
  16. package/src/model-thinking.ts +8 -0
  17. package/src/models.json +201 -3
  18. package/src/provider-models/openai-compat.ts +61 -0
  19. package/src/providers/amazon-bedrock.ts +5 -1
  20. package/src/providers/anthropic.d.ts +1 -1
  21. package/src/providers/anthropic.ts +1 -1
  22. package/src/providers/aws-credentials.ts +6 -0
  23. package/src/providers/google-gemini-headers.d.ts +1 -1
  24. package/src/providers/google-gemini-headers.ts +1 -1
  25. package/src/providers/kiro-api-key.ts +33 -8
  26. package/src/providers/kiro-codewhisperer.ts +4 -1
  27. package/src/providers/openai-codex-responses.d.ts +6 -0
  28. package/src/providers/openai-codex-responses.ts +17 -2
  29. package/src/providers/pi-native-client.ts +24 -1
  30. package/src/utils/oauth/kiro.ts +91 -22
  31. package/src/utils/schema/dereference.ts +169 -49
  32. package/src/utils/schema/draft.ts +46 -23
  33. package/src/utils/schema/normalize.d.ts +0 -5
  34. package/src/utils/schema/normalize.ts +396 -119
  35. package/src/utils/schema/types.ts +3 -1
  36. package/src/utils/schema/zod-decontaminate.ts +83 -29
  37. package/src/utils/sqlite-errors.d.ts +4 -0
  38. package/src/utils/sqlite-errors.ts +13 -0
  39. package/src/utils/tool-choice-capability.ts +2 -3
@@ -22,6 +22,7 @@
22
22
  import * as fs from "node:fs";
23
23
  import * as path from "node:path";
24
24
  import { $env, getTrustedHomeDir, isEnoent, logger } from "@gajae-code/utils";
25
+ import { assertAwsRegionLabel } from "../adapter-internals/aws-region";
25
26
  import {
26
27
  type AwsIniFile,
27
28
  classifyAwsProfileCapability,
@@ -155,6 +156,7 @@ async function readSsoCredentials(
155
156
  }
156
157
  }
157
158
  if (!startUrl || !ssoRegion) return undefined;
159
+ assertAwsRegionLabel(ssoRegion);
158
160
 
159
161
  const token = await loadSsoCachedToken(startUrl, sessionName);
160
162
  if (!token?.accessToken) {
@@ -172,6 +174,7 @@ async function readSsoCredentials(
172
174
  const response = await fetch(url, {
173
175
  method: "GET",
174
176
  headers: { "x-amz-sso_bearer_token": token.accessToken },
177
+ redirect: "error",
175
178
  signal,
176
179
  });
177
180
  if (!response.ok) {
@@ -411,6 +414,7 @@ async function readImdsCredentials(parentSignal: AbortSignal | undefined): Promi
411
414
  const tokenRes = await fetch(`http://${IMDS_HOST}/latest/api/token`, {
412
415
  method: "PUT",
413
416
  headers: { "x-aws-ec2-metadata-token-ttl-seconds": "21600" },
417
+ redirect: "error",
414
418
  signal,
415
419
  });
416
420
  if (!tokenRes.ok) return undefined;
@@ -418,6 +422,7 @@ async function readImdsCredentials(parentSignal: AbortSignal | undefined): Promi
418
422
 
419
423
  const roleRes = await fetch(`http://${IMDS_HOST}/latest/meta-data/iam/security-credentials/`, {
420
424
  headers: { "x-aws-ec2-metadata-token": token },
425
+ redirect: "error",
421
426
  signal,
422
427
  });
423
428
  if (!roleRes.ok) return undefined;
@@ -428,6 +433,7 @@ async function readImdsCredentials(parentSignal: AbortSignal | undefined): Promi
428
433
  `http://${IMDS_HOST}/latest/meta-data/iam/security-credentials/${encodeURIComponent(role)}`,
429
434
  {
430
435
  headers: { "x-aws-ec2-metadata-token": token },
436
+ redirect: "error",
431
437
  signal,
432
438
  },
433
439
  );
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export declare const GEMINI_CLI_VERSION_ENV = "GJC_AI_GEMINI_CLI_VERSION";
7
7
  export declare const LEGACY_GEMINI_CLI_VERSION_ENV = "PI_AI_GEMINI_CLI_VERSION";
8
- export declare const DEFAULT_GEMINI_CLI_VERSION = "0.52.0";
8
+ export declare const DEFAULT_GEMINI_CLI_VERSION = "0.58.0";
9
9
  export declare function getGeminiCliUserAgent(modelId?: string): string;
10
10
  export declare const getGeminiCliHeaders: (modelId?: string) => {
11
11
  "User-Agent": string;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export const GEMINI_CLI_VERSION_ENV = "GJC_AI_GEMINI_CLI_VERSION";
7
7
  export const LEGACY_GEMINI_CLI_VERSION_ENV = "PI_AI_GEMINI_CLI_VERSION";
8
- export const DEFAULT_GEMINI_CLI_VERSION = "0.52.0";
8
+ export const DEFAULT_GEMINI_CLI_VERSION = "0.58.0";
9
9
 
10
10
  export function getGeminiCliUserAgent(modelId = "gemini-3.1-pro-preview"): string {
11
11
  const version =
@@ -6,6 +6,7 @@
6
6
  * AWS SSO OIDC / CodeWhisperer streaming path used by `gjc auth-broker login kiro`.
7
7
  */
8
8
  import { $env } from "@gajae-code/utils";
9
+ import { assertAwsRegionLabel } from "../adapter-internals/aws-region";
9
10
  import { Effort } from "../model-thinking";
10
11
  import type {
11
12
  Api,
@@ -53,19 +54,40 @@ export function isKiroApiKey(value: string | undefined): value is string {
53
54
 
54
55
  export function kiroApiRegion(options?: { region?: string }): string {
55
56
  return (
56
- options?.region ||
57
- $env.KIRO_API_REGION ||
58
- $env.KIRO_REGION ||
59
- $env.AWS_REGION ||
60
- $env.AWS_DEFAULT_REGION ||
57
+ options?.region ??
58
+ $env.KIRO_API_REGION ??
59
+ $env.KIRO_REGION ??
60
+ $env.AWS_REGION ??
61
+ $env.AWS_DEFAULT_REGION ??
61
62
  DEFAULT_REGION
62
63
  );
63
64
  }
64
65
 
65
66
  export function kiroApiBaseUrl(region: string): string {
67
+ assertAwsRegionLabel(region);
66
68
  return `https://q.${region}.amazonaws.com/`;
67
69
  }
68
70
 
71
+ function isRegionDerivedKiroApiBaseUrl(baseUrl: string): boolean {
72
+ try {
73
+ const url = new URL(baseUrl);
74
+ const match = /^q\.([a-z0-9-]+)\.amazonaws\.com$/.exec(url.hostname);
75
+ if (!match) return false;
76
+ assertAwsRegionLabel(match[1]);
77
+ return (
78
+ url.protocol === "https:" &&
79
+ url.username === "" &&
80
+ url.password === "" &&
81
+ url.port === "" &&
82
+ url.pathname === "/" &&
83
+ url.search === "" &&
84
+ url.hash === ""
85
+ );
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
69
91
  export function toKiroModelId(modelId: string): string {
70
92
  return modelId.replace(/(\d)-(\d)/g, "$1.$2");
71
93
  }
@@ -279,12 +301,13 @@ export async function fetchKiroApiModels(
279
301
  apiKey: string,
280
302
  region?: string,
281
303
  ): Promise<Model<"kiro-codewhisperer-stream">[]> {
282
- const resolvedRegion = region || kiroApiRegion();
304
+ const resolvedRegion = region ?? kiroApiRegion();
283
305
  const baseUrl = kiroApiBaseUrl(resolvedRegion);
284
306
  const response = await fetch(baseUrl, {
285
307
  method: "POST",
286
308
  headers: kiroApiHeaders(apiKey, LIST_TARGET),
287
309
  body: JSON.stringify({ origin: KIRO_ORIGIN }),
310
+ redirect: "error",
288
311
  signal: AbortSignal.timeout(15_000),
289
312
  });
290
313
  if (!response.ok) {
@@ -585,8 +608,9 @@ export const streamKiroApiKey: StreamFunction<"kiro-codewhisperer-stream"> = (
585
608
  "Kiro API key missing. Set KIRO_API_KEY to a ksk_ key from https://app.kiro.dev/settings/api-keys.",
586
609
  );
587
610
  }
588
- const region = kiroApiRegion(options);
589
- const endpoint = model.baseUrl || kiroApiBaseUrl(region);
611
+ const configuredBaseUrl = model.baseUrl;
612
+ const usesExplicitBaseUrl = Boolean(configuredBaseUrl) && !isRegionDerivedKiroApiBaseUrl(configuredBaseUrl);
613
+ const endpoint = configuredBaseUrl || kiroApiBaseUrl(kiroApiRegion(options));
590
614
  const request = buildApiKeyRequest(model, context, options);
591
615
  options?.onPayload?.(request, model, options?.attemptScope);
592
616
 
@@ -594,6 +618,7 @@ export const streamKiroApiKey: StreamFunction<"kiro-codewhisperer-stream"> = (
594
618
  method: "POST",
595
619
  headers: { ...kiroApiHeaders(apiKey, CHAT_TARGET), ...(options.headers ?? {}) },
596
620
  body: JSON.stringify(request),
621
+ ...(usesExplicitBaseUrl ? {} : { redirect: "error" as const }),
597
622
  signal: options.signal,
598
623
  });
599
624
  if (!response.ok) {
@@ -11,6 +11,7 @@
11
11
  * not from any AGPL reference implementation.
12
12
  */
13
13
  import { $credentialEnv, $env, extractHttpStatusFromError } from "@gajae-code/utils";
14
+ import { assertAwsRegionLabel } from "../adapter-internals/aws-region";
14
15
  import type { Effort } from "../model-thinking";
15
16
  import type {
16
17
  Api,
@@ -178,9 +179,10 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
178
179
  };
179
180
 
180
181
  const blocks = output.content as Block[];
181
- const region = options.region || $env.KIRO_REGION || $env.AWS_REGION || $env.AWS_DEFAULT_REGION || DEFAULT_REGION;
182
+ const region = options.region ?? $env.KIRO_REGION ?? $env.AWS_REGION ?? $env.AWS_DEFAULT_REGION ?? DEFAULT_REGION;
182
183
 
183
184
  try {
185
+ assertAwsRegionLabel(region);
184
186
  // Resolve bearer token
185
187
  const bearerToken = resolveBearerToken(options.apiKey);
186
188
  if (!bearerToken) {
@@ -222,6 +224,7 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
222
224
  method: "POST",
223
225
  headers: requestHeaders,
224
226
  body,
227
+ redirect: "error",
225
228
  signal: options.signal,
226
229
  });
227
230
 
@@ -25,6 +25,12 @@ export declare function normalizeCodexToolChoice(choice: ToolChoice | undefined,
25
25
  export declare function formatCodexUserAgent(platform: string, release: string, arch: string): string;
26
26
  export declare const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses">;
27
27
  export declare function prewarmOpenAICodexResponses(model: Model<"openai-codex-responses">, options?: Pick<OpenAICodexResponsesOptions, "apiKey" | "headers" | "sessionId" | "signal" | "preferWebsockets" | "providerSessionState">): Promise<void>;
28
+ /**
29
+ * Bun 1.4.0's Windows WebSocket client can segfault during TLS handshakes.
30
+ * Keep the model's websocket preference available on other platforms, while
31
+ * requiring an explicit opt-in on Windows until the bundled runtime is fixed.
32
+ */
33
+ export declare function isCodexWebSocketSafeByDefault(platform?: NodeJS.Platform): boolean;
28
34
  export interface OpenAICodexTransportDetails {
29
35
  websocketPreferred: boolean;
30
36
  lastTransport?: CodexTransport;
@@ -2307,6 +2307,15 @@ function recordCodexWebSocketFailure(state: CodexWebSocketSessionState, activate
2307
2307
  }
2308
2308
  }
2309
2309
 
2310
+ /**
2311
+ * Bun 1.4.0's Windows WebSocket client can segfault during TLS handshakes.
2312
+ * Keep the model's websocket preference available on other platforms, while
2313
+ * requiring an explicit opt-in on Windows until the bundled runtime is fixed.
2314
+ */
2315
+ export function isCodexWebSocketSafeByDefault(platform: NodeJS.Platform = process.platform): boolean {
2316
+ return platform !== "win32";
2317
+ }
2318
+
2310
2319
  function shouldUseCodexWebSocket(
2311
2320
  model: Model<"openai-codex-responses">,
2312
2321
  state: CodexWebSocketSessionState | undefined,
@@ -2314,7 +2323,11 @@ function shouldUseCodexWebSocket(
2314
2323
  ): boolean {
2315
2324
  if (!state || state.disableWebsocket) return false;
2316
2325
  if (preferWebsockets === false) return false;
2317
- return isCodexWebSocketEnvEnabled() || preferWebsockets === true || model.preferWebsockets === true;
2326
+ return (
2327
+ isCodexWebSocketEnvEnabled() ||
2328
+ preferWebsockets === true ||
2329
+ (isCodexWebSocketSafeByDefault() && model.preferWebsockets === true)
2330
+ );
2318
2331
  }
2319
2332
 
2320
2333
  export interface OpenAICodexTransportDetails {
@@ -2372,7 +2385,9 @@ export function getOpenAICodexTransportDetails(
2372
2385
  const websocketPreferred =
2373
2386
  options?.preferWebsockets === false
2374
2387
  ? false
2375
- : isCodexWebSocketEnvEnabled() || options?.preferWebsockets === true || model.preferWebsockets === true;
2388
+ : isCodexWebSocketEnvEnabled() ||
2389
+ options?.preferWebsockets === true ||
2390
+ (isCodexWebSocketSafeByDefault() && model.preferWebsockets === true);
2376
2391
  const state = getCodexWebSocketStateForPublicSession(model, options);
2377
2392
 
2378
2393
  return {
@@ -49,6 +49,29 @@ const NON_WIRE_KEYS = new Set<keyof SimpleStreamOptions>([
49
49
  "fallbackAttempt",
50
50
  ]);
51
51
 
52
+ /**
53
+ * Project the caller's {@link Context} onto the wire schema. Runtime tool
54
+ * objects routinely carry harness-only state (runners, session managers,
55
+ * fs-stat BigInts) that must never be serialized: BigInt fields make
56
+ * `JSON.stringify` throw outright, and the rest is dead weight the gateway
57
+ * re-derives from its own tool registry. Only the protocol-meaningful,
58
+ * JSON-safe `Tool` fields cross the wire.
59
+ */
60
+ function buildWireContext(context: Context): Context {
61
+ if (!context.tools || context.tools.length === 0) return context;
62
+ return {
63
+ ...context,
64
+ tools: context.tools.map(tool => ({
65
+ name: tool.name,
66
+ description: tool.description,
67
+ parameters: tool.parameters,
68
+ ...(tool.strict !== undefined ? { strict: tool.strict } : {}),
69
+ ...(tool.customFormat !== undefined ? { customFormat: tool.customFormat } : {}),
70
+ ...(tool.customWireName !== undefined ? { customWireName: tool.customWireName } : {}),
71
+ })),
72
+ };
73
+ }
74
+
52
75
  function buildWireOptions(options: SimpleStreamOptions | undefined): Record<string, unknown> {
53
76
  if (!options) return {};
54
77
  const wire: Record<string, unknown> = {};
@@ -166,7 +189,7 @@ export function streamPiNative<TApi extends Api>(
166
189
  const headers = buildHeaders(model as Model<Api>, options?.apiKey);
167
190
  const body = JSON.stringify({
168
191
  modelId: model.id,
169
- context,
192
+ context: buildWireContext(context),
170
193
  options: buildWireOptions(options),
171
194
  stream: true,
172
195
  });
@@ -8,6 +8,7 @@
8
8
  * and AWS public documentation, not from any third-party reference.
9
9
  */
10
10
  import { scheduler } from "node:timers/promises";
11
+ import { assertAwsRegionLabel } from "../../adapter-internals/aws-region";
11
12
  import type { OAuthCredentials } from "./types";
12
13
 
13
14
  // ─────────────────────────────────────────────────────────────────────────────
@@ -72,23 +73,38 @@ interface CreateTokenError {
72
73
  error_uri?: string;
73
74
  }
74
75
 
76
+ interface CreateTokenResult {
77
+ response: Response;
78
+ status: number;
79
+ data: CreateTokenSuccess | CreateTokenError;
80
+ }
81
+
75
82
  // ─────────────────────────────────────────────────────────────────────────────
76
83
  // Typed SSO OIDC error names from the published service model
77
84
  // ─────────────────────────────────────────────────────────────────────────────
78
85
 
79
86
  const SSO_OIDC_FATAL_ERRORS = new Set([
80
87
  "access_denied_exception",
88
+ "access_denied",
81
89
  "expired_token_exception",
90
+ "expired_token",
82
91
  "internal_server_exception",
92
+ "server_error",
83
93
  "invalid_client_exception",
94
+ "invalid_client",
84
95
  "invalid_client_metadata_exception",
85
96
  "invalid_grant_exception",
97
+ "invalid_grant",
86
98
  "invalid_redirect_uri_exception",
87
99
  "invalid_request_exception",
100
+ "invalid_request",
88
101
  "invalid_request_region_exception",
89
102
  "invalid_scope_exception",
103
+ "invalid_scope",
90
104
  "unauthorized_client_exception",
105
+ "unauthorized_client",
91
106
  "unsupported_grant_type_exception",
107
+ "unsupported_grant_type",
92
108
  ]);
93
109
 
94
110
  // ─────────────────────────────────────────────────────────────────────────────
@@ -133,6 +149,7 @@ export async function registerClient(
133
149
  method: "POST",
134
150
  headers: { "Content-Type": "application/json" },
135
151
  body: JSON.stringify(body),
152
+ redirect: "error",
136
153
  signal,
137
154
  });
138
155
 
@@ -180,6 +197,7 @@ export async function startDeviceAuthorization(
180
197
  method: "POST",
181
198
  headers: { "Content-Type": "application/json" },
182
199
  body: JSON.stringify(body),
200
+ redirect: "error",
183
201
  signal,
184
202
  });
185
203
 
@@ -227,33 +245,54 @@ export async function pollForToken(
227
245
  deviceCode,
228
246
  };
229
247
 
230
- const response = await fetchOidc(url, {
231
- method: "POST",
232
- headers: { "Content-Type": "application/json" },
233
- body: JSON.stringify(body),
234
- signal,
235
- });
236
-
237
- const data = (await response.json()) as CreateTokenSuccess | CreateTokenError;
248
+ const requestSignal = AbortSignal.any([
249
+ ...(signal ? [signal] : []),
250
+ AbortSignal.timeout(Math.max(1, deadline - Date.now())),
251
+ ]);
252
+ let result: CreateTokenResult;
253
+ try {
254
+ result = await createTokenOnce(url, {
255
+ method: "POST",
256
+ headers: { "Content-Type": "application/json" },
257
+ body: JSON.stringify(body),
258
+ signal: requestSignal,
259
+ });
260
+ } catch (error) {
261
+ if (signal?.aborted) throw new Error("Login cancelled");
262
+ if (Date.now() >= deadline) break;
263
+ throw error;
264
+ }
238
265
 
239
- if ("accessToken" in data && typeof data.accessToken === "string") {
266
+ if (Date.now() >= deadline) break;
267
+ const { status, data } = result;
268
+
269
+ if ("accessToken" in data) {
270
+ if (
271
+ status < 200 ||
272
+ status >= 300 ||
273
+ "error" in data ||
274
+ data.accessToken.length === 0 ||
275
+ !Number.isFinite(data.expiresIn) ||
276
+ data.expiresIn <= 0
277
+ ) {
278
+ throw new Error("SSO OIDC CreateToken: invalid success response");
279
+ }
240
280
  return data;
241
281
  }
242
282
 
243
283
  if ("error" in data) {
284
+ if (status !== 400) throw new Error(oidcRequestFailure(url, result.response));
244
285
  const errorCode = data.error;
245
286
  if (errorCode === "authorization_pending") continue;
246
287
  if (errorCode === "slow_down") {
247
- currentInterval = Math.ceil(currentInterval * 1.5);
288
+ currentInterval += 5_000;
248
289
  continue;
249
290
  }
250
291
  if (SSO_OIDC_FATAL_ERRORS.has(errorCode)) {
251
- const desc = data.error_description ? `: ${data.error_description}` : "";
252
- throw new Error(`SSO OIDC token error: ${errorCode}${desc}`);
292
+ throw new Error(`SSO OIDC token error: ${errorCode}`);
253
293
  }
254
294
  // Unknown error — fail closed
255
- const desc = data.error_description ? `: ${data.error_description}` : "";
256
- throw new Error(`SSO OIDC unrecognized token error: ${errorCode}${desc}`);
295
+ throw new Error(`SSO OIDC unrecognized token error: ${errorCode}`);
257
296
  }
258
297
 
259
298
  throw new Error("SSO OIDC CreateToken: unrecognized response shape");
@@ -292,6 +331,7 @@ export async function refreshKiroToken(credentials: OAuthCredentials): Promise<O
292
331
  method: "POST",
293
332
  headers: { "Content-Type": "application/json" },
294
333
  body: JSON.stringify(body),
334
+ redirect: "error",
295
335
  });
296
336
 
297
337
  const data = (await response.json()) as CreateTokenSuccess | CreateTokenError;
@@ -430,19 +470,48 @@ export function importSsoCacheToken(): OAuthCredentials | undefined {
430
470
  // ─────────────────────────────────────────────────────────────────────────────
431
471
 
432
472
  function ssoOidcEndpoint(region: string, pathSuffix: string): string {
473
+ assertAwsRegionLabel(region);
433
474
  return `https://oidc.${region}.amazonaws.com${pathSuffix}`;
434
475
  }
435
476
 
436
477
  async function fetchOidc(url: string, init: RequestInit & { signal?: AbortSignal }): Promise<Response> {
437
- const response = await fetch(url, init);
478
+ const response = await fetch(url, { ...init, redirect: "error" });
438
479
  if (!response.ok) {
439
- let errorBody = "";
440
- try {
441
- errorBody = await response.text();
442
- } catch {}
443
- throw new Error(
444
- `SSO OIDC request to ${url} failed: ${response.status} ${response.statusText}: ${errorBody.slice(0, 500)}`,
445
- );
480
+ throw new Error(oidcRequestFailure(url, response));
446
481
  }
447
482
  return response;
448
483
  }
484
+
485
+ function oidcRequestFailure(url: string, response: Response): string {
486
+ return `SSO OIDC request to ${url} failed: ${response.status} ${response.statusText}`;
487
+ }
488
+
489
+ /**
490
+ * `CreateToken` reports the in-progress device-code states (`authorization_pending`,
491
+ * `slow_down`) as HTTP 400 responses whose body carries the error code, so the poll
492
+ * loop must read the payload instead of treating a non-2xx status as fatal.
493
+ * Non-2xx responses without an `error` field still fail closed.
494
+ */
495
+ async function createTokenOnce(url: string, init: RequestInit & { signal?: AbortSignal }): Promise<CreateTokenResult> {
496
+ const response = await fetch(url, { ...init, redirect: "error" });
497
+ const rawBody = await response.text();
498
+ let data: CreateTokenSuccess | CreateTokenError;
499
+ try {
500
+ data = JSON.parse(rawBody) as CreateTokenSuccess | CreateTokenError;
501
+ } catch {
502
+ throw new Error(oidcRequestFailure(url, response));
503
+ }
504
+ if (data === null || typeof data !== "object") {
505
+ throw new Error(oidcRequestFailure(url, response));
506
+ }
507
+ if (!response.ok && !("error" in data)) {
508
+ throw new Error(oidcRequestFailure(url, response));
509
+ }
510
+ if ("error" in data && typeof data.error !== "string") {
511
+ throw new Error(oidcRequestFailure(url, response));
512
+ }
513
+ if ("accessToken" in data && typeof data.accessToken !== "string") {
514
+ throw new Error(oidcRequestFailure(url, response));
515
+ }
516
+ return { response, status: response.status, data };
517
+ }