@x12i/ai-dispatcher 3.0.0 → 3.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 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.2.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.3.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` 3.0.0. It runs on Node 22 or newer and publishes ESM and CommonJS from the same entry.
7
+ The package is `@x12i/ai-dispatcher` 3.2.0. It runs on Node 22 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,21 @@ 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-input@1.1.0`, `@x12i/ai-profiles@^5.2.0`, `@x12i/provider-metadata@^1.2.1`, `@x12i/openrouter-runtime@^2.2.1`, and `@x12i/bedrock-runtime@^2.2.2`. `gpt-tokenizer` and `ajv` are optional peers of `@x12i/ai-input` and are not installed with the dispatcher. `registerHostIntents` is re-exported from this package so a host registers intents on the same `@x12i/ai-profiles` copy the dispatcher resolves with.
17
+ The package depends on `@x12i/ai-input@1.1.0`, `@x12i/ai-profiles@5.3.0`, `@x12i/provider-metadata@^1.2.1`, `@x12i/openrouter-runtime@^2.2.1`, and `@x12i/bedrock-runtime@^2.2.2`. `@x12i/ai-catalogs@1.2.0` comes in through profiles. `gpt-tokenizer` and `ajv` are optional peers of `@x12i/ai-input` and are not installed with the dispatcher. `registerHostIntents` and `resolveAIProfile` are re-exported from this package so a host uses the same `@x12i/ai-profiles` copy the dispatcher resolves with.
18
+
19
+ ## AI registry
20
+
21
+ `@x12i/ai-profiles@5.3.0` loads `GET /v1/datasets/profiles` and `GET /v1/datasets/catalog` from `https://ai-registry.app.x12i.com`. A successful response is used as-is and cached briefly. If the registry is unreachable, the JSON shipped in the npm package is used. Do not set `AI_REGISTRY_SOURCE`. Override the origin only when you proxy it: `AI_REGISTRY_URL`.
22
+
23
+ `resolveReasoning` still reads the bundled reasoning catalog. Profile names such as `cheap/default` resolve through the live registry:
24
+
25
+ ```ts
26
+ import { resolveAIProfile } from "@x12i/ai-dispatcher";
27
+
28
+ const profile = await resolveAIProfile("cheap/default", { catalogLane: "text" });
29
+ ```
30
+
31
+ Leave `AUTHIX_TOKEN` unset for the published model. Set it only if a host is pinning a profile or role to a catalog id for an Authix subject. Omit `context` / `AI_REGISTRY_CONTEXT` for the default bucket. A stored pin replaces that profile. A key with no pin stays on the published model. If the pin service cannot be reached, the published model is used and a `CUSTOMIZATION_UNAVAILABLE` warning is attached. Resolve does not fail. The token is sent only to `/v1/customization`. This package does not write pins, does not depend on `@x12i/ai-registry-client` or `@x12i/agent-roles`, and does not log the token. A `role` stamp on the request is caller metadata.
18
32
 
19
33
  ## One request
20
34
 
@@ -56,7 +70,7 @@ Use model ids that belong to the selected catalog target. An OpenRouter id is no
56
70
  | `openai` | `gpt-5.4` | `reasoning.effort: "low"` on the Responses body |
57
71
  | `cloudflare` | `openai/gpt-5.4` | `reasoning.effort: "low"` on the Cloudflare Responses body |
58
72
 
59
- Representative catalog results for `@x12i/ai-profiles@5.2.0`:
73
+ Representative catalog results for `@x12i/ai-profiles@5.3.0`:
60
74
 
61
75
  - OpenRouter `~google/gemini-flash-latest` + `max` is `degraded` and sends `reasoning.effort: "high"`.
62
76
  - OpenRouter `aion-labs/aion-2.0` is `ignored` with an empty reasoning body. The call still runs.
@@ -144,7 +158,7 @@ Text can arrive as `prompt`, `messages`, `input`, `system`, or `instructions`. A
144
158
  | `functionTools` | Local function tools: `name`, `description`, `parameters`, optional `executor`. |
145
159
  | `toolChoice` | `"auto"`, `"none"`, `"required"`, `{ type: "function", functionName }`, or an OpenRouter `{ type: "server_tool", serverTool }`. |
146
160
  | `execution` | `maxToolIterations`, `maxServerToolCalls`, `maxFunctionToolCalls`, `timeoutMs`. Bedrock keeps the iteration, function-call, and timeout limits. |
147
- | `metadata` | The one metadata record. String, number, and boolean values, including `xtivix.execution`, `recordId`, and `objectType`. |
161
+ | `metadata` | The one metadata record. String, number, and boolean values, including `session_id`, `xtivix.caller`, `xtivix.execution`, `recordId`, and `objectType`. The package adds `xtivix.gate`. |
148
162
  | `serverTools` | OpenRouter server tools only. Bedrock, direct OpenAI, and Cloudflare reject an enabled policy. |
149
163
  | `apiMode` | `"chat"`, `"responses"`, or `"auto"`. Direct OpenAI rejects `"chat"`. Bedrock rejects any `apiMode`. On Cloudflare, `"chat"` and `"responses"` select those endpoints when `cloudflare.endpoint` is omitted or `auto`. |
150
164
  | `responseFormat` | OpenRouter forwards it. Direct OpenAI and Cloudflare Responses place it on `text`. Cloudflare chat sends `response_format`. Cloudflare messages and run reject it. Bedrock rejects it. |
@@ -156,6 +170,8 @@ Text can arrive as `prompt`, `messages`, `input`, `system`, or `instructions`. A
156
170
 
157
171
  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.
158
172
 
173
+ `session_id` is the thread id. Send the full string on `metadata`, including `dd:`, the package, the version, and the hash. Leave it as one string. `session.id` is promoted to `session_id`. A key named `sessionId` is a different key and does not become the thread. `xtivix.caller` is your name for who sent the call. Omit it when you do not have one. The dispatcher does not invent it. `xtivix.gate` is written by this package on every call, as `ai-dispatcher` (`AI_DISPATCHER_GATE`). A value you send for that key is replaced. The gate is not the model provider. These values are plain strings. Slashes, `@`, and colons in the session id need no escaping.
174
+
159
175
  ```ts
160
176
  import { decodeProviderMetadata } from "@x12i/ai-dispatcher";
161
177
 
@@ -167,6 +183,8 @@ await dispatcher.run({
167
183
  stepId: "step-3",
168
184
  skillId: "skill-9",
169
185
  metadata: {
186
+ session_id: "dd:@xverix/mail-decisions/vendor@0.1.0:46b03b5e",
187
+ "xtivix.caller": "mail-decisions",
170
188
  "xtivix.execution": executionJson,
171
189
  recordId: "rec-1",
172
190
  objectType: "note"
@@ -176,9 +194,9 @@ await dispatcher.run({
176
194
  const record = decodeProviderMetadata(logMetadata);
177
195
  ```
178
196
 
179
- `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.
197
+ `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. `xtivix.gate` is the exception: the package value wins. String, number, and boolean values are stored, including a JSON string. Objects and arrays stay on the normalized response and the tool context.
180
198
 
181
- 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`. Pass the Cloudflare `cf-aig-metadata` header as that same object or as the raw JSON string. 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.
199
+ 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`. Pass the Cloudflare `cf-aig-metadata` header as that same object or as the raw JSON string. 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`. `session_id`, `xtivix.caller`, and `xtivix.gate` are not in that list. Unlisted keys are dropped longest first, so these short values stay unless the record is still over the limit after every longer unlisted value, including `xtivix.execution`, has been dropped. Include `session_id`, `xtivix.caller`, and `xtivix.gate` when you pass your own `metadataPriority`. A trimmed call adds a `METADATA_RECORD_TRIMMED` warning.
182
200
 
183
201
  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.
184
202
 
@@ -311,7 +329,7 @@ The rest of the response is the runtime shape: `id`, `status` (`completed`, `fai
311
329
 
312
330
  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.
313
331
 
314
- 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.2.0 paths are empty, so a real directive deletes nothing.
332
+ 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.3.0 paths are empty, so a real directive deletes nothing.
315
333
 
316
334
  ## Streaming
317
335
 
@@ -507,18 +525,18 @@ OpenAI HTTP failures such as `401` return a failed response from `run()`. `FUNCT
507
525
 
508
526
  ## Logging
509
527
 
510
- Pass `logger` on the dispatcher. `run` and `executeStreamingChat` emit `ai-dispatcher.request.started` at info. `compile` emits `ai-dispatcher.request.compiled` at debug. The record can include `provider`, `entrypoint`, `streaming`, `requestedModel`, `effectiveModel`, `requestId`, `outcome`, and `bypassed`. It does not include credentials, prompts, reasoning text, or raw bodies.
528
+ Pass `logger` on the dispatcher. `run` and `executeStreamingChat` emit `ai-dispatcher.request.started` at info. `compile` emits `ai-dispatcher.request.compiled` at debug. The record can include `provider`, `entrypoint`, `streaming`, `requestedModel`, `effectiveModel`, `requestId`, `outcome`, and `bypassed`. It does not include credentials, `AUTHIX_TOKEN`, prompts, reasoning text, or raw bodies.
511
529
 
512
530
  ## Limitations
513
531
 
514
- - Catalog 5.2.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.
532
+ - Catalog 5.3.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.
515
533
  - 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.
516
534
  - Provenance notes about output-token headroom are not enforced, because the resolver does not return them as fields.
517
535
  - 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.
518
536
  - 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.
519
537
  - OpenRouter advisor, subagent, and fusion cannot call MCP sessions registered on the dispatcher.
520
538
  - Bedrock `run()` returns a failed response for `UNSUPPORTED_CONTENT`. `compile()` throws that error.
521
- - Live provider calls are not part of the package test suite. Tests mock HTTP and the Bedrock client and use the real 5.2.0 catalog for contract cases.
539
+ - Live provider calls are not part of the package test suite. Tests mock HTTP and the Bedrock client and use the real 5.3.0 reasoning catalog for contract cases.
522
540
  - Input preparation does not claim release gate 9. Automatic TOON and `shared-v1` stay on compact outline or the original string until the caller passes a matching approval and a tokenizer. `codec: "toon"` sends TOON as a caller choice. `codec: "json"` and `mode: "raw"` keep JSON.
523
541
  - `gpt-tokenizer` and `ajv` are not installed. A `gpt:*` tokenizer is loaded only when that call requests it.
524
542
  - The dispatcher does not read `AI_INPUT_*` or a `.env` file for input preparation.
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AI_DISPATCHER_GATE: () => AI_DISPATCHER_GATE,
33
34
  AiDispatcherError: () => AiDispatcherError,
34
35
  DEFAULT_METADATA_PRIORITY: () => DEFAULT_METADATA_PRIORITY,
35
36
  IMPLEMENTED_AI_PROVIDERS: () => IMPLEMENTED_AI_PROVIDERS,
@@ -41,6 +42,7 @@ __export(index_exports, {
41
42
  normalizeMetadata: () => import_openrouter_runtime4.normalizeMetadata,
42
43
  providerNotImplemented: () => providerNotImplemented,
43
44
  registerHostIntents: () => import_ai_profiles2.registerHostIntents,
45
+ resolveAIProfile: () => import_ai_profiles2.resolveAIProfile,
44
46
  resolveProvider: () => resolveProvider,
45
47
  stripReasoningHistoryFields: () => stripReasoningHistoryFields
46
48
  });
@@ -2650,6 +2652,7 @@ function prepareDispatchRequest(params) {
2650
2652
  applyRoleStamp(cloned);
2651
2653
  applyDispatchIdentity(cloned);
2652
2654
  absorbOverrideMetadata(cloned);
2655
+ applyDispatchGate(cloned);
2653
2656
  const provider = params.provider;
2654
2657
  const reasoningEffort = cloned.reasoningEffort;
2655
2658
  const bedrockControls = cloned.bedrockControls;
@@ -3002,6 +3005,12 @@ function applyDispatchIdentity(request) {
3002
3005
  for (const key of Object.keys(identity)) delete rest[key];
3003
3006
  request.metadata = { ...identity, ...rest };
3004
3007
  }
3008
+ var AI_DISPATCHER_GATE = "ai-dispatcher";
3009
+ function applyDispatchGate(request) {
3010
+ const rest = { ...request.metadata ?? {} };
3011
+ delete rest["xtivix.gate"];
3012
+ request.metadata = { "xtivix.gate": AI_DISPATCHER_GATE, ...rest };
3013
+ }
3005
3014
  function cloneRequest(request) {
3006
3015
  const functionTools = request.functionTools?.map((tool) => ({ ...tool }));
3007
3016
  const cloned = JSON.parse(JSON.stringify(request));
@@ -4026,6 +4035,7 @@ function stripReasoningHistoryFields(message, paths) {
4026
4035
  }
4027
4036
  // Annotate the CommonJS export names for ESM import in node:
4028
4037
  0 && (module.exports = {
4038
+ AI_DISPATCHER_GATE,
4029
4039
  AiDispatcherError,
4030
4040
  DEFAULT_METADATA_PRIORITY,
4031
4041
  IMPLEMENTED_AI_PROVIDERS,
@@ -4037,6 +4047,7 @@ function stripReasoningHistoryFields(message, paths) {
4037
4047
  normalizeMetadata,
4038
4048
  providerNotImplemented,
4039
4049
  registerHostIntents,
4050
+ resolveAIProfile,
4040
4051
  resolveProvider,
4041
4052
  stripReasoningHistoryFields
4042
4053
  });