@x12i/ai-dispatcher 1.10.0 → 2.1.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@ One request shape for OpenRouter, AWS Bedrock, the OpenAI Responses API, and Clo
4
4
 
5
5
  **Implemented providers:** `openrouter`, `bedrock`, `openai`, `cloudflare`. Any other provider throws `PROVIDER_NOT_IMPLEMENTED`.
6
6
 
7
- The package is `@x12i/ai-dispatcher` 1.10.0. It runs on Node 20 or newer and publishes ESM and CommonJS from the same entry.
7
+ The package is `@x12i/ai-dispatcher` 2.1.0. It runs on Node 20 or newer and publishes ESM and CommonJS from the same entry.
8
8
 
9
9
  Connected MCP servers can be passed to `createAiDispatcher`. `run()` and `compile()` expose those tools to the model. `executeStreamingChat` does not. Details are in [MCP tools](#mcp-tools).
10
10
 
@@ -14,7 +14,7 @@ Connected MCP servers can be passed to `createAiDispatcher`. `run()` and `compil
14
14
  npm install @x12i/ai-dispatcher
15
15
  ```
16
16
 
17
- The package depends on `@x12i/ai-profiles@5.0.0`, `@x12i/openrouter-runtime@^1.4.0`, and `@x12i/bedrock-runtime@^1.2.0`.
17
+ The package depends on `@x12i/ai-profiles@5.0.0`, `@x12i/provider-metadata@1.1.0`, `@x12i/openrouter-runtime@^2.1.0`, and `@x12i/bedrock-runtime@^2.1.0`.
18
18
 
19
19
  ## One request
20
20
 
@@ -177,7 +177,7 @@ const record = decodeProviderMetadata(logMetadata);
177
177
 
178
178
  `metadata` is the one record. When `rawOpenRouterOverrides` only repeats `metadata`, the dispatcher folds it in. `metadata` wins when the same key is in both places. String, number, and boolean values are stored, including a JSON string. Objects and arrays stay on the normalized response and the tool context.
179
179
 
180
- Every provider writes that record as `m.0`, `m.1`, and so on: one base64 string, 256 characters per piece, at most 16 pieces. The base64 is the UTF-8 JSON object of the string map, with keys sorted. `decodeProviderMetadata` reads those pieces from OpenRouter body `metadata`, OpenAI Responses `metadata`, Bedrock `requestMetadata`, or Cloudflare body `metadata` and `cf-aig-metadata`. The pieces are the same on every provider. A larger record throws `METADATA_RECORD_TOO_LARGE`.
180
+ Every provider writes that record as `m.0`, `m.1`, and so on: one base64 string, 256 characters per piece, at most 16 pieces. The base64 is the UTF-8 JSON object of the string map, with keys sorted. `decodeProviderMetadata` reads those pieces from OpenRouter body `metadata`, OpenAI Responses `metadata`, Bedrock `requestMetadata`, or Cloudflare body `metadata` and `cf-aig-metadata`. The pieces are the same on every provider. The dispatcher trims on every call. Pass `metadataPriority` (highest priority first) to choose what stays. The default keeps `agentId`, `orgId`, `stepId`, `skillId`, `recordId`, and `objectType`. A trimmed call adds a `METADATA_RECORD_TRIMMED` warning.
181
181
 
182
182
  OpenRouter also puts `agentId` in `X-Title` and `X-OpenRouter-Title`, ahead of `appAttribution.appName` and `defaultHeaders`. That header is the activity-log App name. `appAttribution.siteUrl` is still `HTTP-Referer` when set. The App header is not a second metadata record.
183
183
 
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AiDispatcherError: () => AiDispatcherError,
24
+ DEFAULT_METADATA_PRIORITY: () => DEFAULT_METADATA_PRIORITY,
24
25
  IMPLEMENTED_AI_PROVIDERS: () => IMPLEMENTED_AI_PROVIDERS,
25
26
  createAiDispatcher: () => createAiDispatcher,
26
27
  decodeProviderMetadata: () => import_openrouter_runtime4.decodeProviderMetadata,
@@ -32,6 +33,9 @@ __export(index_exports, {
32
33
  module.exports = __toCommonJS(index_exports);
33
34
  var import_openrouter_runtime4 = require("@x12i/openrouter-runtime");
34
35
 
36
+ // src/create-dispatcher.ts
37
+ var import_provider_metadata = require("@x12i/provider-metadata");
38
+
35
39
  // src/cache-usage.ts
36
40
  function readCacheTokens(source) {
37
41
  const cached = [];
@@ -1178,8 +1182,8 @@ function restoreCloudflareModel(model, prefix) {
1178
1182
  if (!prefix || model.includes("/")) return model;
1179
1183
  return `${prefix}${model}`;
1180
1184
  }
1181
- function createCloudflareProviderAdapter(options = {}) {
1182
- const resolved = resolveCloudflareOptions(options);
1185
+ function createCloudflareProviderAdapter(options = {}, packer) {
1186
+ const resolved = resolveCloudflareOptions(options, packer);
1183
1187
  return {
1184
1188
  compile(request, call) {
1185
1189
  return compileCloudflareRequest(request, resolved, call, false);
@@ -1232,12 +1236,13 @@ function compileCloudflareRequest(request, options, call, streaming) {
1232
1236
  if (endpoint !== "run") body = { ...body, stream: streaming };
1233
1237
  const gateway = mergeGateway(options.gateway, call?.cloudflare?.gateway);
1234
1238
  const fromBody = isRecord5(body.metadata) ? body.metadata : void 0;
1235
- const record2 = (0, import_openrouter_runtime.toProviderMetadata)(
1239
+ const packed = (0, import_openrouter_runtime.packProviderRecord)(
1236
1240
  request.metadata,
1237
1241
  request.agentId,
1238
- import_openrouter_runtime.OPENROUTER_METADATA_LIMITS,
1239
- overlayMetadata(gateway?.metadata, fromBody)
1242
+ overlayMetadata(gateway?.metadata, fromBody),
1243
+ options.metadataPacker
1240
1244
  );
1245
+ const record2 = packed.wire;
1241
1246
  body = applyProviderRecord(body, record2);
1242
1247
  if (endpoint === "run") body = wrapRun(body, run);
1243
1248
  return {
@@ -1245,7 +1250,7 @@ function compileCloudflareRequest(request, options, call, streaming) {
1245
1250
  url: endpointUrl(options, endpoint),
1246
1251
  headers: buildHeaders(options, call?.cloudflare, model, record2),
1247
1252
  body,
1248
- warnings: []
1253
+ warnings: packed.warning ? [packed.warning] : []
1249
1254
  };
1250
1255
  }
1251
1256
  function compileResponsesBody(request, model) {
@@ -2294,7 +2299,7 @@ function parseSsePart(part) {
2294
2299
  return void 0;
2295
2300
  }
2296
2301
  }
2297
- function resolveCloudflareOptions(options) {
2302
+ function resolveCloudflareOptions(options, packer) {
2298
2303
  const {
2299
2304
  apiToken,
2300
2305
  accountId,
@@ -2323,7 +2328,8 @@ function resolveCloudflareOptions(options) {
2323
2328
  maxAttempts: maxAttempts ?? 2,
2324
2329
  fetch: fetch ?? globalThis.fetch,
2325
2330
  ...tools ? { tools } : {},
2326
- ...logger ? { logger } : {}
2331
+ ...logger ? { logger } : {},
2332
+ ...packer ? { metadataPacker: packer } : {}
2327
2333
  };
2328
2334
  }
2329
2335
  function hasGateway(gateway) {
@@ -2838,8 +2844,8 @@ function isBedrockToolChoice(toolChoice) {
2838
2844
  var import_openrouter_runtime2 = require("@x12i/openrouter-runtime");
2839
2845
  var DEFAULT_BASE_URL2 = "https://api.openai.com/v1";
2840
2846
  var DEFAULT_TIMEOUT_MS2 = 12e4;
2841
- function createOpenAiProviderAdapter(options = {}) {
2842
- const resolved = resolveOpenAiOptions(options);
2847
+ function createOpenAiProviderAdapter(options = {}, packer) {
2848
+ const resolved = resolveOpenAiOptions(options, packer);
2843
2849
  return {
2844
2850
  compile(request, call) {
2845
2851
  return compileOpenAiRequest(request, resolved, call, false);
@@ -2892,12 +2898,13 @@ function compileOpenAiRequest(request, options, call, streaming) {
2892
2898
  if (call?.finalizeBody) body = call.finalizeBody(body);
2893
2899
  if (streaming) body = { ...body, stream: true };
2894
2900
  else body = { ...body, stream: false };
2895
- body = attachProviderMetadata(body, request);
2901
+ const packed = attachProviderMetadata(body, request, options.metadataPacker);
2902
+ body = packed.body;
2896
2903
  return {
2897
2904
  url: `${options.baseUrl}/responses`,
2898
2905
  headers: buildHeaders2(options),
2899
2906
  body,
2900
- warnings: []
2907
+ warnings: packed.warning ? [packed.warning] : []
2901
2908
  };
2902
2909
  }
2903
2910
  function assertSupported2(request) {
@@ -3397,14 +3404,14 @@ function messageFrom2(body) {
3397
3404
  function numberValue2(value) {
3398
3405
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3399
3406
  }
3400
- function attachProviderMetadata(body, request) {
3407
+ function attachProviderMetadata(body, request, packer) {
3401
3408
  const fromBody = isRecord7(body.metadata) ? body.metadata : void 0;
3402
- const metadata = (0, import_openrouter_runtime2.toProviderMetadata)(request.metadata, request.agentId, import_openrouter_runtime2.OPENROUTER_METADATA_LIMITS, fromBody);
3403
- if (metadata) return { ...body, metadata };
3404
- if (body.metadata === void 0) return body;
3409
+ const packed = (0, import_openrouter_runtime2.packProviderRecord)(request.metadata, request.agentId, fromBody, packer);
3410
+ if (packed.wire) return { body: { ...body, metadata: packed.wire }, ...packed.warning ? { warning: packed.warning } : {} };
3411
+ if (body.metadata === void 0) return { body, ...packed.warning ? { warning: packed.warning } : {} };
3405
3412
  const rest = { ...body };
3406
3413
  delete rest.metadata;
3407
- return rest;
3414
+ return { body: rest, ...packed.warning ? { warning: packed.warning } : {} };
3408
3415
  }
3409
3416
  function clean2(body) {
3410
3417
  return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== void 0));
@@ -3452,7 +3459,7 @@ function parseSsePart2(part) {
3452
3459
  return void 0;
3453
3460
  }
3454
3461
  }
3455
- function resolveOpenAiOptions(options) {
3462
+ function resolveOpenAiOptions(options, packer) {
3456
3463
  const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
3457
3464
  return {
3458
3465
  apiKey: options.apiKey ?? readEnv3("OPENAI_API_KEY") ?? "",
@@ -3464,7 +3471,8 @@ function resolveOpenAiOptions(options) {
3464
3471
  maxAttempts: options.maxAttempts ?? 3,
3465
3472
  fetch: options.fetch ?? globalThis.fetch,
3466
3473
  ...options.tools ? { tools: options.tools } : {},
3467
- ...options.logger ? { logger: options.logger } : {}
3474
+ ...options.logger ? { logger: options.logger } : {},
3475
+ ...packer ? { metadataPacker: packer } : {}
3468
3476
  };
3469
3477
  }
3470
3478
  function readEnv3(name) {
@@ -3517,7 +3525,9 @@ function resolveProvider(requestProvider, defaultProvider) {
3517
3525
 
3518
3526
  // src/create-dispatcher.ts
3519
3527
  var SECRET_HEADERS = /* @__PURE__ */ new Set(["authorization", "x-api-key", "api-key"]);
3528
+ var DEFAULT_METADATA_PRIORITY = ["agentId", "orgId", "stepId", "skillId", "recordId", "objectType"];
3520
3529
  function createAiDispatcher(options = {}) {
3530
+ const packer = (0, import_provider_metadata.createMetadataPacker)({ priority: options.metadataPriority ?? DEFAULT_METADATA_PRIORITY });
3521
3531
  const defaultProvider = resolveProvider(options.provider, "openrouter");
3522
3532
  const catalog = createMcpCatalog(options.mcp);
3523
3533
  let refreshChain = Promise.resolve();
@@ -3526,6 +3536,7 @@ function createAiDispatcher(options = {}) {
3526
3536
  if (provider === "openrouter") {
3527
3537
  const openrouterOptions = {
3528
3538
  ...options.openrouter,
3539
+ metadataPacker: packer,
3529
3540
  ...options.logger && !options.openrouter?.logger ? { logger: options.logger } : {}
3530
3541
  };
3531
3542
  return createOpenRouterProviderAdapter(openrouterOptions);
@@ -3533,6 +3544,7 @@ function createAiDispatcher(options = {}) {
3533
3544
  if (provider === "bedrock") {
3534
3545
  const bedrockOptions = {
3535
3546
  ...options.bedrock,
3547
+ metadataPacker: packer,
3536
3548
  ...options.logger && !options.bedrock?.logger ? { logger: options.logger } : {}
3537
3549
  };
3538
3550
  return createBedrockProviderAdapter(bedrockOptions);
@@ -3542,14 +3554,14 @@ function createAiDispatcher(options = {}) {
3542
3554
  ...options.openai,
3543
3555
  ...options.logger && !options.openai?.logger ? { logger: options.logger } : {}
3544
3556
  };
3545
- return createOpenAiProviderAdapter(openaiOptions);
3557
+ return createOpenAiProviderAdapter(openaiOptions, packer);
3546
3558
  }
3547
3559
  if (provider === "cloudflare") {
3548
3560
  const cloudflareOptions = {
3549
3561
  ...options.cloudflare,
3550
3562
  ...options.logger && !options.cloudflare?.logger ? { logger: options.logger } : {}
3551
3563
  };
3552
- return createCloudflareProviderAdapter(cloudflareOptions);
3564
+ return createCloudflareProviderAdapter(cloudflareOptions, packer);
3553
3565
  }
3554
3566
  throw new AiDispatcherError(
3555
3567
  "PROVIDER_ADAPTER_MISSING",
@@ -3670,6 +3682,7 @@ function stripReasoningHistoryFields(message, paths) {
3670
3682
  // Annotate the CommonJS export names for ESM import in node:
3671
3683
  0 && (module.exports = {
3672
3684
  AiDispatcherError,
3685
+ DEFAULT_METADATA_PRIORITY,
3673
3686
  IMPLEMENTED_AI_PROVIDERS,
3674
3687
  createAiDispatcher,
3675
3688
  decodeProviderMetadata,