@x12i/ai-dispatcher 2.0.0 → 2.2.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 +5 -3
- package/dist/index.cjs +40 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +44 -26
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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` 2.
|
|
7
|
+
The package is `@x12i/ai-dispatcher` 2.2.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@^2.
|
|
17
|
+
The package depends on `@x12i/ai-profiles@5.0.0`, `@x12i/provider-metadata@^1.2.0`, `@x12i/openrouter-runtime@^2.2.0`, and `@x12i/bedrock-runtime@^2.2.0`.
|
|
18
18
|
|
|
19
19
|
## One request
|
|
20
20
|
|
|
@@ -177,7 +177,9 @@ 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
|
|
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
|
+
|
|
182
|
+
Repeating vendor fields normalize to one key. Prefer `metadata.api_key_name` over `metadata.openrouter.api_key_name`. `normalizeMetadata` and `decodeProviderMetadata` promote known aliases and keep the vendor key. Grow `METADATA_NORMALIZATION_MAP` as more shared fields appear.
|
|
181
183
|
|
|
182
184
|
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
185
|
|
package/dist/index.cjs
CHANGED
|
@@ -21,10 +21,14 @@ 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,
|
|
26
|
+
METADATA_NORMALIZATION_MAP: () => import_openrouter_runtime4.METADATA_NORMALIZATION_MAP,
|
|
27
|
+
canonicalMetadataKey: () => import_openrouter_runtime4.canonicalMetadataKey,
|
|
25
28
|
createAiDispatcher: () => createAiDispatcher,
|
|
26
29
|
decodeProviderMetadata: () => import_openrouter_runtime4.decodeProviderMetadata,
|
|
27
30
|
isImplementedAiProvider: () => isImplementedAiProvider,
|
|
31
|
+
normalizeMetadata: () => import_openrouter_runtime4.normalizeMetadata,
|
|
28
32
|
providerNotImplemented: () => providerNotImplemented,
|
|
29
33
|
resolveProvider: () => resolveProvider,
|
|
30
34
|
stripReasoningHistoryFields: () => stripReasoningHistoryFields
|
|
@@ -32,6 +36,9 @@ __export(index_exports, {
|
|
|
32
36
|
module.exports = __toCommonJS(index_exports);
|
|
33
37
|
var import_openrouter_runtime4 = require("@x12i/openrouter-runtime");
|
|
34
38
|
|
|
39
|
+
// src/create-dispatcher.ts
|
|
40
|
+
var import_provider_metadata = require("@x12i/provider-metadata");
|
|
41
|
+
|
|
35
42
|
// src/cache-usage.ts
|
|
36
43
|
function readCacheTokens(source) {
|
|
37
44
|
const cached = [];
|
|
@@ -1178,8 +1185,8 @@ function restoreCloudflareModel(model, prefix) {
|
|
|
1178
1185
|
if (!prefix || model.includes("/")) return model;
|
|
1179
1186
|
return `${prefix}${model}`;
|
|
1180
1187
|
}
|
|
1181
|
-
function createCloudflareProviderAdapter(options = {}) {
|
|
1182
|
-
const resolved = resolveCloudflareOptions(options);
|
|
1188
|
+
function createCloudflareProviderAdapter(options = {}, packer) {
|
|
1189
|
+
const resolved = resolveCloudflareOptions(options, packer);
|
|
1183
1190
|
return {
|
|
1184
1191
|
compile(request, call) {
|
|
1185
1192
|
return compileCloudflareRequest(request, resolved, call, false);
|
|
@@ -1232,12 +1239,13 @@ function compileCloudflareRequest(request, options, call, streaming) {
|
|
|
1232
1239
|
if (endpoint !== "run") body = { ...body, stream: streaming };
|
|
1233
1240
|
const gateway = mergeGateway(options.gateway, call?.cloudflare?.gateway);
|
|
1234
1241
|
const fromBody = isRecord5(body.metadata) ? body.metadata : void 0;
|
|
1235
|
-
const
|
|
1242
|
+
const packed = (0, import_openrouter_runtime.packProviderRecord)(
|
|
1236
1243
|
request.metadata,
|
|
1237
1244
|
request.agentId,
|
|
1238
|
-
|
|
1239
|
-
|
|
1245
|
+
overlayMetadata(gateway?.metadata, fromBody),
|
|
1246
|
+
options.metadataPacker
|
|
1240
1247
|
);
|
|
1248
|
+
const record2 = packed.wire;
|
|
1241
1249
|
body = applyProviderRecord(body, record2);
|
|
1242
1250
|
if (endpoint === "run") body = wrapRun(body, run);
|
|
1243
1251
|
return {
|
|
@@ -1245,7 +1253,7 @@ function compileCloudflareRequest(request, options, call, streaming) {
|
|
|
1245
1253
|
url: endpointUrl(options, endpoint),
|
|
1246
1254
|
headers: buildHeaders(options, call?.cloudflare, model, record2),
|
|
1247
1255
|
body,
|
|
1248
|
-
warnings: []
|
|
1256
|
+
warnings: packed.warning ? [packed.warning] : []
|
|
1249
1257
|
};
|
|
1250
1258
|
}
|
|
1251
1259
|
function compileResponsesBody(request, model) {
|
|
@@ -2294,7 +2302,7 @@ function parseSsePart(part) {
|
|
|
2294
2302
|
return void 0;
|
|
2295
2303
|
}
|
|
2296
2304
|
}
|
|
2297
|
-
function resolveCloudflareOptions(options) {
|
|
2305
|
+
function resolveCloudflareOptions(options, packer) {
|
|
2298
2306
|
const {
|
|
2299
2307
|
apiToken,
|
|
2300
2308
|
accountId,
|
|
@@ -2323,7 +2331,8 @@ function resolveCloudflareOptions(options) {
|
|
|
2323
2331
|
maxAttempts: maxAttempts ?? 2,
|
|
2324
2332
|
fetch: fetch ?? globalThis.fetch,
|
|
2325
2333
|
...tools ? { tools } : {},
|
|
2326
|
-
...logger ? { logger } : {}
|
|
2334
|
+
...logger ? { logger } : {},
|
|
2335
|
+
...packer ? { metadataPacker: packer } : {}
|
|
2327
2336
|
};
|
|
2328
2337
|
}
|
|
2329
2338
|
function hasGateway(gateway) {
|
|
@@ -2838,8 +2847,8 @@ function isBedrockToolChoice(toolChoice) {
|
|
|
2838
2847
|
var import_openrouter_runtime2 = require("@x12i/openrouter-runtime");
|
|
2839
2848
|
var DEFAULT_BASE_URL2 = "https://api.openai.com/v1";
|
|
2840
2849
|
var DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
2841
|
-
function createOpenAiProviderAdapter(options = {}) {
|
|
2842
|
-
const resolved = resolveOpenAiOptions(options);
|
|
2850
|
+
function createOpenAiProviderAdapter(options = {}, packer) {
|
|
2851
|
+
const resolved = resolveOpenAiOptions(options, packer);
|
|
2843
2852
|
return {
|
|
2844
2853
|
compile(request, call) {
|
|
2845
2854
|
return compileOpenAiRequest(request, resolved, call, false);
|
|
@@ -2892,12 +2901,13 @@ function compileOpenAiRequest(request, options, call, streaming) {
|
|
|
2892
2901
|
if (call?.finalizeBody) body = call.finalizeBody(body);
|
|
2893
2902
|
if (streaming) body = { ...body, stream: true };
|
|
2894
2903
|
else body = { ...body, stream: false };
|
|
2895
|
-
|
|
2904
|
+
const packed = attachProviderMetadata(body, request, options.metadataPacker);
|
|
2905
|
+
body = packed.body;
|
|
2896
2906
|
return {
|
|
2897
2907
|
url: `${options.baseUrl}/responses`,
|
|
2898
2908
|
headers: buildHeaders2(options),
|
|
2899
2909
|
body,
|
|
2900
|
-
warnings: []
|
|
2910
|
+
warnings: packed.warning ? [packed.warning] : []
|
|
2901
2911
|
};
|
|
2902
2912
|
}
|
|
2903
2913
|
function assertSupported2(request) {
|
|
@@ -3397,14 +3407,14 @@ function messageFrom2(body) {
|
|
|
3397
3407
|
function numberValue2(value) {
|
|
3398
3408
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3399
3409
|
}
|
|
3400
|
-
function attachProviderMetadata(body, request) {
|
|
3410
|
+
function attachProviderMetadata(body, request, packer) {
|
|
3401
3411
|
const fromBody = isRecord7(body.metadata) ? body.metadata : void 0;
|
|
3402
|
-
const
|
|
3403
|
-
if (
|
|
3404
|
-
if (body.metadata === void 0) return body;
|
|
3412
|
+
const packed = (0, import_openrouter_runtime2.packProviderRecord)(request.metadata, request.agentId, fromBody, packer);
|
|
3413
|
+
if (packed.wire) return { body: { ...body, metadata: packed.wire }, ...packed.warning ? { warning: packed.warning } : {} };
|
|
3414
|
+
if (body.metadata === void 0) return { body, ...packed.warning ? { warning: packed.warning } : {} };
|
|
3405
3415
|
const rest = { ...body };
|
|
3406
3416
|
delete rest.metadata;
|
|
3407
|
-
return rest;
|
|
3417
|
+
return { body: rest, ...packed.warning ? { warning: packed.warning } : {} };
|
|
3408
3418
|
}
|
|
3409
3419
|
function clean2(body) {
|
|
3410
3420
|
return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== void 0));
|
|
@@ -3452,7 +3462,7 @@ function parseSsePart2(part) {
|
|
|
3452
3462
|
return void 0;
|
|
3453
3463
|
}
|
|
3454
3464
|
}
|
|
3455
|
-
function resolveOpenAiOptions(options) {
|
|
3465
|
+
function resolveOpenAiOptions(options, packer) {
|
|
3456
3466
|
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
3457
3467
|
return {
|
|
3458
3468
|
apiKey: options.apiKey ?? readEnv3("OPENAI_API_KEY") ?? "",
|
|
@@ -3464,7 +3474,8 @@ function resolveOpenAiOptions(options) {
|
|
|
3464
3474
|
maxAttempts: options.maxAttempts ?? 3,
|
|
3465
3475
|
fetch: options.fetch ?? globalThis.fetch,
|
|
3466
3476
|
...options.tools ? { tools: options.tools } : {},
|
|
3467
|
-
...options.logger ? { logger: options.logger } : {}
|
|
3477
|
+
...options.logger ? { logger: options.logger } : {},
|
|
3478
|
+
...packer ? { metadataPacker: packer } : {}
|
|
3468
3479
|
};
|
|
3469
3480
|
}
|
|
3470
3481
|
function readEnv3(name) {
|
|
@@ -3517,7 +3528,9 @@ function resolveProvider(requestProvider, defaultProvider) {
|
|
|
3517
3528
|
|
|
3518
3529
|
// src/create-dispatcher.ts
|
|
3519
3530
|
var SECRET_HEADERS = /* @__PURE__ */ new Set(["authorization", "x-api-key", "api-key"]);
|
|
3531
|
+
var DEFAULT_METADATA_PRIORITY = ["agentId", "orgId", "stepId", "skillId", "recordId", "objectType"];
|
|
3520
3532
|
function createAiDispatcher(options = {}) {
|
|
3533
|
+
const packer = (0, import_provider_metadata.createMetadataPacker)({ priority: options.metadataPriority ?? DEFAULT_METADATA_PRIORITY });
|
|
3521
3534
|
const defaultProvider = resolveProvider(options.provider, "openrouter");
|
|
3522
3535
|
const catalog = createMcpCatalog(options.mcp);
|
|
3523
3536
|
let refreshChain = Promise.resolve();
|
|
@@ -3526,6 +3539,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3526
3539
|
if (provider === "openrouter") {
|
|
3527
3540
|
const openrouterOptions = {
|
|
3528
3541
|
...options.openrouter,
|
|
3542
|
+
metadataPacker: packer,
|
|
3529
3543
|
...options.logger && !options.openrouter?.logger ? { logger: options.logger } : {}
|
|
3530
3544
|
};
|
|
3531
3545
|
return createOpenRouterProviderAdapter(openrouterOptions);
|
|
@@ -3533,6 +3547,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3533
3547
|
if (provider === "bedrock") {
|
|
3534
3548
|
const bedrockOptions = {
|
|
3535
3549
|
...options.bedrock,
|
|
3550
|
+
metadataPacker: packer,
|
|
3536
3551
|
...options.logger && !options.bedrock?.logger ? { logger: options.logger } : {}
|
|
3537
3552
|
};
|
|
3538
3553
|
return createBedrockProviderAdapter(bedrockOptions);
|
|
@@ -3542,14 +3557,14 @@ function createAiDispatcher(options = {}) {
|
|
|
3542
3557
|
...options.openai,
|
|
3543
3558
|
...options.logger && !options.openai?.logger ? { logger: options.logger } : {}
|
|
3544
3559
|
};
|
|
3545
|
-
return createOpenAiProviderAdapter(openaiOptions);
|
|
3560
|
+
return createOpenAiProviderAdapter(openaiOptions, packer);
|
|
3546
3561
|
}
|
|
3547
3562
|
if (provider === "cloudflare") {
|
|
3548
3563
|
const cloudflareOptions = {
|
|
3549
3564
|
...options.cloudflare,
|
|
3550
3565
|
...options.logger && !options.cloudflare?.logger ? { logger: options.logger } : {}
|
|
3551
3566
|
};
|
|
3552
|
-
return createCloudflareProviderAdapter(cloudflareOptions);
|
|
3567
|
+
return createCloudflareProviderAdapter(cloudflareOptions, packer);
|
|
3553
3568
|
}
|
|
3554
3569
|
throw new AiDispatcherError(
|
|
3555
3570
|
"PROVIDER_ADAPTER_MISSING",
|
|
@@ -3670,10 +3685,14 @@ function stripReasoningHistoryFields(message, paths) {
|
|
|
3670
3685
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3671
3686
|
0 && (module.exports = {
|
|
3672
3687
|
AiDispatcherError,
|
|
3688
|
+
DEFAULT_METADATA_PRIORITY,
|
|
3673
3689
|
IMPLEMENTED_AI_PROVIDERS,
|
|
3690
|
+
METADATA_NORMALIZATION_MAP,
|
|
3691
|
+
canonicalMetadataKey,
|
|
3674
3692
|
createAiDispatcher,
|
|
3675
3693
|
decodeProviderMetadata,
|
|
3676
3694
|
isImplementedAiProvider,
|
|
3695
|
+
normalizeMetadata,
|
|
3677
3696
|
providerNotImplemented,
|
|
3678
3697
|
resolveProvider,
|
|
3679
3698
|
stripReasoningHistoryFields
|