@x12i/ai-gateway 11.0.0 → 11.0.1
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 +3 -3
- package/dist/gateway-utils.js +1 -1
- package/dist/openrouter-runtime-adapter/create-openrouter-runtime-provider.js +54 -0
- package/dist/openrouter-runtime-adapter/map-gateway-request.js +20 -4
- package/dist/openrouter-runtime-adapter/validate-server-tools.js +4 -14
- package/dist-cjs/gateway-utils.cjs +1 -1
- package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-provider.cjs +54 -0
- package/dist-cjs/openrouter-runtime-adapter/map-gateway-request.cjs +20 -4
- package/dist-cjs/openrouter-runtime-adapter/validate-server-tools.cjs +4 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -463,14 +463,14 @@ At invoke, gateway merges server-tool intent in this order (later wins):
|
|
|
463
463
|
2. `request.config.serverTools` / `request.config.openrouter`
|
|
464
464
|
3. `request.modelConfig.serverTools` / `request.modelConfig.openrouter`
|
|
465
465
|
|
|
466
|
-
Gateway then applies **post-routing policy** internally (`applyPostRoutingServerToolsPolicy`):
|
|
466
|
+
Gateway then applies **post-routing policy** internally (`applyPostRoutingServerToolsPolicy`): any active server tool (`allowed` or `required`) forces `provider=openrouter` so tools are sent in the OpenRouter request. **Do not duplicate post-routing policy in consumers** — merge pre-invoke intent only.
|
|
467
467
|
|
|
468
468
|
### Stable error codes
|
|
469
469
|
|
|
470
470
|
| Code | When |
|
|
471
471
|
|------|------|
|
|
472
|
-
| `SERVER_TOOL_REQUIRES_OPENROUTER_PROVIDER` | Not thrown — gateway **forces** `provider=openrouter` when
|
|
473
|
-
| `OPENROUTER_SERVER_TOOL_REQUIRES_KEY` |
|
|
472
|
+
| `SERVER_TOOL_REQUIRES_OPENROUTER_PROVIDER` | Not thrown — gateway **forces** `provider=openrouter` when active server tools are configured and a key is present |
|
|
473
|
+
| `OPENROUTER_SERVER_TOOL_REQUIRES_KEY` | Active server tools but no OpenRouter key (`codeAliases` includes `OPENROUTER_SERVER_TOOL_REQUIRES_OPENROUTER_KEY`) |
|
|
474
474
|
| `WEB_SEARCH_REQUIRED_BUT_NOT_USED`, etc. | From `@x12i/openrouter-runtime` policy → `GatewayPolicyViolationError.code` and `error.metadata.openrouterRuntime.policyViolations` |
|
|
475
475
|
| `CITATIONS_REQUIRED_BUT_MISSING` | `@x12i/openrouter-runtime` ≥ **1.0.3** policy when `webSearch.requireCitations: true` (or global default) and search used with no citations |
|
|
476
476
|
| `APPLY_PATCH_REQUIRES_RESPONSES_API` | `applyPatch` with `openrouter.apiMode: 'chat'` |
|
package/dist/gateway-utils.js
CHANGED
|
@@ -209,7 +209,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
209
209
|
});
|
|
210
210
|
if (serverToolsPolicy.forceOpenRouter) {
|
|
211
211
|
merged.provider = 'openrouter';
|
|
212
|
-
logger.verbose('
|
|
212
|
+
logger.verbose('OpenRouter server tool forced provider=openrouter', {
|
|
213
213
|
jobId: request.identity.jobId,
|
|
214
214
|
});
|
|
215
215
|
}
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import { createOpenRouterRuntime, RuntimeConfigError, OpenRouterHttpError, } from '@x12i/openrouter-runtime';
|
|
2
2
|
import { OPENROUTER_RUNTIME_OPERATION } from './map-gateway-request.js';
|
|
3
3
|
import { throwMappedOpenRouterHttpError, throwMappedRuntimeConfigError, throwMappedRuntimeResponseErrors, } from './map-runtime-errors.js';
|
|
4
|
+
function summarizeServerTools(serverTools) {
|
|
5
|
+
if (!serverTools)
|
|
6
|
+
return undefined;
|
|
7
|
+
const out = {};
|
|
8
|
+
for (const [key, value] of Object.entries(serverTools)) {
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
out[key] = value.map((item) => item.mode).join(',');
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (value && typeof value === 'object' && 'mode' in value && typeof value.mode === 'string') {
|
|
14
|
+
out[key] = value.mode;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return Object.keys(out).length ? out : undefined;
|
|
18
|
+
}
|
|
19
|
+
function summarizeCompiledTools(compiled) {
|
|
20
|
+
const tools = Array.isArray(compiled.body.tools) ? compiled.body.tools : [];
|
|
21
|
+
const toolTypes = tools
|
|
22
|
+
.map((tool) => (tool && typeof tool === 'object' && 'type' in tool ? String(tool.type) : undefined))
|
|
23
|
+
.filter((type) => typeof type === 'string');
|
|
24
|
+
return {
|
|
25
|
+
apiMode: compiled.apiMode,
|
|
26
|
+
toolCount: tools.length,
|
|
27
|
+
toolTypes,
|
|
28
|
+
bodyKeys: Object.keys(compiled.body),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
4
31
|
export function createOpenRouterRuntimeProvider(options) {
|
|
5
32
|
const runtime = options.runtime ??
|
|
6
33
|
createOpenRouterRuntime({
|
|
@@ -9,6 +36,22 @@ export function createOpenRouterRuntimeProvider(options) {
|
|
|
9
36
|
retry: { enabled: false },
|
|
10
37
|
onPolicyViolation: 'throw',
|
|
11
38
|
},
|
|
39
|
+
logger: options.logger
|
|
40
|
+
? {
|
|
41
|
+
debug: (event, data) => options.logger?.debug(event, data),
|
|
42
|
+
info: (event, data) => options.logger?.info(event, data),
|
|
43
|
+
warn: (event, data) => options.logger?.warn(event, data),
|
|
44
|
+
error: (event, data) => options.logger?.error(event, data),
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
47
|
+
hooks: options.logger
|
|
48
|
+
? {
|
|
49
|
+
beforeOpenRouterRequest: (compiled) => {
|
|
50
|
+
const summary = summarizeCompiledTools(compiled);
|
|
51
|
+
options.logger?.debug('OpenRouter runtime compiled request tools', summary);
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
: undefined,
|
|
12
55
|
});
|
|
13
56
|
return {
|
|
14
57
|
name: 'openrouter',
|
|
@@ -22,6 +65,17 @@ export function createOpenRouterRuntimeProvider(options) {
|
|
|
22
65
|
throw new Error(`Unsupported OpenRouter runtime operation: ${spec.operation}`);
|
|
23
66
|
}
|
|
24
67
|
const runtimeRequest = spec.args;
|
|
68
|
+
const rawOverrideKeys = runtimeRequest.rawOpenRouterOverrides
|
|
69
|
+
? Object.keys(runtimeRequest.rawOpenRouterOverrides)
|
|
70
|
+
: [];
|
|
71
|
+
options.logger?.debug('OpenRouter runtime request server tools', {
|
|
72
|
+
requestId: spec.requestId,
|
|
73
|
+
model: runtimeRequest.model,
|
|
74
|
+
apiMode: runtimeRequest.apiMode,
|
|
75
|
+
serverTools: summarizeServerTools(runtimeRequest.serverTools),
|
|
76
|
+
rawOverrideKeys,
|
|
77
|
+
rawOverridesToolsPresent: Object.prototype.hasOwnProperty.call(runtimeRequest.rawOpenRouterOverrides ?? {}, 'tools'),
|
|
78
|
+
});
|
|
25
79
|
try {
|
|
26
80
|
const response = await runtime.run(runtimeRequest);
|
|
27
81
|
if (response.status !== 'completed') {
|
|
@@ -22,14 +22,30 @@ function buildGenerationOverrides(config) {
|
|
|
22
22
|
overrides.stop = config.stop;
|
|
23
23
|
return overrides;
|
|
24
24
|
}
|
|
25
|
+
function hasActiveServerTools(serverTools) {
|
|
26
|
+
if (!serverTools)
|
|
27
|
+
return false;
|
|
28
|
+
return Object.values(serverTools).some((value) => {
|
|
29
|
+
if (Array.isArray(value))
|
|
30
|
+
return value.some((item) => item.mode !== 'disabled');
|
|
31
|
+
return !!value && value.mode !== 'disabled';
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function buildRawOpenRouterOverrides(generationOverrides, trustedRaw, serverTools) {
|
|
35
|
+
const raw = { ...trustedRaw };
|
|
36
|
+
if (hasActiveServerTools(serverTools)) {
|
|
37
|
+
delete raw.tools;
|
|
38
|
+
}
|
|
39
|
+
const merged = { ...generationOverrides, ...raw };
|
|
40
|
+
return Object.keys(merged).length ? merged : undefined;
|
|
41
|
+
}
|
|
25
42
|
export function mapGatewayRequestToRuntimeRequest(request, exec) {
|
|
26
43
|
const config = request.config ?? {};
|
|
27
44
|
const openrouter = config.openrouter;
|
|
28
45
|
const generationOverrides = buildGenerationOverrides(config);
|
|
29
46
|
const trustedRaw = openrouter?.rawOverrides ?? {};
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
: undefined;
|
|
47
|
+
const serverTools = mapGatewayServerTools(config.serverTools);
|
|
48
|
+
const rawOpenRouterOverrides = buildRawOpenRouterOverrides(generationOverrides, trustedRaw, serverTools);
|
|
33
49
|
const aiRequestId = (typeof request.aiRequestId === 'string' && request.aiRequestId) ||
|
|
34
50
|
(typeof request.identity?.aiRequestId === 'string' ? request.identity.aiRequestId : undefined);
|
|
35
51
|
return {
|
|
@@ -42,7 +58,7 @@ export function mapGatewayRequestToRuntimeRequest(request, exec) {
|
|
|
42
58
|
reasoning: config.reasoning != null && typeof config.reasoning === 'object'
|
|
43
59
|
? config.reasoning
|
|
44
60
|
: undefined,
|
|
45
|
-
serverTools
|
|
61
|
+
serverTools,
|
|
46
62
|
execution: exec?.timeoutMs ? { timeoutMs: exec.timeoutMs } : undefined,
|
|
47
63
|
rawOpenRouterOverrides,
|
|
48
64
|
metadata: {
|
|
@@ -73,22 +73,12 @@ export function applyPostRoutingServerToolsPolicy(input) {
|
|
|
73
73
|
return { serverTools, warnings, forceOpenRouter: false };
|
|
74
74
|
}
|
|
75
75
|
validateApplyPatchConfig(serverTools, openrouter);
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
throw new ProviderConfigError('OPENROUTER_SERVER_TOOL_REQUIRES_KEY', 'Required OpenRouter server tools need OPENROUTER_API_KEY or GatewayConfig.openrouter.apiKey', { codeAliases: ['OPENROUTER_SERVER_TOOL_REQUIRES_OPENROUTER_KEY'] });
|
|
79
|
-
}
|
|
80
|
-
if (provider !== 'openrouter') {
|
|
81
|
-
provider = 'openrouter';
|
|
82
|
-
return { serverTools, warnings, forceOpenRouter: true };
|
|
83
|
-
}
|
|
84
|
-
return { serverTools, warnings, forceOpenRouter: false };
|
|
76
|
+
if (!openRouterApiKey) {
|
|
77
|
+
throw new ProviderConfigError('OPENROUTER_SERVER_TOOL_REQUIRES_KEY', 'OpenRouter server tools need OPENROUTER_API_KEY or GatewayConfig.openrouter.apiKey', { codeAliases: ['OPENROUTER_SERVER_TOOL_REQUIRES_OPENROUTER_KEY'] });
|
|
85
78
|
}
|
|
86
79
|
if (provider !== 'openrouter') {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
message: 'OpenRouter server tools were requested but the final provider is not openrouter; tools were stripped',
|
|
90
|
-
});
|
|
91
|
-
return { serverTools: undefined, warnings, forceOpenRouter: false };
|
|
80
|
+
provider = 'openrouter';
|
|
81
|
+
return { serverTools, warnings, forceOpenRouter: true };
|
|
92
82
|
}
|
|
93
83
|
return { serverTools, warnings, forceOpenRouter: false };
|
|
94
84
|
}
|
|
@@ -209,7 +209,7 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
209
209
|
});
|
|
210
210
|
if (serverToolsPolicy.forceOpenRouter) {
|
|
211
211
|
merged.provider = 'openrouter';
|
|
212
|
-
logger.verbose('
|
|
212
|
+
logger.verbose('OpenRouter server tool forced provider=openrouter', {
|
|
213
213
|
jobId: request.identity.jobId,
|
|
214
214
|
});
|
|
215
215
|
}
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import { createOpenRouterRuntime, RuntimeConfigError, OpenRouterHttpError, } from '@x12i/openrouter-runtime';
|
|
2
2
|
import { OPENROUTER_RUNTIME_OPERATION } from './map-gateway-request.js';
|
|
3
3
|
import { throwMappedOpenRouterHttpError, throwMappedRuntimeConfigError, throwMappedRuntimeResponseErrors, } from './map-runtime-errors.js';
|
|
4
|
+
function summarizeServerTools(serverTools) {
|
|
5
|
+
if (!serverTools)
|
|
6
|
+
return undefined;
|
|
7
|
+
const out = {};
|
|
8
|
+
for (const [key, value] of Object.entries(serverTools)) {
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
out[key] = value.map((item) => item.mode).join(',');
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (value && typeof value === 'object' && 'mode' in value && typeof value.mode === 'string') {
|
|
14
|
+
out[key] = value.mode;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return Object.keys(out).length ? out : undefined;
|
|
18
|
+
}
|
|
19
|
+
function summarizeCompiledTools(compiled) {
|
|
20
|
+
const tools = Array.isArray(compiled.body.tools) ? compiled.body.tools : [];
|
|
21
|
+
const toolTypes = tools
|
|
22
|
+
.map((tool) => (tool && typeof tool === 'object' && 'type' in tool ? String(tool.type) : undefined))
|
|
23
|
+
.filter((type) => typeof type === 'string');
|
|
24
|
+
return {
|
|
25
|
+
apiMode: compiled.apiMode,
|
|
26
|
+
toolCount: tools.length,
|
|
27
|
+
toolTypes,
|
|
28
|
+
bodyKeys: Object.keys(compiled.body),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
4
31
|
export function createOpenRouterRuntimeProvider(options) {
|
|
5
32
|
const runtime = options.runtime ??
|
|
6
33
|
createOpenRouterRuntime({
|
|
@@ -9,6 +36,22 @@ export function createOpenRouterRuntimeProvider(options) {
|
|
|
9
36
|
retry: { enabled: false },
|
|
10
37
|
onPolicyViolation: 'throw',
|
|
11
38
|
},
|
|
39
|
+
logger: options.logger
|
|
40
|
+
? {
|
|
41
|
+
debug: (event, data) => options.logger?.debug(event, data),
|
|
42
|
+
info: (event, data) => options.logger?.info(event, data),
|
|
43
|
+
warn: (event, data) => options.logger?.warn(event, data),
|
|
44
|
+
error: (event, data) => options.logger?.error(event, data),
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
47
|
+
hooks: options.logger
|
|
48
|
+
? {
|
|
49
|
+
beforeOpenRouterRequest: (compiled) => {
|
|
50
|
+
const summary = summarizeCompiledTools(compiled);
|
|
51
|
+
options.logger?.debug('OpenRouter runtime compiled request tools', summary);
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
: undefined,
|
|
12
55
|
});
|
|
13
56
|
return {
|
|
14
57
|
name: 'openrouter',
|
|
@@ -22,6 +65,17 @@ export function createOpenRouterRuntimeProvider(options) {
|
|
|
22
65
|
throw new Error(`Unsupported OpenRouter runtime operation: ${spec.operation}`);
|
|
23
66
|
}
|
|
24
67
|
const runtimeRequest = spec.args;
|
|
68
|
+
const rawOverrideKeys = runtimeRequest.rawOpenRouterOverrides
|
|
69
|
+
? Object.keys(runtimeRequest.rawOpenRouterOverrides)
|
|
70
|
+
: [];
|
|
71
|
+
options.logger?.debug('OpenRouter runtime request server tools', {
|
|
72
|
+
requestId: spec.requestId,
|
|
73
|
+
model: runtimeRequest.model,
|
|
74
|
+
apiMode: runtimeRequest.apiMode,
|
|
75
|
+
serverTools: summarizeServerTools(runtimeRequest.serverTools),
|
|
76
|
+
rawOverrideKeys,
|
|
77
|
+
rawOverridesToolsPresent: Object.prototype.hasOwnProperty.call(runtimeRequest.rawOpenRouterOverrides ?? {}, 'tools'),
|
|
78
|
+
});
|
|
25
79
|
try {
|
|
26
80
|
const response = await runtime.run(runtimeRequest);
|
|
27
81
|
if (response.status !== 'completed') {
|
|
@@ -22,14 +22,30 @@ function buildGenerationOverrides(config) {
|
|
|
22
22
|
overrides.stop = config.stop;
|
|
23
23
|
return overrides;
|
|
24
24
|
}
|
|
25
|
+
function hasActiveServerTools(serverTools) {
|
|
26
|
+
if (!serverTools)
|
|
27
|
+
return false;
|
|
28
|
+
return Object.values(serverTools).some((value) => {
|
|
29
|
+
if (Array.isArray(value))
|
|
30
|
+
return value.some((item) => item.mode !== 'disabled');
|
|
31
|
+
return !!value && value.mode !== 'disabled';
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function buildRawOpenRouterOverrides(generationOverrides, trustedRaw, serverTools) {
|
|
35
|
+
const raw = { ...trustedRaw };
|
|
36
|
+
if (hasActiveServerTools(serverTools)) {
|
|
37
|
+
delete raw.tools;
|
|
38
|
+
}
|
|
39
|
+
const merged = { ...generationOverrides, ...raw };
|
|
40
|
+
return Object.keys(merged).length ? merged : undefined;
|
|
41
|
+
}
|
|
25
42
|
export function mapGatewayRequestToRuntimeRequest(request, exec) {
|
|
26
43
|
const config = request.config ?? {};
|
|
27
44
|
const openrouter = config.openrouter;
|
|
28
45
|
const generationOverrides = buildGenerationOverrides(config);
|
|
29
46
|
const trustedRaw = openrouter?.rawOverrides ?? {};
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
: undefined;
|
|
47
|
+
const serverTools = mapGatewayServerTools(config.serverTools);
|
|
48
|
+
const rawOpenRouterOverrides = buildRawOpenRouterOverrides(generationOverrides, trustedRaw, serverTools);
|
|
33
49
|
const aiRequestId = (typeof request.aiRequestId === 'string' && request.aiRequestId) ||
|
|
34
50
|
(typeof request.identity?.aiRequestId === 'string' ? request.identity.aiRequestId : undefined);
|
|
35
51
|
return {
|
|
@@ -42,7 +58,7 @@ export function mapGatewayRequestToRuntimeRequest(request, exec) {
|
|
|
42
58
|
reasoning: config.reasoning != null && typeof config.reasoning === 'object'
|
|
43
59
|
? config.reasoning
|
|
44
60
|
: undefined,
|
|
45
|
-
serverTools
|
|
61
|
+
serverTools,
|
|
46
62
|
execution: exec?.timeoutMs ? { timeoutMs: exec.timeoutMs } : undefined,
|
|
47
63
|
rawOpenRouterOverrides,
|
|
48
64
|
metadata: {
|
|
@@ -73,22 +73,12 @@ export function applyPostRoutingServerToolsPolicy(input) {
|
|
|
73
73
|
return { serverTools, warnings, forceOpenRouter: false };
|
|
74
74
|
}
|
|
75
75
|
validateApplyPatchConfig(serverTools, openrouter);
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
throw new ProviderConfigError('OPENROUTER_SERVER_TOOL_REQUIRES_KEY', 'Required OpenRouter server tools need OPENROUTER_API_KEY or GatewayConfig.openrouter.apiKey', { codeAliases: ['OPENROUTER_SERVER_TOOL_REQUIRES_OPENROUTER_KEY'] });
|
|
79
|
-
}
|
|
80
|
-
if (provider !== 'openrouter') {
|
|
81
|
-
provider = 'openrouter';
|
|
82
|
-
return { serverTools, warnings, forceOpenRouter: true };
|
|
83
|
-
}
|
|
84
|
-
return { serverTools, warnings, forceOpenRouter: false };
|
|
76
|
+
if (!openRouterApiKey) {
|
|
77
|
+
throw new ProviderConfigError('OPENROUTER_SERVER_TOOL_REQUIRES_KEY', 'OpenRouter server tools need OPENROUTER_API_KEY or GatewayConfig.openrouter.apiKey', { codeAliases: ['OPENROUTER_SERVER_TOOL_REQUIRES_OPENROUTER_KEY'] });
|
|
85
78
|
}
|
|
86
79
|
if (provider !== 'openrouter') {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
message: 'OpenRouter server tools were requested but the final provider is not openrouter; tools were stripped',
|
|
90
|
-
});
|
|
91
|
-
return { serverTools: undefined, warnings, forceOpenRouter: false };
|
|
80
|
+
provider = 'openrouter';
|
|
81
|
+
return { serverTools, warnings, forceOpenRouter: true };
|
|
92
82
|
}
|
|
93
83
|
return { serverTools, warnings, forceOpenRouter: false };
|
|
94
84
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x12i/ai-gateway",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.1",
|
|
4
4
|
"description": "AI Gateway - Unified interface for LLM provider routing and management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@x12i/activix": "^9.0.2",
|
|
46
46
|
"@x12i/ai-profiles": "^3.4.1",
|
|
47
|
-
"@x12i/ai-providers-router": "^4.
|
|
47
|
+
"@x12i/ai-providers-router": "^4.11.0",
|
|
48
48
|
"@x12i/ai-tools": "^3.3.5",
|
|
49
49
|
"@x12i/flex-md": "^4.8.0",
|
|
50
50
|
"@x12i/logxer": "^5.1.0",
|