@x12i/ai-gateway 10.0.2 → 10.0.5
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 +40 -13
- package/dist/ai-tools-client.d.ts +15 -33
- package/dist/ai-tools-client.js +57 -107
- package/dist/gateway-utils.d.ts +30 -10
- package/dist/gateway-utils.js +168 -110
- package/dist/gateway.js +8 -18
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/openrouter-routing.d.ts +3 -12
- package/dist/openrouter-routing.js +5 -34
- package/dist/types.d.ts +12 -0
- package/dist-cjs/ai-tools-client.cjs +57 -107
- package/dist-cjs/ai-tools-client.d.ts +15 -33
- package/dist-cjs/gateway-utils.cjs +168 -110
- package/dist-cjs/gateway-utils.d.ts +30 -10
- package/dist-cjs/gateway.cjs +8 -18
- package/dist-cjs/index.cjs +3 -1
- package/dist-cjs/index.d.ts +4 -1
- package/dist-cjs/openrouter-routing.cjs +5 -34
- package/dist-cjs/openrouter-routing.d.ts +3 -12
- package/dist-cjs/types.d.ts +12 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Unified gateway for LLM provider routing, structured logging, optional Activix a
|
|
|
13
13
|
| **Activix** | Optional Mongo-backed activity rows (`ai-actions`, `bad-requests`, `skill-executions`) with root billing fields and `outer` I/O. |
|
|
14
14
|
| **Trace mode** | `diagnostics.mode === 'trace'` adds `metadata.attempts[]`, `metadata.usage`, and per-attempt billing when priced. |
|
|
15
15
|
|
|
16
|
-
Pinned dependency versions are in `package.json` (currently **Activix ^
|
|
16
|
+
Pinned dependency versions are in `package.json` (currently **Activix ^8.5**, **ai-tools ^2.5**, **ai-providers-router ^4.9**).
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -79,7 +79,7 @@ const response = await gateway.invoke({
|
|
|
79
79
|
agentId: 'agent-456'
|
|
80
80
|
},
|
|
81
81
|
workingMemory: { input: 'Hello!' },
|
|
82
|
-
config: { model: '
|
|
82
|
+
config: { model: 'openai/gpt-4o-mini', provider: 'openrouter', maxTokens: 256 }
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
console.log(response.content, response.metadata?.costUsd, response.metadata?.tokens);
|
|
@@ -87,7 +87,7 @@ console.log(response.content, response.metadata?.costUsd, response.metadata?.tok
|
|
|
87
87
|
|
|
88
88
|
### Providers without manual `register()`
|
|
89
89
|
|
|
90
|
-
- **OpenRouter:** Set **`OPENROUTER_API_KEY`** in `.env`. The gateway always passes this key to the router when set. **By default, OpenRouter is preferred** for routing (including when you also have direct keys such as `OPENAI_API_KEY`). **`@x12i/ai-tools
|
|
90
|
+
- **OpenRouter:** Set **`OPENROUTER_API_KEY`** in `.env`. The gateway always passes this key to the router when set. **By default, OpenRouter is preferred** for routing (including when you also have direct keys such as `OPENAI_API_KEY`). **`@x12i/ai-tools`** resolves concrete model ids + provider via `resolveInvokeModel()` (catalog normalization, OpenRouter vs direct transport, router proxy flags). Pass catalog model ids such as `openai/gpt-4o-mini` or `gpt-4o` — not profile shortcuts (`cheapest`, `cheap/default`) unless you set `aiTools.modelsOnly: false`.
|
|
91
91
|
- **`USE_OPENROUTER=false`:** Do **not** prefer OpenRouter when a direct provider API key exists — use the direct provider instead. OpenRouter is **still** used as **fallback** when the request targets a provider without a direct key (e.g. `anthropic` without `ANTHROPIC_API_KEY`). It does not disable OpenRouter while `OPENROUTER_API_KEY` is set.
|
|
92
92
|
- **Direct providers:** Set `OPENAI_API_KEY`, `GROK_API_KEY`, etc. Registered lazily on first invoke.
|
|
93
93
|
|
|
@@ -188,7 +188,7 @@ Instructions must be **complete caller text** — the gateway no longer injects
|
|
|
188
188
|
| `AI_GATEWAY_LOGS_LEVEL` | Log threshold for gateway diagnostics (`AI_GATEWAY` prefix): `error` … `verbose` |
|
|
189
189
|
| `AI_GATEWAY_VERBOSE` | Full payload lines (still requires `AI_GATEWAY_LOGS_LEVEL=verbose`) |
|
|
190
190
|
| `LOGXER_PACKAGE_LEVELS` | Bulk stack levels, e.g. `AI_GATEWAY:info,AI_PROVIDER_ROUTER:debug` |
|
|
191
|
-
| `OPENROUTER_API_KEY` | OpenRouter key; always wired when set (required for
|
|
191
|
+
| `OPENROUTER_API_KEY` | OpenRouter key; always wired when set (required for OpenRouter transport) |
|
|
192
192
|
| `USE_OPENROUTER` | Optional; default **prefer** OpenRouter when key is set. `false` = use direct provider keys when present; OpenRouter still used as fallback when a provider has no key |
|
|
193
193
|
| Other provider keys | `OPENAI_API_KEY`, `GROK_API_KEY`, etc. |
|
|
194
194
|
|
|
@@ -210,12 +210,39 @@ Exports: `GATEWAY_LOGXER_PACKAGE`, `GATEWAY_LOG_ENV_PREFIX`, `createGatewayLogge
|
|
|
210
210
|
|
|
211
211
|
## @x12i/ai-tools v2 (models + cost)
|
|
212
212
|
|
|
213
|
-
-
|
|
214
|
-
- **`aiTools.enabled`** — bootstrap catalog client + calculator.
|
|
215
|
-
- **`aiTools.resolveModels`** — `mergeConfig()` resolves model ids (strict in **`mode: 'dev'`**).
|
|
216
|
-
- **`aiTools.calculateCost`** — prices usage before Activix `completeRecord` when the router did not mark the call priced.
|
|
213
|
+
Engine-owned catalog bootstrap and post-call billing. Consumers read **`metadata.costUsd`** / **`costStatus`** only — no direct `@x12i/ai-tools` dependency for cost.
|
|
217
214
|
|
|
218
|
-
|
|
215
|
+
### Resolution order (after every successful LLM call)
|
|
216
|
+
|
|
217
|
+
| Step | Condition | Result |
|
|
218
|
+
|------|-----------|--------|
|
|
219
|
+
| A | Router/provider returned finite **`costUsd`** (or equivalent) | **`costStatus: "priced"`**, set cost |
|
|
220
|
+
| B | Tokens + catalog pricing succeeds (`isAuthoritative`, not `unknownModel`, finite cost ≥ 0) | **`priced`** (+ optional breakdown) |
|
|
221
|
+
| C | Tokens but no price | **`unpriced`** |
|
|
222
|
+
| D | No usage | omit **`costUsd`** and **`costStatus`** |
|
|
223
|
+
|
|
224
|
+
Step A always wins; explicit router **`costStatus: "unpriced"`** is never overridden by catalog.
|
|
225
|
+
|
|
226
|
+
Implemented in **`resolveCostCompletionWithAiTools`** (delegates to **`CostCalculator.calculateFromRecord`** via **`buildGatewayPricingRecord`**). Target: move orchestrator to ai-tools as **`resolveInvokeBilling`** — see [AI_TOOLS_INVOKE_BILLING_ORCHESTRATOR_SPEC.md](./docs/upstream-reports/AI_TOOLS_INVOKE_BILLING_ORCHESTRATOR_SPEC.md).
|
|
227
|
+
|
|
228
|
+
### `aiTools` config (aligned with funcx / generic engine contract)
|
|
229
|
+
|
|
230
|
+
| Flag | Default | Purpose |
|
|
231
|
+
|------|---------|---------|
|
|
232
|
+
| **`enabled`** | `true` | Bootstrap **`AiModelsCatalogClient`** + **`CostCalculator`** |
|
|
233
|
+
| **`calculateCost`** | `true` | Run post-call catalog pricing when router did not price |
|
|
234
|
+
| **`resolveModels`** | `true` | **`mergeConfig()`** → **`resolveInvokeModel()`** |
|
|
235
|
+
| **`modelsOnly`** | `true` | Reject profile shortcuts (`cheapest`, `cheap/default`, …) |
|
|
236
|
+
| **`bundledOnly`** | `false` | Offline bundled catalogs only |
|
|
237
|
+
| **`costIncludeBreakdown`** | `false` | Include prompt/completion breakdown on priced results |
|
|
238
|
+
| **`catalogLane`** | `"text"` (ai-tools default) | Catalog lane for resolution + cost lookup (`text`, `image`, …) |
|
|
239
|
+
| **`cacheTtlMs`** | ai-tools default (24h) | In-memory catalog cache TTL |
|
|
240
|
+
|
|
241
|
+
- **No Catalox / Firestore** — catalogs come from ai-tools open-assets JSON (optional **`bundledOnly`**).
|
|
242
|
+
|
|
243
|
+
Gateway exports the model orchestrator from `@x12i/ai-tools` ≥ **2.5.0** (`resolveInvokeModel`, …) — see [AI_TOOLS_INVOKE_MODEL_RESOLUTION_ORCHESTRATOR_SPEC.md](./docs/upstream-reports/AI_TOOLS_INVOKE_MODEL_RESOLUTION_ORCHESTRATOR_SPEC.md).
|
|
244
|
+
|
|
245
|
+
Gateway billing helpers (also exported): `resolveCostCompletionWithAiTools`, `buildGatewayPricingRecord`, `catalogPricingSucceeded`, `ensureInvokeBillingCostStatus`, `buildTraceUsageSummary`, `enrichTraceAttemptsWithBilling`.
|
|
219
246
|
|
|
220
247
|
---
|
|
221
248
|
|
|
@@ -246,9 +273,9 @@ Mongo env: `MONGO_URI` + `MONGO_LOGS_DB` or `MONGO_DB`.
|
|
|
246
273
|
|
|
247
274
|
## Response metadata and cost
|
|
248
275
|
|
|
249
|
-
On every successful **`invoke()`**:
|
|
276
|
+
On every successful **`invoke()`** and **`invokeChat()`**:
|
|
250
277
|
|
|
251
|
-
- **`metadata.provider`**, **`modelUsed`**, **`maxTokensRequested`**, **`effectiveModelConfig`**
|
|
278
|
+
- **`metadata.provider`**, **`modelUsed`**, **`maxTokensRequested`**, **`effectiveModelConfig`** (invoke only)
|
|
252
279
|
- **`metadata.tokens`**, **`costStatus`**, **`costUsd`** when usage exists and pricing applies
|
|
253
280
|
|
|
254
281
|
Full contract: [AI Gateway invoke execution metadata](./docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md).
|
|
@@ -276,7 +303,7 @@ Adds **`metadata.attempts`**, **`metadata.usage`**, **`metadata.requestIds`**, a
|
|
|
276
303
|
|
|
277
304
|
Set via constructor `mode` or env `mode` / `MODE`. **Downstream hosts should document and expose `mode`** so graph/skill callers know resolution behavior.
|
|
278
305
|
|
|
279
|
-
Every mode requires an explicit **`model`** on the request
|
|
306
|
+
Every mode requires an explicit **`model`** on the request (concrete catalog id when `aiTools.modelsOnly` is true). Unknown models throw `ModelResolutionError`. Profile keys only work when `aiTools.modelsOnly: false`.
|
|
280
307
|
|
|
281
308
|
---
|
|
282
309
|
|
|
@@ -291,7 +318,7 @@ Every mode requires an explicit **`model`** on the request. Unresolved catalog p
|
|
|
291
318
|
| `npm run test:flex-md-esm-regression` | ESM build regression for flex-md |
|
|
292
319
|
| `npm run test:prepublish` | `build` + `npm test` |
|
|
293
320
|
|
|
294
|
-
Live tests use `LIVE_TEST_PROVIDER` / `LIVE_TEST_MODEL` (default `openrouter` + `
|
|
321
|
+
Live tests use `LIVE_TEST_PROVIDER` / `LIVE_TEST_MODEL` (default `openrouter` + `openai/gpt-4o-mini`). Set `LIVE_TEST_PROFILES=1` to run legacy profile-key invokes. Set `LIVE_SKIP_INVOKE=1` to skip the LLM call.
|
|
295
322
|
|
|
296
323
|
---
|
|
297
324
|
|
|
@@ -1,44 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* @x12i/ai-tools invoke client bootstrap for the gateway.
|
|
3
|
+
* Model resolution orchestration lives in ai-tools ≥ 2.5.0 (`resolveInvokeModel`).
|
|
3
4
|
*/
|
|
4
|
-
import {
|
|
5
|
+
import { type AiToolsInvokeClient, type ModelResolutionSuccess, type OpenRouterRoutingConfig } from '@x12i/ai-tools';
|
|
5
6
|
import type { Logxer } from '@x12i/logxer';
|
|
6
|
-
import type {
|
|
7
|
-
export type AiToolsClientBundle =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
import type { GatewayConfig } from './types.js';
|
|
8
|
+
export type AiToolsClientBundle = AiToolsInvokeClient;
|
|
9
|
+
export { resolveInvokeModel, applyOpenRouterInvokePolicy, buildInvokeModelResolverOptions, enrichModelResolutionError, mapResolutionToRouterConfig, ModelProfileUnroutableError, ModelProfileInputRejectedError, MODEL_PROFILE_UNROUTABLE, getAiToolsInvokeClient, resetAiToolsInvokeClientForTests as resetAiToolsInvokeClientForTestsUpstream, createAiToolsInvokeClient, } from '@x12i/ai-tools';
|
|
10
|
+
export { resolveOpenRouterApiKey, resolvePreferOpenRouter, readPreferOpenRouterFromEnv } from './openrouter-routing.js';
|
|
11
|
+
export type { InvokeModelResolutionDiagnostics, InvokeModelResolutionInput, InvokeModelResolutionOptions, InvokeModelResolutionResult, InvokeRouterConfigSlice, AiToolsInvokeClient, } from '@x12i/ai-tools';
|
|
12
|
+
/** @deprecated Use buildInvokeModelResolverOptions */
|
|
13
|
+
export declare function buildModelResolverOptions(config: GatewayConfig, routingEnv?: OpenRouterRoutingConfig): import("@x12i/ai-tools").ModelResolverOptions;
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
15
|
+
* @deprecated Use mapResolutionToRouterConfig from @x12i/ai-tools
|
|
14
16
|
*/
|
|
15
|
-
export declare function
|
|
17
|
+
export declare function applyModelResolution(merged: {
|
|
18
|
+
provider?: string;
|
|
19
|
+
model?: string;
|
|
20
|
+
}, resolution: ModelResolutionSuccess, gatewayDefaultEngine?: string, inputModel?: string): void;
|
|
16
21
|
/**
|
|
17
22
|
* Returns catalog + calculator, or null when disabled or bootstrap fails.
|
|
18
23
|
*/
|
|
19
24
|
export declare function getAiToolsClient(config: GatewayConfig, logger: Logxer): Promise<AiToolsClientBundle | null>;
|
|
20
25
|
/** Reset singleton (tests). */
|
|
21
26
|
export declare function resetAiToolsClientForTests(): void;
|
|
22
|
-
/**
|
|
23
|
-
* Map catalog resolution to router `{ provider, model }` (agnostic to openrouter vs vendor input).
|
|
24
|
-
*/
|
|
25
|
-
export declare function applyModelResolution(merged: NonNullable<ChatRequest['config']>, resolution: ModelResolutionSuccess, gatewayDefaultEngine?: string, inputModel?: string): void;
|
|
26
|
-
type RouterConfigSlice = {
|
|
27
|
-
provider?: string;
|
|
28
|
-
model?: string;
|
|
29
|
-
allowOpenRouterProxy?: boolean;
|
|
30
|
-
providerProxy?: string;
|
|
31
|
-
};
|
|
32
|
-
type ModelResolutionMeta = {
|
|
33
|
-
routedViaOpenRouter?: boolean;
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Router invoke flags after mergeConfig + ai-tools resolution (OpenRouter vs direct transport).
|
|
37
|
-
*/
|
|
38
|
-
export declare function applyOpenRouterInvokePolicy(merged: RouterConfigSlice, options: {
|
|
39
|
-
openRouterApiKey?: string;
|
|
40
|
-
preferOpenRouter?: boolean;
|
|
41
|
-
routingEnv?: OpenRouterRoutingConfig;
|
|
42
|
-
resolution?: ModelResolutionMeta;
|
|
43
|
-
}): void;
|
|
44
|
-
export {};
|
package/dist/ai-tools-client.js
CHANGED
|
@@ -1,26 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* @x12i/ai-tools invoke client bootstrap for the gateway.
|
|
3
|
+
* Model resolution orchestration lives in ai-tools ≥ 2.5.0 (`resolveInvokeModel`).
|
|
3
4
|
*/
|
|
4
|
-
import {
|
|
5
|
+
import { getAiToolsInvokeClient, resetAiToolsInvokeClientForTests as resetAiToolsInvokeClientForTestsUpstream, mapResolutionToRouterConfig, buildInvokeModelResolverOptions, CostCalculator, } from '@x12i/ai-tools';
|
|
5
6
|
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
6
7
|
import { resolvePreferOpenRouter } from './openrouter-routing.js';
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
export { resolveInvokeModel, applyOpenRouterInvokePolicy, buildInvokeModelResolverOptions, enrichModelResolutionError, mapResolutionToRouterConfig, ModelProfileUnroutableError, ModelProfileInputRejectedError, MODEL_PROFILE_UNROUTABLE, getAiToolsInvokeClient, resetAiToolsInvokeClientForTests as resetAiToolsInvokeClientForTestsUpstream, createAiToolsInvokeClient, } from '@x12i/ai-tools';
|
|
9
|
+
export { resolveOpenRouterApiKey, resolvePreferOpenRouter, readPreferOpenRouterFromEnv } from './openrouter-routing.js';
|
|
9
10
|
let bootstrapFailedLogged = false;
|
|
10
|
-
function
|
|
11
|
-
return
|
|
11
|
+
function invokeClientOptions(config) {
|
|
12
|
+
return {
|
|
13
|
+
cacheTtlMs: config.aiTools?.cacheTtlMs,
|
|
14
|
+
...(config.aiTools?.bundledOnly ? { bundledOnly: true } : {}),
|
|
15
|
+
...(config.aiTools?.costIncludeBreakdown ? { costIncludeBreakdown: true } : {}),
|
|
16
|
+
cacheKey: `${config.aiTools?.cacheTtlMs ?? ''}:${config.aiTools?.costIncludeBreakdown ?? ''}:${config.aiTools?.bundledOnly ?? ''}:${config.aiTools?.catalogLane ?? ''}`,
|
|
17
|
+
};
|
|
12
18
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const env = routingEnv ?? loadOpenRouterRoutingEnv();
|
|
18
|
-
const prefer = resolvePreferOpenRouter(config);
|
|
19
|
+
function withCatalogLaneCalculator(client, config) {
|
|
20
|
+
const lane = config.aiTools?.catalogLane;
|
|
21
|
+
if (!lane)
|
|
22
|
+
return client;
|
|
19
23
|
return {
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
...client,
|
|
25
|
+
calculator: new CostCalculator(client.catalog, {
|
|
26
|
+
...(config.aiTools?.costIncludeBreakdown ? { includeBreakdown: true } : {}),
|
|
27
|
+
resolverOptions: buildInvokeModelResolverOptions({
|
|
28
|
+
routingEnv: client.routingEnv,
|
|
29
|
+
catalogLane: lane
|
|
30
|
+
})
|
|
31
|
+
})
|
|
22
32
|
};
|
|
23
33
|
}
|
|
34
|
+
/** @deprecated Use buildInvokeModelResolverOptions */
|
|
35
|
+
export function buildModelResolverOptions(config, routingEnv) {
|
|
36
|
+
return buildInvokeModelResolverOptions({
|
|
37
|
+
routingEnv,
|
|
38
|
+
preferOpenRouter: resolvePreferOpenRouter(config),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated Use mapResolutionToRouterConfig from @x12i/ai-tools
|
|
43
|
+
*/
|
|
44
|
+
export function applyModelResolution(merged, resolution, gatewayDefaultEngine, inputModel) {
|
|
45
|
+
const mapped = mapResolutionToRouterConfig(resolution, { provider: merged.provider, model: inputModel ?? merged.model ?? '' }, gatewayDefaultEngine);
|
|
46
|
+
merged.provider = mapped.provider;
|
|
47
|
+
merged.model = mapped.model;
|
|
48
|
+
}
|
|
24
49
|
/**
|
|
25
50
|
* Returns catalog + calculator, or null when disabled or bootstrap fails.
|
|
26
51
|
*/
|
|
@@ -28,102 +53,27 @@ export async function getAiToolsClient(config, logger) {
|
|
|
28
53
|
if (config.aiTools?.enabled === false) {
|
|
29
54
|
return null;
|
|
30
55
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
sharedClientPromise = null;
|
|
44
|
-
sharedConfigKey = undefined;
|
|
45
|
-
bootstrapFailedLogged = false;
|
|
46
|
-
}
|
|
47
|
-
async function bootstrapAiTools(config, logger) {
|
|
48
|
-
try {
|
|
49
|
-
const routingEnv = loadOpenRouterRoutingEnv();
|
|
50
|
-
const catalog = new AiModelsCatalogClient({
|
|
51
|
-
cacheTtlMs: config.aiTools?.cacheTtlMs,
|
|
52
|
-
...(config.aiTools?.bundledOnly ? { bundledOnly: true } : {}),
|
|
53
|
-
resolverOptions: { routingEnv },
|
|
54
|
-
});
|
|
55
|
-
const calculator = new CostCalculator(catalog, {
|
|
56
|
-
includeBreakdown: config.aiTools?.costIncludeBreakdown === true,
|
|
57
|
-
});
|
|
56
|
+
const client = await getAiToolsInvokeClient(invokeClientOptions(config), {
|
|
57
|
+
warn: (msg, err) => {
|
|
58
|
+
if (!bootstrapFailedLogged) {
|
|
59
|
+
bootstrapFailedLogged = true;
|
|
60
|
+
logger.warn(msg, withActivityIdentity(undefined, {
|
|
61
|
+
error: err instanceof Error ? err.message : String(err),
|
|
62
|
+
debugKind: gatewayLogDebug.anomaly,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
if (client) {
|
|
58
68
|
logger.debug('ai-tools catalog client ready', {
|
|
59
69
|
debugKind: gatewayLogDebug.state,
|
|
60
70
|
});
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
if (!bootstrapFailedLogged) {
|
|
65
|
-
bootstrapFailedLogged = true;
|
|
66
|
-
logger.warn('ai-tools catalog bootstrap failed; model resolution and catalog cost calculation disabled', withActivityIdentity(undefined, {
|
|
67
|
-
error: error instanceof Error ? error.message : String(error),
|
|
68
|
-
debugKind: gatewayLogDebug.anomaly,
|
|
69
|
-
}));
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Map catalog resolution to router `{ provider, model }` (agnostic to openrouter vs vendor input).
|
|
76
|
-
*/
|
|
77
|
-
export function applyModelResolution(merged, resolution, gatewayDefaultEngine, inputModel) {
|
|
78
|
-
const ref = resolveModelVendorFromResolution(resolution, inputModel ?? merged.model ?? '', {
|
|
79
|
-
asOpenRouter: resolution.routedViaOpenRouter,
|
|
80
|
-
});
|
|
81
|
-
if (ref) {
|
|
82
|
-
merged.provider = ref.provider;
|
|
83
|
-
merged.model = ref.model;
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (resolution.routedViaOpenRouter) {
|
|
87
|
-
merged.provider = 'openrouter';
|
|
88
|
-
merged.model = resolution.modelId;
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const slash = resolution.modelId.indexOf('/');
|
|
92
|
-
if (slash > 0) {
|
|
93
|
-
merged.provider = resolution.record?.providerId ?? resolution.modelId.slice(0, slash);
|
|
94
|
-
merged.model = resolution.modelId.slice(slash + 1);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
merged.model = resolution.modelId;
|
|
98
|
-
if (resolution.record?.providerId) {
|
|
99
|
-
merged.provider = resolution.record.providerId;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (!merged.provider && gatewayDefaultEngine) {
|
|
103
|
-
merged.provider = gatewayDefaultEngine;
|
|
71
|
+
return withCatalogLaneCalculator(client, config);
|
|
104
72
|
}
|
|
73
|
+
return client;
|
|
105
74
|
}
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (!options.openRouterApiKey?.trim())
|
|
111
|
-
return;
|
|
112
|
-
const routingEnv = options.routingEnv ?? loadOpenRouterRoutingEnv();
|
|
113
|
-
const viaOpenRouter = options.resolution?.routedViaOpenRouter === true ||
|
|
114
|
-
(options.resolution?.routedViaOpenRouter !== false &&
|
|
115
|
-
isEffectiveOpenRouterTransport(routingEnv, {
|
|
116
|
-
provider: merged.provider,
|
|
117
|
-
modelId: merged.model,
|
|
118
|
-
routeViaOpenRouter: options.preferOpenRouter ? true : undefined,
|
|
119
|
-
}));
|
|
120
|
-
if (viaOpenRouter) {
|
|
121
|
-
merged.allowOpenRouterProxy = true;
|
|
122
|
-
if (merged.provider && merged.provider !== 'openrouter') {
|
|
123
|
-
merged.providerProxy = 'openrouter';
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
merged.allowOpenRouterProxy = false;
|
|
128
|
-
}
|
|
75
|
+
/** Reset singleton (tests). */
|
|
76
|
+
export function resetAiToolsClientForTests() {
|
|
77
|
+
resetAiToolsInvokeClientForTestsUpstream();
|
|
78
|
+
bootstrapFailedLogged = false;
|
|
129
79
|
}
|
package/dist/gateway-utils.d.ts
CHANGED
|
@@ -16,7 +16,10 @@ export declare function ensureTaskTypeId(request: ChatRequest, logger: Logxer):
|
|
|
16
16
|
export type MergeConfigOptions = {
|
|
17
17
|
catalog?: AiModelsCatalogClient | null;
|
|
18
18
|
routingEnv?: OpenRouterRoutingConfig;
|
|
19
|
+
openRouterApiKey?: string;
|
|
20
|
+
preferOpenRouter?: boolean;
|
|
19
21
|
};
|
|
22
|
+
export { MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, } from '@x12i/ai-tools';
|
|
20
23
|
/**
|
|
21
24
|
* Merges config with defaults
|
|
22
25
|
* Supports using internal system action defaults (internalSkill or skillAudit) when useInternalDefaults is set
|
|
@@ -91,13 +94,38 @@ export type ResolveCostCompletionOptions = {
|
|
|
91
94
|
calculator?: CostCalculator | null;
|
|
92
95
|
calculateCost?: boolean;
|
|
93
96
|
};
|
|
94
|
-
/**
|
|
97
|
+
/** Optional cache/reasoning token fields for catalog pricing records. */
|
|
98
|
+
export type InvokeUsageExtras = {
|
|
99
|
+
cached?: number;
|
|
100
|
+
cacheWrite?: number;
|
|
101
|
+
reasoning?: number;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Best-effort cache/reasoning token counts from router usage buckets
|
|
105
|
+
* (for {@link buildGatewayPricingRecord} / ai-tools {@link CostCalculator.calculateFromRecord}).
|
|
106
|
+
*/
|
|
107
|
+
export declare function extractUsageExtrasFromRouterResponse(routerResponse: unknown): InvokeUsageExtras;
|
|
108
|
+
/**
|
|
109
|
+
* Whether ai-tools catalog pricing is authoritative enough for Step B (`priced`).
|
|
110
|
+
* Matches the generic engine contract: authoritative catalog hit with finite cost ≥ 0.
|
|
111
|
+
*/
|
|
112
|
+
export declare function catalogPricingSucceeded(result: AiCostResult): boolean;
|
|
113
|
+
/** Record shape for {@link CostCalculator.calculateFromRecord} (shared engine contract). */
|
|
95
114
|
export declare function buildGatewayPricingRecord(routerResponse: unknown, tokens: {
|
|
96
115
|
prompt: number;
|
|
97
116
|
completion: number;
|
|
98
117
|
total: number;
|
|
99
118
|
}, mergedConfig?: unknown): Record<string, unknown>;
|
|
100
119
|
export declare function mapAiCostResultToResolvedActivityCost(base: ResolvedActivityCost, result: AiCostResult): ResolvedActivityCost;
|
|
120
|
+
/**
|
|
121
|
+
* G8 safety net: token usage without a billing signal → `unpriced`.
|
|
122
|
+
* Used at invoke boundaries after {@link resolveCostCompletionWithAiTools}.
|
|
123
|
+
*/
|
|
124
|
+
export declare function ensureInvokeBillingCostStatus(billing: ResolvedActivityCost, tokens: {
|
|
125
|
+
prompt: number;
|
|
126
|
+
completion: number;
|
|
127
|
+
total: number;
|
|
128
|
+
}): ResolvedActivityCost;
|
|
101
129
|
/**
|
|
102
130
|
* Router cost passthrough, then optional @x12i/ai-tools catalog pricing when still unpriced.
|
|
103
131
|
*/
|
|
@@ -149,14 +177,6 @@ export declare function pickEffectiveModelConfigFromInvokeRequest(request: Pick<
|
|
|
149
177
|
*/
|
|
150
178
|
export declare function tryExtractRouterLikePayloadFromErrorChain(error: unknown, maxDepth?: number): unknown;
|
|
151
179
|
export declare function pickRequestIdsFromRouterLike(gatewayAiRequestId: string | undefined, routerLike: unknown): GatewayTraceRequestIds | undefined;
|
|
152
|
-
/** Error code hint when a bundled profile name cannot be routed to a catalog target. */
|
|
153
|
-
export declare const MODEL_PROFILE_UNROUTABLE = "MODEL_PROFILE_UNROUTABLE";
|
|
154
|
-
export declare class ModelProfileUnroutableError extends Error {
|
|
155
|
-
readonly profileAlias: string;
|
|
156
|
-
readonly provider: string | undefined;
|
|
157
|
-
readonly code = "MODEL_PROFILE_UNROUTABLE";
|
|
158
|
-
constructor(profileAlias: string, provider: string | undefined, cause?: unknown);
|
|
159
|
-
}
|
|
160
180
|
type ModelResolutionCandidate = {
|
|
161
181
|
provider: string;
|
|
162
182
|
model: string;
|
|
@@ -175,7 +195,7 @@ export declare function mapGatewayFallbackAttemptsToRouter(attempts: GatewayFall
|
|
|
175
195
|
responsePreview?: string;
|
|
176
196
|
}>;
|
|
177
197
|
/**
|
|
178
|
-
* Log
|
|
198
|
+
* Log original input vs OpenRouter model id actually sent to the router after catalog resolution.
|
|
179
199
|
*/
|
|
180
200
|
export declare function logResolvedModelRouting(logger: Logxer, request: ChatRequest, mergedConfig: ChatRequest['config']): void;
|
|
181
201
|
/**
|