@vedmalex/ai-connect 0.11.0 → 0.13.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 (47) hide show
  1. package/README.md +54 -0
  2. package/dist/browser/index.js +120 -48
  3. package/dist/browser/index.js.map +2 -2
  4. package/dist/bun/index.js +157 -58
  5. package/dist/bun/index.js.map +2 -2
  6. package/dist/bun/local.js +157 -58
  7. package/dist/bun/local.js.map +2 -2
  8. package/dist/bun/registry-fs.js +1194 -0
  9. package/dist/bun/registry-fs.js.map +7 -0
  10. package/dist/node/index.js +157 -58
  11. package/dist/node/index.js.map +2 -2
  12. package/dist/node/local.js +157 -58
  13. package/dist/node/local.js.map +2 -2
  14. package/dist/node/registry-fs.js +1194 -0
  15. package/dist/node/registry-fs.js.map +7 -0
  16. package/dist/registry/index.js +1119 -0
  17. package/dist/registry/index.js.map +7 -0
  18. package/dist/types/acp.d.ts.map +1 -1
  19. package/dist/types/cli.d.ts.map +1 -1
  20. package/dist/types/config.d.ts.map +1 -1
  21. package/dist/types/default-handlers.d.ts.map +1 -1
  22. package/dist/types/mock-gateway.d.ts +20 -0
  23. package/dist/types/mock-gateway.d.ts.map +1 -1
  24. package/dist/types/registry/compile.d.ts +24 -0
  25. package/dist/types/registry/compile.d.ts.map +1 -0
  26. package/dist/types/registry/convert-v1.d.ts +21 -0
  27. package/dist/types/registry/convert-v1.d.ts.map +1 -0
  28. package/dist/types/registry/fs.d.ts +124 -0
  29. package/dist/types/registry/fs.d.ts.map +1 -0
  30. package/dist/types/registry/index.d.ts +15 -0
  31. package/dist/types/registry/index.d.ts.map +1 -0
  32. package/dist/types/registry/merge.d.ts +26 -0
  33. package/dist/types/registry/merge.d.ts.map +1 -0
  34. package/dist/types/registry/parse.d.ts +26 -0
  35. package/dist/types/registry/parse.d.ts.map +1 -0
  36. package/dist/types/registry/save.d.ts +96 -0
  37. package/dist/types/registry/save.d.ts.map +1 -0
  38. package/dist/types/registry/types.d.ts +168 -0
  39. package/dist/types/registry/types.d.ts.map +1 -0
  40. package/dist/types/router.d.ts.map +1 -1
  41. package/dist/types/server.d.ts.map +1 -1
  42. package/dist/types/transport-runtime.d.ts +30 -0
  43. package/dist/types/transport-runtime.d.ts.map +1 -1
  44. package/dist/types/types.d.ts +66 -2
  45. package/dist/types/types.d.ts.map +1 -1
  46. package/package.json +15 -2
  47. package/schema/connections.v2.json +395 -0
package/README.md CHANGED
@@ -611,6 +611,60 @@ Three error codes are intentionally **hard-terminal**: they never rotate, retry,
611
611
 
612
612
  All other normalized error codes (`rate_limit`, `quota_exhausted`, `temporary_unavailable`, etc.) remain eligible for the rotation/retry/fallback chain you configure under `routing.fallback`.
613
613
 
614
+ ## Connection Registry (`connections.json`)
615
+
616
+ Instead of hand-writing `defineConfig(...)`, you can declare your whole topology — endpoints,
617
+ aliases (ordered failover chains), auth, and defaults — in a single **v2 registry document** and
618
+ compile it into the exact config the client already consumes, plus ready-made `routeHints` per
619
+ alias. It ships as two subpaths: `@vedmalex/ai-connect/registry` (pure, browser-safe:
620
+ `parseRegistry` / `compileRegistry` / `convertV1Registry` / `mergeRegistries`) and
621
+ `@vedmalex/ai-connect/registry/fs` (node/bun loader: `loadConnectionRegistry` / `loadV1Pair`).
622
+
623
+ ```json
624
+ {
625
+ "$schema": "https://raw.githubusercontent.com/vedmalex/ai-connect/main/schema/connections.v2.json",
626
+ "version": 2,
627
+ "defaults": { "alias": "fast" },
628
+ "endpoints": [
629
+ {
630
+ "id": "openai-main",
631
+ "provider": "openai",
632
+ "transport": "api",
633
+ "baseUrl": "https://api.openai.com/v1",
634
+ "auth": { "tokenEnv": "OPENAI_API_KEY" },
635
+ "models": ["gpt-4.1"],
636
+ "defaultModel": "gpt-4.1"
637
+ }
638
+ ],
639
+ "aliases": [{ "id": "fast", "chain": [{ "endpointId": "openai-main" }] }]
640
+ }
641
+ ```
642
+
643
+ ```ts
644
+ import { loadConnectionRegistry } from "@vedmalex/ai-connect/registry/fs";
645
+ import { compileRegistry } from "@vedmalex/ai-connect/registry";
646
+ import { createLocalClient } from "@vedmalex/ai-connect";
647
+
648
+ const loaded = await loadConnectionRegistry(); // layered: user → project → AI_CONNECT_CONNECTIONS
649
+ const compiled = compileRegistry(loaded.registry, { env: (name) => process.env[name] });
650
+ const client = createLocalClient(compiled.config);
651
+
652
+ const fast = compiled.aliases["fast"];
653
+ if (!fast?.usable) throw new Error('alias "fast" is not usable'); // never dispatch on usable:false
654
+
655
+ const result = await client.generate({
656
+ messages: [{ role: "user", content: "Summarize this design brief." }],
657
+ routeHints: fast.routeHints, // ordered failover pool
658
+ });
659
+ console.log(result.text);
660
+ ```
661
+
662
+ The loader layers `~/.config/ai-connect/connections.json` (user) under
663
+ `<cwd>/.config/ai-connect/connections.json` (project) under `$AI_CONNECT_CONNECTIONS`, each with
664
+ a sibling `connections.secrets.json` overlay, and auto-converts legacy v1 (`rag-endpoints.json` +
665
+ `rag-aliases.json`) pairs. Full format, layering, failure-taxonomy, and v1-migration guide:
666
+ [docs/connection-registry.md](./docs/connection-registry.md).
667
+
614
668
  ## Cancellation, Pause, and Timeouts
615
669
 
616
670
  `generate(request, opts?)` and `stream(request, opts?)` accept an optional second argument:
@@ -964,6 +964,13 @@ function normalizeTransport(providerId, input) {
964
964
  methodId: descriptor.auth.methodId.trim(),
965
965
  params: descriptor.auth.params ?? {}
966
966
  } : void 0;
967
+ const normalizedHeaders = (() => {
968
+ if (!descriptor.headers) {
969
+ return void 0;
970
+ }
971
+ const entries = Object.entries(descriptor.headers).map(([key, value]) => [key.trim(), value]).filter(([key]) => key.length > 0);
972
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
973
+ })();
967
974
  assert(
968
975
  descriptor.kind === "api" || descriptor.kind === "acp" || descriptor.kind === "cli" || descriptor.kind === "server",
969
976
  `Unsupported transport kind for provider "${providerId}".`
@@ -973,23 +980,79 @@ function normalizeTransport(providerId, input) {
973
980
  !normalizedAuth,
974
981
  `Transport auth is only supported for ACP routes. Provider "${providerId}" declared auth on a non-ACP transport.`
975
982
  );
983
+ }
984
+ if (descriptor.kind === "api") {
976
985
  assert(
977
986
  !descriptor.launch,
978
- `Transport launch options are only supported for ACP routes. Provider "${providerId}" declared launch settings on a non-ACP transport.`
987
+ `Transport launch options are only supported for local (acp/cli/server) routes. Provider "${providerId}" declared launch settings on an api transport.`
979
988
  );
980
989
  }
990
+ const normalizeLaunch = () => {
991
+ const launchInput = descriptor.launch;
992
+ if (!launchInput) {
993
+ return void 0;
994
+ }
995
+ if (descriptor.kind !== "acp") {
996
+ assert(
997
+ launchInput.contextMode === void 0,
998
+ `Transport launch.contextMode is only supported for ACP routes. Provider "${providerId}" declared it on a ${descriptor.kind} transport.`
999
+ );
1000
+ assert(
1001
+ launchInput.skillsMode === void 0,
1002
+ `Transport launch.skillsMode is only supported for ACP routes. Provider "${providerId}" declared it on a ${descriptor.kind} transport.`
1003
+ );
1004
+ }
1005
+ const contextMode = launchInput.contextMode ?? "workspace";
1006
+ const skillsMode = launchInput.skillsMode ?? "default";
1007
+ assert(
1008
+ contextMode === "workspace" || contextMode === "clean",
1009
+ `Unsupported ACP contextMode "${String(contextMode)}" for provider "${providerId}".`
1010
+ );
1011
+ assert(
1012
+ skillsMode === "default" || skillsMode === "disabled",
1013
+ `Unsupported ACP skillsMode "${String(skillsMode)}" for provider "${providerId}".`
1014
+ );
1015
+ const env = (() => {
1016
+ if (!launchInput.env) {
1017
+ return void 0;
1018
+ }
1019
+ const entries = Object.entries(launchInput.env).map(([key, value]) => [key.trim(), value]).filter(([key]) => key.length > 0);
1020
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
1021
+ })();
1022
+ const envPassthrough = (() => {
1023
+ if (!launchInput.envPassthrough) {
1024
+ return void 0;
1025
+ }
1026
+ const names = launchInput.envPassthrough.map((name) => String(name).trim()).filter((name) => name.length > 0);
1027
+ return names.length > 0 ? names : void 0;
1028
+ })();
1029
+ return {
1030
+ contextMode,
1031
+ skillsMode,
1032
+ ...env ? { env } : {},
1033
+ ...envPassthrough ? { envPassthrough } : {}
1034
+ };
1035
+ };
1036
+ const normalizedLaunch = normalizeLaunch();
981
1037
  if (descriptor.kind !== "cli") {
982
1038
  assert(
983
1039
  !normalizedCli,
984
1040
  `CLI transport options are only supported for CLI routes. Provider "${providerId}" declared cli settings on a non-CLI transport.`
985
1041
  );
986
1042
  }
1043
+ if (descriptor.kind !== "api") {
1044
+ assert(
1045
+ !normalizedHeaders,
1046
+ `Transport headers are only supported for API routes. Provider "${providerId}" declared headers on a non-API transport.`
1047
+ );
1048
+ }
987
1049
  if (descriptor.kind === "api") {
988
1050
  return {
989
1051
  kind: "api",
990
1052
  id: descriptor.id?.trim() || "api",
991
1053
  ...descriptor.command?.trim() ? { command: descriptor.command.trim() } : {},
992
- ...descriptor.baseUrl?.trim() ? { baseUrl: descriptor.baseUrl.trim() } : {}
1054
+ ...descriptor.baseUrl?.trim() ? { baseUrl: descriptor.baseUrl.trim() } : {},
1055
+ ...normalizedHeaders ? { headers: normalizedHeaders } : {}
993
1056
  };
994
1057
  }
995
1058
  if (descriptor.kind === "cli") {
@@ -998,7 +1061,8 @@ function normalizeTransport(providerId, input) {
998
1061
  kind: "cli",
999
1062
  id: descriptor.id?.trim() || inferredId2,
1000
1063
  ...descriptor.command?.trim() ? { command: descriptor.command.trim() } : {},
1001
- ...normalizedCli ? { cli: normalizedCli } : {}
1064
+ ...normalizedCli ? { cli: normalizedCli } : {},
1065
+ ...normalizedLaunch ? { launch: normalizedLaunch } : {}
1002
1066
  };
1003
1067
  }
1004
1068
  if (descriptor.kind === "server") {
@@ -1007,26 +1071,11 @@ function normalizeTransport(providerId, input) {
1007
1071
  kind: "server",
1008
1072
  id: descriptor.id?.trim() || inferredId2,
1009
1073
  ...descriptor.command?.trim() ? { command: descriptor.command.trim() } : {},
1010
- ...descriptor.baseUrl?.trim() ? { baseUrl: descriptor.baseUrl.trim() } : {}
1074
+ ...descriptor.baseUrl?.trim() ? { baseUrl: descriptor.baseUrl.trim() } : {},
1075
+ ...normalizedLaunch ? { launch: normalizedLaunch } : {}
1011
1076
  };
1012
1077
  }
1013
1078
  const inferredId = ACP_TRANSPORT_ID_BY_PROVIDER[providerId] ?? "acp";
1014
- const normalizedLaunch = descriptor.launch ? (() => {
1015
- const contextMode = descriptor.launch?.contextMode ?? "workspace";
1016
- const skillsMode = descriptor.launch?.skillsMode ?? "default";
1017
- assert(
1018
- contextMode === "workspace" || contextMode === "clean",
1019
- `Unsupported ACP contextMode "${String(contextMode)}" for provider "${providerId}".`
1020
- );
1021
- assert(
1022
- skillsMode === "default" || skillsMode === "disabled",
1023
- `Unsupported ACP skillsMode "${String(skillsMode)}" for provider "${providerId}".`
1024
- );
1025
- return {
1026
- contextMode,
1027
- skillsMode
1028
- };
1029
- })() : void 0;
1030
1079
  return {
1031
1080
  kind: "acp",
1032
1081
  id: descriptor.id?.trim() || inferredId,
@@ -1245,12 +1294,16 @@ function normalizeOperationRouting(input, routes, defaultStrategy, resolution) {
1245
1294
  if (Array.isArray(input)) {
1246
1295
  return {
1247
1296
  strategy: defaultStrategy,
1248
- pool: resolveRouteRefs(input, routes, policy)
1297
+ pool: resolveRouteRefs(input, routes, policy),
1298
+ // WU-1: an explicit selector list was supplied iff it is non-empty (an empty list makes
1299
+ // resolveRouteRefs default to all routes — indistinguishable from "no pool given").
1300
+ explicitPool: input.length > 0
1249
1301
  };
1250
1302
  }
1251
1303
  return {
1252
1304
  strategy: input?.strategy ?? defaultStrategy,
1253
- pool: resolveRouteRefs(input?.pool, routes, policy)
1305
+ pool: resolveRouteRefs(input?.pool, routes, policy),
1306
+ explicitPool: Boolean(input?.pool && input.pool.length > 0)
1254
1307
  };
1255
1308
  }
1256
1309
  function finalizeRotationCapabilities(routes) {
@@ -2398,6 +2451,7 @@ var RouteRegistry = class {
2398
2451
  )
2399
2452
  ).map((route) => applyRequestedModel(route, requestedModel)).filter((route) => Boolean(route));
2400
2453
  const strategy = request.routeHints?.strategy ?? operationRouting.strategy;
2454
+ const orderedPool = Boolean(request.routeHints?.pool?.length) || operationRouting.explicitPool;
2401
2455
  const cursorKey = `${operation}:${strategy}:${eligible.map((route) => route.id).join("|")}`;
2402
2456
  const healthyIds = new Set(
2403
2457
  eligible.filter((route) => {
@@ -2410,7 +2464,8 @@ var RouteRegistry = class {
2410
2464
  eligible,
2411
2465
  healthyIds,
2412
2466
  request.routeHints?.preferredProviders,
2413
- cursorKey
2467
+ cursorKey,
2468
+ orderedPool
2414
2469
  );
2415
2470
  }
2416
2471
  recordSuccess(route) {
@@ -2443,9 +2498,10 @@ var RouteRegistry = class {
2443
2498
  failureCount
2444
2499
  });
2445
2500
  }
2446
- #order(strategy, eligible, healthyIds, preferredProviders, cursorKey) {
2447
- const sorted = baseSort(eligible, preferredProviders);
2448
- const orderedFull = this.config.routing.shuffleOnInit ? this.#shuffle(cursorKey, sorted) : sorted;
2501
+ #order(strategy, eligible, healthyIds, preferredProviders, cursorKey, orderedPool) {
2502
+ const honorPoolOrder = strategy === "failover" && orderedPool;
2503
+ const sorted = honorPoolOrder ? eligible : baseSort(eligible, preferredProviders);
2504
+ const orderedFull = !honorPoolOrder && this.config.routing.shuffleOnInit ? this.#shuffle(cursorKey, sorted) : sorted;
2449
2505
  const ordered = orderedFull.filter((route) => healthyIds.has(route.id));
2450
2506
  const strategyFn = ROUTING_STRATEGIES[strategy];
2451
2507
  return strategyFn(ordered, (key, length) => this.#cursor(key, length), cursorKey);
@@ -5343,6 +5399,22 @@ function extractImagePayloads(...nodes) {
5343
5399
 
5344
5400
  // src/default-handlers.ts
5345
5401
  var INLINE_ATTACHMENT_TEXT_LIMIT = 16e3;
5402
+ function mergeRouteHeaders(route, builtin) {
5403
+ const routeHeaders = route.transport.headers;
5404
+ if (!routeHeaders || Object.keys(routeHeaders).length === 0) {
5405
+ return builtin;
5406
+ }
5407
+ const builtinKeys = new Set(
5408
+ Object.keys(builtin).map((key) => key.toLowerCase())
5409
+ );
5410
+ const passthrough = {};
5411
+ for (const [key, value] of Object.entries(routeHeaders)) {
5412
+ if (!builtinKeys.has(key.toLowerCase())) {
5413
+ passthrough[key] = value;
5414
+ }
5415
+ }
5416
+ return { ...passthrough, ...builtin };
5417
+ }
5346
5418
  function trimTrailingSlash(value) {
5347
5419
  return value.replace(/\/+$/, "");
5348
5420
  }
@@ -5967,9 +6039,9 @@ async function discoverOpenAiModels(fetchImpl, context) {
5967
6039
  discoveryTransportSummary(context, "openai-models", endpoint, "GET");
5968
6040
  const response = await fetchImpl(endpoint, {
5969
6041
  method: "GET",
5970
- headers: {
6042
+ headers: mergeRouteHeaders(context.route, {
5971
6043
  authorization: `Bearer ${apiKey}`
5972
- },
6044
+ }),
5973
6045
  signal: context.abort.signal
5974
6046
  });
5975
6047
  const payload = await readPayload(response);
@@ -5984,10 +6056,10 @@ async function discoverAnthropicModels(fetchImpl, context) {
5984
6056
  discoveryTransportSummary(context, "anthropic-models", endpoint, "GET");
5985
6057
  const response = await fetchImpl(endpoint, {
5986
6058
  method: "GET",
5987
- headers: {
6059
+ headers: mergeRouteHeaders(context.route, {
5988
6060
  "x-api-key": apiKey,
5989
6061
  "anthropic-version": "2023-06-01"
5990
- },
6062
+ }),
5991
6063
  signal: context.abort.signal
5992
6064
  });
5993
6065
  const payload = await readPayload(response);
@@ -6002,7 +6074,7 @@ async function discoverGeminiModels(fetchImpl, context) {
6002
6074
  discoveryTransportSummary(context, "gemini-models", endpoint, "GET");
6003
6075
  const response = await fetchImpl(endpoint, {
6004
6076
  method: "GET",
6005
- headers: geminiApiKeyHeaders(apiKey),
6077
+ headers: mergeRouteHeaders(context.route, geminiApiKeyHeaders(apiKey)),
6006
6078
  signal: context.abort.signal
6007
6079
  });
6008
6080
  const payload = await readPayload(response);
@@ -6215,11 +6287,11 @@ async function uploadAnthropicFile(payload, uploader) {
6215
6287
  );
6216
6288
  const response = await uploader.fetchImpl(endpoint, {
6217
6289
  method: "POST",
6218
- headers: {
6290
+ headers: mergeRouteHeaders(uploader.context.route, {
6219
6291
  "x-api-key": uploader.apiKey,
6220
6292
  "anthropic-version": "2023-06-01",
6221
6293
  "anthropic-beta": "files-api-2025-04-14"
6222
- },
6294
+ }),
6223
6295
  body: form,
6224
6296
  signal: uploader.context.abort.signal
6225
6297
  });
@@ -6253,9 +6325,9 @@ async function uploadOpenAiFile(payload, uploader) {
6253
6325
  form.append("purpose", "user_data");
6254
6326
  const response = await uploader.fetchImpl(endpoint, {
6255
6327
  method: "POST",
6256
- headers: {
6328
+ headers: mergeRouteHeaders(uploader.context.route, {
6257
6329
  authorization: `Bearer ${uploader.apiKey}`
6258
- },
6330
+ }),
6259
6331
  body: form,
6260
6332
  signal: uploader.context.abort.signal
6261
6333
  });
@@ -6285,12 +6357,12 @@ async function uploadGeminiFile(payload, uploader) {
6285
6357
  ) : "https://generativelanguage.googleapis.com/upload/v1beta/files";
6286
6358
  const response = await uploader.fetchImpl(endpoint, {
6287
6359
  method: "POST",
6288
- headers: {
6360
+ headers: mergeRouteHeaders(uploader.context.route, {
6289
6361
  ...geminiApiKeyHeaders(uploader.apiKey),
6290
6362
  "X-Goog-Upload-Protocol": "raw",
6291
6363
  "X-Goog-Upload-Header-Content-Type": payload.mimeType,
6292
6364
  "content-type": payload.mimeType
6293
- },
6365
+ }),
6294
6366
  body: base64ToBlob(payload.base64, payload.mimeType),
6295
6367
  signal: uploader.context.abort.signal
6296
6368
  });
@@ -6872,10 +6944,10 @@ async function runOpenAi(fetchImpl, context) {
6872
6944
  transportSummary(context, "openai-chat", endpoint, "POST", false, body);
6873
6945
  const response = await fetchImpl(endpoint, {
6874
6946
  method: "POST",
6875
- headers: {
6947
+ headers: mergeRouteHeaders(context.route, {
6876
6948
  "content-type": "application/json",
6877
6949
  authorization: `Bearer ${apiKey}`
6878
- },
6950
+ }),
6879
6951
  body: JSON.stringify(body),
6880
6952
  signal: context.abort.signal
6881
6953
  });
@@ -6954,10 +7026,10 @@ async function* runOpenAiStream(fetchImpl, context) {
6954
7026
  transportSummary(context, "openai-chat", endpoint, "POST", true, body);
6955
7027
  const response2 = await fetchImpl(endpoint, {
6956
7028
  method: "POST",
6957
- headers: {
7029
+ headers: mergeRouteHeaders(context.route, {
6958
7030
  "content-type": "application/json",
6959
7031
  authorization: `Bearer ${apiKey}`
6960
- },
7032
+ }),
6961
7033
  body: JSON.stringify(body),
6962
7034
  signal: context.abort.signal
6963
7035
  });
@@ -7162,15 +7234,15 @@ async function runProviderImage(fetchImpl, context, opts) {
7162
7234
  );
7163
7235
  const response = await fetchImpl(endpoint, useEditEndpoint ? {
7164
7236
  method: "POST",
7165
- headers: { ...opts.authHeaders },
7237
+ headers: mergeRouteHeaders(context.route, { ...opts.authHeaders }),
7166
7238
  body: await buildImageEditFormData(context, baseFields),
7167
7239
  signal: context.abort.signal
7168
7240
  } : {
7169
7241
  method: "POST",
7170
- headers: {
7242
+ headers: mergeRouteHeaders(context.route, {
7171
7243
  "content-type": "application/json",
7172
7244
  ...opts.authHeaders
7173
- },
7245
+ }),
7174
7246
  body: JSON.stringify(baseFields),
7175
7247
  signal: context.abort.signal
7176
7248
  });
@@ -7225,11 +7297,11 @@ async function runAnthropic(fetchImpl, context) {
7225
7297
  transportSummary(context, "anthropic-messages", endpoint, "POST", false, requestBody);
7226
7298
  const response = await fetchImpl(endpoint, {
7227
7299
  method: "POST",
7228
- headers: {
7300
+ headers: mergeRouteHeaders(context.route, {
7229
7301
  "content-type": "application/json",
7230
7302
  "x-api-key": apiKey,
7231
7303
  "anthropic-version": "2023-06-01"
7232
- },
7304
+ }),
7233
7305
  body: JSON.stringify(requestBody),
7234
7306
  signal: context.abort.signal
7235
7307
  });
@@ -7294,10 +7366,10 @@ async function runGemini(fetchImpl, context) {
7294
7366
  transportSummary(context, "gemini-generate-content", endpoint, "POST", false, requestBody);
7295
7367
  const response = await fetchImpl(endpoint, {
7296
7368
  method: "POST",
7297
- headers: {
7369
+ headers: mergeRouteHeaders(context.route, {
7298
7370
  ...geminiApiKeyHeaders(apiKey),
7299
7371
  "content-type": "application/json"
7300
- },
7372
+ }),
7301
7373
  body: JSON.stringify(requestBody),
7302
7374
  signal: context.abort.signal
7303
7375
  });