@x12i/ai-dispatcher 2.4.1 → 3.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 +31 -8
- package/dist/index.cjs +441 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -3
- package/dist/index.d.ts +77 -3
- package/dist/index.js +434 -122
- package/dist/index.js.map +1 -1
- package/package.json +7 -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`
|
|
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.
|
|
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.2.0`, `@x12i/provider-metadata@^1.2.1`, `@x12i/openrouter-runtime@^2.2.1`, and `@x12i/bedrock-runtime@^2.2.2`. `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.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.
|
|
18
18
|
|
|
19
19
|
## One request
|
|
20
20
|
|
|
@@ -128,7 +128,7 @@ OpenAI retries `429`, `408`, and `5xx` with backoff capped at 5 seconds. After t
|
|
|
128
128
|
|
|
129
129
|
## The request
|
|
130
130
|
|
|
131
|
-
`AiDispatcherRequest` is the OpenRouter runtime request plus `provider`, `orgId`, `stepId`, `skillId`, `reasoningEffort`, `bedrockControls`, `cache`, `cloudflare`, and `
|
|
131
|
+
`AiDispatcherRequest` is the OpenRouter runtime request plus `provider`, `orgId`, `stepId`, `skillId`, `reasoningEffort`, `bedrockControls`, `cache`, `cloudflare`, `mcp`, and `aiInput`. `agentId` is already on that request.
|
|
132
132
|
|
|
133
133
|
Text can arrive as `prompt`, `messages`, `input`, `system`, or `instructions`. A message role is `system`, `user`, `assistant`, or `tool`. Content is a string or parts: `text`, `image_url`, or `file`.
|
|
134
134
|
|
|
@@ -144,7 +144,7 @@ Text can arrive as `prompt`, `messages`, `input`, `system`, or `instructions`. A
|
|
|
144
144
|
| `functionTools` | Local function tools: `name`, `description`, `parameters`, optional `executor`. |
|
|
145
145
|
| `toolChoice` | `"auto"`, `"none"`, `"required"`, `{ type: "function", functionName }`, or an OpenRouter `{ type: "server_tool", serverTool }`. |
|
|
146
146
|
| `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`. |
|
|
147
|
+
| `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
148
|
| `serverTools` | OpenRouter server tools only. Bedrock, direct OpenAI, and Cloudflare reject an enabled policy. |
|
|
149
149
|
| `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
150
|
| `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. |
|
|
@@ -152,9 +152,12 @@ Text can arrive as `prompt`, `messages`, `input`, `system`, or `instructions`. A
|
|
|
152
152
|
| `cache` | Optional prompt-cache intent: `mode` (`auto`, `explicit`, or `disabled`), `key`, and `ttl` (`5m`, `30m`, or `1h`). Omit it and the compiled body is unchanged. `explicit` places one breakpoint at the end of `system` or `instructions` when that model supports it. On Cloudflare it also sets AI Gateway cache headers. |
|
|
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
|
+
| `aiInput` | `false` skips input preparation. A settings object overrides the dispatcher default for this call. Stripped before the provider payload is built. See [Input preparation](#input-preparation). |
|
|
155
156
|
|
|
156
157
|
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
158
|
|
|
159
|
+
`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.
|
|
160
|
+
|
|
158
161
|
```ts
|
|
159
162
|
import { decodeProviderMetadata } from "@x12i/ai-dispatcher";
|
|
160
163
|
|
|
@@ -166,6 +169,8 @@ await dispatcher.run({
|
|
|
166
169
|
stepId: "step-3",
|
|
167
170
|
skillId: "skill-9",
|
|
168
171
|
metadata: {
|
|
172
|
+
session_id: "dd:@xverix/mail-decisions/vendor@0.1.0:46b03b5e",
|
|
173
|
+
"xtivix.caller": "mail-decisions",
|
|
169
174
|
"xtivix.execution": executionJson,
|
|
170
175
|
recordId: "rec-1",
|
|
171
176
|
objectType: "note"
|
|
@@ -175,9 +180,9 @@ await dispatcher.run({
|
|
|
175
180
|
const record = decodeProviderMetadata(logMetadata);
|
|
176
181
|
```
|
|
177
182
|
|
|
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.
|
|
183
|
+
`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.
|
|
179
184
|
|
|
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`. 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.
|
|
185
|
+
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.
|
|
181
186
|
|
|
182
187
|
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.
|
|
183
188
|
|
|
@@ -195,10 +200,10 @@ Bedrock receives `id`, `model`, `messages`, `system`, `prompt`, `temperature`, `
|
|
|
195
200
|
| --- | --- |
|
|
196
201
|
| `dispatcher.run(request)` | Non-streaming call. Returns answer text, optional reasoning text, usage, tool results, and diagnostics. |
|
|
197
202
|
| `dispatcher.executeStreamingChat(request)` | The only streaming entrypoint. Yields typed events. |
|
|
198
|
-
| `dispatcher.compile(request)` |
|
|
203
|
+
| `dispatcher.compile(request)` | Returns a `Promise` of the same preparation as a live call, without sending. |
|
|
199
204
|
| `dispatcher.refreshMcp()` | Reloads MCP servers that implement `listTools`. A failed reload keeps the previous list. |
|
|
200
205
|
|
|
201
|
-
`compile()` returns a provider payload plus `requestedModel`, `effectiveModel`, and `reasoningResolution` when reasoning was involved:
|
|
206
|
+
`compile()` returns a `Promise`. The value is a provider payload plus `requestedModel`, `effectiveModel`, and `reasoningResolution` when reasoning was involved. `inputResolution` is included when preparation is off, when model-visible text changes, or when the chosen codec is not the outline, JSON, or text baseline:
|
|
202
207
|
|
|
203
208
|
- OpenRouter: `{ provider, url, headers, body, warnings }`
|
|
204
209
|
- Bedrock: `{ provider, modelId, input }`
|
|
@@ -207,6 +212,20 @@ Bedrock receives `id`, `model`, `messages`, `system`, `prompt`, `temperature`, `
|
|
|
207
212
|
|
|
208
213
|
`Authorization`, `x-api-key`, and `api-key` are replaced with `[redacted]` in the compiled result. Execution sends the real headers. A streaming call adds only the transport stream flag (`stream: true`, or Bedrock `ConverseStream`).
|
|
209
214
|
|
|
215
|
+
## Input preparation
|
|
216
|
+
|
|
217
|
+
Preparation is on unless `aiInput: false` is set on the dispatcher or on the request. A request settings object turns a disabled dispatcher back on for that call. The default policy is compact mode, `codec: "auto"`, `sharing: "auto"`, raw framing, and no tokenizer. A string prompt stays the original string unless it contains a JSON document, which compact opens in place. A plain object, including a tool result, becomes compact outline. `codec: "json"` keeps compact JSON. `mode: "raw"` keeps `JSON.stringify`. The dispatcher does not read `AI_INPUT_*` or `.env`.
|
|
218
|
+
|
|
219
|
+
Prepared text is `prompt`, `system`, `instructions`, message text, string `input`, and `text` or `input_text` parts. Object results from request tools, MCP tools, and provider `tools` executors use the same policy before the runtime stringifies them. `Date`, class instances, and `undefined` stay on `JSON.stringify`.
|
|
220
|
+
|
|
221
|
+
`mode` (`compact`, `readable`, `raw`), `codec` (`auto`, `json`, `toon`, `text`, `outline`), `readableFormat`, `sharing`, and `framing` can be set on the factory or the request. Approval lookup uses `aiInput.model` when set, and otherwise the effective model for the call. Changing `request.model` changes that id. `aiInput.model` does not change the provider model. `gpt-5.4` and `openai/gpt-5.4` are different keys.
|
|
222
|
+
|
|
223
|
+
The built-in approval list is empty, so automatic TOON and `shared-v1` stay on the direct baseline until the caller passes a matching `CapabilityApproval` and a tokenizer. Explicit `codec: "toon"` does not need an approval. When the choice stays on outline, JSON, or text, the model sees the unframed body. This package does not claim release gate 9.
|
|
224
|
+
|
|
225
|
+
`tokenizer: "gpt:o200k_base"` (also `cl100k_base`, `p50k_base`, `p50k_edit`, `r50k_base`) loads `@x12i/ai-input/tokenizers/gpt` for that call. If `gpt-tokenizer` is not installed, the call fails with `AI_INPUT_FAILED`. `ajv` is not used.
|
|
226
|
+
|
|
227
|
+
`run()` and `compile()` attach `inputResolution` when preparation is disabled, when any model-visible text changes, or when the chosen codec is not the baseline. The inert default omits the field. The record has `enabled`, `model`, `mode`, `codec`, `representation`, `approvalSource`, and `changed`. Requirements are in [.operations/ai-input.md](.operations/ai-input.md).
|
|
228
|
+
|
|
210
229
|
## MCP tools
|
|
211
230
|
|
|
212
231
|
Pass connected MCP servers to `createAiDispatcher`. The dispatcher does not spawn processes or open transports. It lists the tools you already fetched and calls `callTool` when the model uses one.
|
|
@@ -486,6 +505,7 @@ Catalog failures throw `AiDispatcherError` with `code`, `message`, and the catal
|
|
|
486
505
|
| `MCP_TOOL_NAME_CONFLICT` | Two MCP tools share an exposed name |
|
|
487
506
|
| `MCP_TOOL_NOT_FOUND` | `request.mcp` names a tool that is not registered, or `request.mcp` is not `false` or a list of names |
|
|
488
507
|
| `MCP_LIST_FAILED` | `refreshMcp()` failed, or the new list breaks the name rules. The previous catalog stays |
|
|
508
|
+
| `AI_INPUT_FAILED` | Input preparation failed. `details.code` is the `@x12i/ai-input` code, such as `ADAPTER_UNAVAILABLE` when a `gpt:*` tokenizer is requested and `gpt-tokenizer` is not installed |
|
|
489
509
|
|
|
490
510
|
OpenAI HTTP failures such as `401` return a failed response from `run()`. `FUNCTION_TOOL_LIMIT` does too. Streaming yields `stream.error` and then throws, matching the other transports.
|
|
491
511
|
|
|
@@ -503,3 +523,6 @@ Pass `logger` on the dispatcher. `run` and `executeStreamingChat` emit `ai-dispa
|
|
|
503
523
|
- OpenRouter advisor, subagent, and fusion cannot call MCP sessions registered on the dispatcher.
|
|
504
524
|
- Bedrock `run()` returns a failed response for `UNSUPPORTED_CONTENT`. `compile()` throws that error.
|
|
505
525
|
- 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.
|
|
526
|
+
- 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.
|
|
527
|
+
- `gpt-tokenizer` and `ajv` are not installed. A `gpt:*` tokenizer is loaded only when that call requests it.
|
|
528
|
+
- The dispatcher does not read `AI_INPUT_*` or a `.env` file for input preparation.
|