@x12i/ai-dispatcher 2.1.0 → 2.3.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
@@ -1,10 +1,10 @@
1
1
  # @x12i/ai-dispatcher
2
2
 
3
- One request shape for OpenRouter, AWS Bedrock, the OpenAI Responses API, and Cloudflare AI. You send the same request shape. The package picks the provider, and `@x12i/ai-profiles@5.0.0` turns `reasoningEffort` into that provider's wire fields. Callers do not map effort levels onto provider fields or parse provider reasoning channels.
3
+ One request shape for OpenRouter, AWS Bedrock, the OpenAI Responses API, and Cloudflare AI. You send the same request shape. The package picks the provider, and `@x12i/ai-profiles@^5.1.0` turns `reasoningEffort` into that provider's wire fields. Callers do not map effort levels onto provider fields or parse provider reasoning channels.
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.1.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.3.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/provider-metadata@1.1.0`, `@x12i/openrouter-runtime@^2.1.0`, and `@x12i/bedrock-runtime@^2.1.0`.
17
+ The package depends on `@x12i/ai-profiles@^5.1.0`, `@x12i/provider-metadata@^1.2.0`, `@x12i/openrouter-runtime@^2.2.0`, and `@x12i/bedrock-runtime@^2.2.0`. `registerHostIntents` is re-exported from this package so a host registers intents on the same `@x12i/ai-profiles` copy the dispatcher resolves with.
18
18
 
19
19
  ## One request
20
20
 
@@ -56,7 +56,7 @@ Use model ids that belong to the selected catalog target. An OpenRouter id is no
56
56
  | `openai` | `gpt-5.4` | `reasoning.effort: "low"` on the Responses body |
57
57
  | `cloudflare` | `openai/gpt-5.4` | `reasoning.effort: "low"` on the Cloudflare Responses body |
58
58
 
59
- Representative catalog results for `@x12i/ai-profiles@5.0.0`:
59
+ Representative catalog results for `@x12i/ai-profiles@5.1.0`:
60
60
 
61
61
  - OpenRouter `~google/gemini-flash-latest` + `max` is `degraded` and sends `reasoning.effort: "high"`.
62
62
  - OpenRouter `aion-labs/aion-2.0` is `ignored` with an empty reasoning body. The call still runs.
@@ -153,7 +153,7 @@ Text can arrive as `prompt`, `messages`, `input`, `system`, or `instructions`. A
153
153
  | `cloudflare` | Per-call overrides: `endpoint`, `gatewayId`, `run`, and `cf-aig-*` controls. Stripped before compilation. |
154
154
  | `mcp` | `false` or `[]` omits MCP tools registered at initialization. A list of exposed names attaches only those tools. Omit the field to attach every registered MCP tool. |
155
155
 
156
- Set `metadata` for the pairs you want back from a log. `agentId`, `orgId`, `stepId`, and `skillId` are fields on the same record. A dedicated field wins over the same key inside `metadata`. Omit an id you do not have. The dispatcher does not invent one.
156
+ Set `metadata` for the pairs you want back from a log. `agentId`, `orgId`, `stepId`, and `skillId` are fields on the same record. A `role` object expands to `role`, `roleLayer`, `roleDigest`, and `profileChoice` on that record. A dedicated field wins over the same key inside `metadata`. Omit an id you do not have. The dispatcher does not invent one.
157
157
 
158
158
  ```ts
159
159
  import { decodeProviderMetadata } from "@x12i/ai-dispatcher";
@@ -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. 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.
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`, `role`, `roleLayer`, `roleDigest`, `profileChoice`, `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
 
@@ -241,7 +243,7 @@ Text blocks in an MCP result are joined and returned to the model. `isError: tru
241
243
 
242
244
  ## Reasoning effort
243
245
 
244
- `reasoningEffort` is `minimal | low | medium | high | max`.
246
+ `reasoningEffort` is `minimal | low | medium | high | max`. `extra-high` is rejected with `INVALID_EFFORT`. It is not stored as `max`.
245
247
 
246
248
  | Request | Catalog |
247
249
  | --- | --- |
@@ -294,7 +296,7 @@ The rest of the response is the runtime shape: `id`, `status` (`completed`, `fai
294
296
 
295
297
  Streaming emits `stream.reasoning.delta` for thought text. That text is not copied into `stream.text.delta`, tool-argument deltas, or structured-output text. If a future catalog asks for think-tag stripping, tags are split out of answer deltas before those deltas are yielded.
296
298
 
297
- The dispatcher does not store conversation history. When a directive lists history fields to drop, callers pass those paths to `stripReasoningHistoryFields(message, paths)`. The helper clones the message, deletes only those pointers, and is safe to call twice. Catalog 5.0.0 paths are empty, so a real directive deletes nothing.
299
+ The dispatcher does not store conversation history. When a directive lists history fields to drop, callers pass those paths to `stripReasoningHistoryFields(message, paths)`. The helper clones the message, deletes only those pointers, and is safe to call twice. Catalog 5.1.0 paths are empty, so a real directive deletes nothing.
298
300
 
299
301
  ## Streaming
300
302
 
@@ -482,11 +484,11 @@ Pass `logger` on the dispatcher. `run` and `executeStreamingChat` emit `ai-dispa
482
484
 
483
485
  ## Limitations
484
486
 
485
- - Catalog 5.0.0 does not currently emit a prompt prefix, a replacement model, think-tag stripping, history deletions, or a reasoning token budget. Those paths exist for the directive type and are covered by synthetic tests.
487
+ - Catalog 5.1.0 does not currently emit a prompt prefix, a replacement model, think-tag stripping, history deletions, or a reasoning token budget. Those paths exist for the directive type and are covered by synthetic tests.
486
488
  - Prompt caching is a dispatcher profile, not an `@x12i/ai-profiles` field. It writes one breakpoint after `system` or `instructions`. It does not place extra breakpoints on tools or on a prefix that changes in the middle of the call.
487
489
  - Provenance notes about output-token headroom are not enforced, because the resolver does not return them as fields.
488
490
  - OpenRouter server tools, `apiMode: "chat"`, and `rawOpenRouterOverrides` stay on OpenRouter. Bedrock, direct OpenAI, and Cloudflare reject the server-tool fields and `rawOpenRouterOverrides` keys other than `metadata`. Cloudflare accepts `apiMode: "chat"` as the chat-completions endpoint.
489
491
  - Direct OpenAI and Cloudflare streaming do not run the function-tool loop. MCP tools are not attached to `executeStreamingChat` on any provider. Cloudflare `/ai/run` does not stream.
490
492
  - OpenRouter advisor, subagent, and fusion cannot call MCP sessions registered on the dispatcher.
491
493
  - Bedrock `run()` returns a failed response for `UNSUPPORTED_CONTENT`. `compile()` throws that error.
492
- - Live provider calls are not part of the package test suite. Tests mock HTTP and the Bedrock client and use the real 5.0.0 catalog for contract cases.
494
+ - Live provider calls are not part of the package test suite. Tests mock HTTP and the Bedrock client and use the real 5.1.0 catalog for contract cases.
package/dist/index.cjs CHANGED
@@ -23,14 +23,19 @@ __export(index_exports, {
23
23
  AiDispatcherError: () => AiDispatcherError,
24
24
  DEFAULT_METADATA_PRIORITY: () => DEFAULT_METADATA_PRIORITY,
25
25
  IMPLEMENTED_AI_PROVIDERS: () => IMPLEMENTED_AI_PROVIDERS,
26
+ METADATA_NORMALIZATION_MAP: () => import_openrouter_runtime4.METADATA_NORMALIZATION_MAP,
27
+ canonicalMetadataKey: () => import_openrouter_runtime4.canonicalMetadataKey,
26
28
  createAiDispatcher: () => createAiDispatcher,
27
29
  decodeProviderMetadata: () => import_openrouter_runtime4.decodeProviderMetadata,
28
30
  isImplementedAiProvider: () => isImplementedAiProvider,
31
+ normalizeMetadata: () => import_openrouter_runtime4.normalizeMetadata,
29
32
  providerNotImplemented: () => providerNotImplemented,
33
+ registerHostIntents: () => import_ai_profiles2.registerHostIntents,
30
34
  resolveProvider: () => resolveProvider,
31
35
  stripReasoningHistoryFields: () => stripReasoningHistoryFields
32
36
  });
33
37
  module.exports = __toCommonJS(index_exports);
38
+ var import_ai_profiles2 = require("@x12i/ai-profiles");
34
39
  var import_openrouter_runtime4 = require("@x12i/openrouter-runtime");
35
40
 
36
41
  // src/create-dispatcher.ts
@@ -2349,6 +2354,7 @@ function isRecord5(value) {
2349
2354
  // src/prepare-request.ts
2350
2355
  function prepareDispatchRequest(params) {
2351
2356
  const cloned = cloneRequest(params.request);
2357
+ applyRoleStamp(cloned);
2352
2358
  applyDispatchIdentity(cloned);
2353
2359
  absorbOverrideMetadata(cloned);
2354
2360
  const provider = params.provider;
@@ -2656,6 +2662,23 @@ function defaultModelFor(provider, options) {
2656
2662
  return options.openai?.defaultModel;
2657
2663
  }
2658
2664
  var DISPATCH_IDENTITY_FIELDS = ["orgId", "stepId", "skillId"];
2665
+ var ROLE_STAMP_FIELDS = ["role", "roleLayer", "roleDigest", "profileChoice"];
2666
+ function applyRoleStamp(request) {
2667
+ const stamp = request.role;
2668
+ delete request.role;
2669
+ if (!isRecord6(stamp)) return;
2670
+ const identity = {};
2671
+ for (const field of ROLE_STAMP_FIELDS) {
2672
+ const value = stamp[field];
2673
+ if (typeof value !== "string") continue;
2674
+ const trimmed = value.trim();
2675
+ if (trimmed) identity[field] = trimmed;
2676
+ }
2677
+ if (!Object.keys(identity).length) return;
2678
+ const rest = { ...request.metadata ?? {} };
2679
+ for (const key of Object.keys(identity)) delete rest[key];
2680
+ request.metadata = { ...identity, ...rest };
2681
+ }
2659
2682
  function absorbOverrideMetadata(request) {
2660
2683
  const overrides = request.rawOpenRouterOverrides;
2661
2684
  if (!isRecord6(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
@@ -3525,7 +3548,18 @@ function resolveProvider(requestProvider, defaultProvider) {
3525
3548
 
3526
3549
  // src/create-dispatcher.ts
3527
3550
  var SECRET_HEADERS = /* @__PURE__ */ new Set(["authorization", "x-api-key", "api-key"]);
3528
- var DEFAULT_METADATA_PRIORITY = ["agentId", "orgId", "stepId", "skillId", "recordId", "objectType"];
3551
+ var DEFAULT_METADATA_PRIORITY = [
3552
+ "agentId",
3553
+ "orgId",
3554
+ "stepId",
3555
+ "skillId",
3556
+ "role",
3557
+ "roleLayer",
3558
+ "roleDigest",
3559
+ "profileChoice",
3560
+ "recordId",
3561
+ "objectType"
3562
+ ];
3529
3563
  function createAiDispatcher(options = {}) {
3530
3564
  const packer = (0, import_provider_metadata.createMetadataPacker)({ priority: options.metadataPriority ?? DEFAULT_METADATA_PRIORITY });
3531
3565
  const defaultProvider = resolveProvider(options.provider, "openrouter");
@@ -3684,10 +3718,14 @@ function stripReasoningHistoryFields(message, paths) {
3684
3718
  AiDispatcherError,
3685
3719
  DEFAULT_METADATA_PRIORITY,
3686
3720
  IMPLEMENTED_AI_PROVIDERS,
3721
+ METADATA_NORMALIZATION_MAP,
3722
+ canonicalMetadataKey,
3687
3723
  createAiDispatcher,
3688
3724
  decodeProviderMetadata,
3689
3725
  isImplementedAiProvider,
3726
+ normalizeMetadata,
3690
3727
  providerNotImplemented,
3728
+ registerHostIntents,
3691
3729
  resolveProvider,
3692
3730
  stripReasoningHistoryFields
3693
3731
  });