@x12i/ai-gateway 11.0.0 → 11.0.2
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.d.ts +1 -0
- package/dist/openrouter-runtime-adapter/create-openrouter-runtime-provider.js +111 -0
- package/dist/openrouter-runtime-adapter/map-gateway-request.js +7 -4
- package/dist/openrouter-runtime-adapter/register-openrouter-runtime.js +7 -2
- package/dist/openrouter-runtime-adapter/validate-server-tools.js +4 -14
- package/dist/types.d.ts +6 -0
- package/dist-cjs/gateway-utils.cjs +1 -1
- package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-provider.cjs +111 -0
- package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-provider.d.ts +1 -0
- package/dist-cjs/openrouter-runtime-adapter/map-gateway-request.cjs +7 -4
- package/dist-cjs/openrouter-runtime-adapter/register-openrouter-runtime.cjs +7 -2
- package/dist-cjs/openrouter-runtime-adapter/validate-server-tools.cjs +4 -14
- package/dist-cjs/types.d.ts +6 -0
- package/package.json +3 -3
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
|
}
|
|
@@ -5,5 +5,6 @@ export type OpenRouterRuntimeProviderOptions = {
|
|
|
5
5
|
apiKey: string;
|
|
6
6
|
logger?: Logxer;
|
|
7
7
|
runtime?: OpenRouterRuntime;
|
|
8
|
+
debugOpenRouterBody?: boolean;
|
|
8
9
|
};
|
|
9
10
|
export declare function createOpenRouterRuntimeProvider(options: OpenRouterRuntimeProviderOptions): ProviderModule;
|
|
@@ -1,7 +1,75 @@
|
|
|
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
|
+
import { redactRawOpenRouterPayload } from './map-trace.js';
|
|
5
|
+
function hasOwn(obj, key) {
|
|
6
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
7
|
+
}
|
|
8
|
+
function summarizeStreamValue(value) {
|
|
9
|
+
if (value == null || ['boolean', 'number', 'string'].includes(typeof value))
|
|
10
|
+
return value;
|
|
11
|
+
if (Array.isArray(value))
|
|
12
|
+
return `[array:${value.length}]`;
|
|
13
|
+
return `[${typeof value}]`;
|
|
14
|
+
}
|
|
15
|
+
function collectStreamFields(value, path = 'body', depth = 0, out = []) {
|
|
16
|
+
if (out.length >= 20 || depth > 8 || value == null || typeof value !== 'object')
|
|
17
|
+
return out;
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
for (let i = 0; i < value.length && out.length < 20; i += 1) {
|
|
20
|
+
collectStreamFields(value[i], `${path}[${i}]`, depth + 1, out);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
for (const [key, child] of Object.entries(value)) {
|
|
25
|
+
const childPath = `${path}.${key}`;
|
|
26
|
+
if (key === 'stream') {
|
|
27
|
+
out.push({ path: childPath, value: summarizeStreamValue(child) });
|
|
28
|
+
if (out.length >= 20)
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
collectStreamFields(child, childPath, depth + 1, out);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function summarizeServerTools(serverTools) {
|
|
36
|
+
if (!serverTools)
|
|
37
|
+
return undefined;
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const [key, value] of Object.entries(serverTools)) {
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
out[key] = value.map((item) => item.mode).join(',');
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (value && typeof value === 'object' && 'mode' in value && typeof value.mode === 'string') {
|
|
45
|
+
out[key] = value.mode;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return Object.keys(out).length ? out : undefined;
|
|
49
|
+
}
|
|
50
|
+
function summarizeCompiledTools(compiled) {
|
|
51
|
+
const tools = Array.isArray(compiled.body.tools) ? compiled.body.tools : [];
|
|
52
|
+
const toolTypes = tools
|
|
53
|
+
.map((tool) => (tool && typeof tool === 'object' && 'type' in tool ? String(tool.type) : undefined))
|
|
54
|
+
.filter((type) => typeof type === 'string');
|
|
55
|
+
return {
|
|
56
|
+
apiMode: compiled.apiMode,
|
|
57
|
+
toolCount: tools.length,
|
|
58
|
+
toolTypes,
|
|
59
|
+
bodyKeys: Object.keys(compiled.body),
|
|
60
|
+
hasTopLevelStream: hasOwn(compiled.body, 'stream'),
|
|
61
|
+
topLevelStream: compiled.body.stream,
|
|
62
|
+
streamFields: collectStreamFields(compiled.body),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function shouldDebugOpenRouterBody(options) {
|
|
66
|
+
if (options.debugOpenRouterBody === true)
|
|
67
|
+
return true;
|
|
68
|
+
const env = process.env.AI_GATEWAY_DEBUG_OPENROUTER_BODY?.toLowerCase();
|
|
69
|
+
return env === '1' || env === 'true' || env === 'yes';
|
|
70
|
+
}
|
|
4
71
|
export function createOpenRouterRuntimeProvider(options) {
|
|
72
|
+
const debugOpenRouterBody = shouldDebugOpenRouterBody(options);
|
|
5
73
|
const runtime = options.runtime ??
|
|
6
74
|
createOpenRouterRuntime({
|
|
7
75
|
apiKey: options.apiKey,
|
|
@@ -9,6 +77,34 @@ export function createOpenRouterRuntimeProvider(options) {
|
|
|
9
77
|
retry: { enabled: false },
|
|
10
78
|
onPolicyViolation: 'throw',
|
|
11
79
|
},
|
|
80
|
+
logger: options.logger
|
|
81
|
+
? {
|
|
82
|
+
debug: (event, data) => options.logger?.debug(event, data),
|
|
83
|
+
info: (event, data) => options.logger?.info(event, data),
|
|
84
|
+
warn: (event, data) => options.logger?.warn(event, data),
|
|
85
|
+
error: (event, data) => options.logger?.error(event, data),
|
|
86
|
+
}
|
|
87
|
+
: undefined,
|
|
88
|
+
hooks: options.logger
|
|
89
|
+
? {
|
|
90
|
+
beforeOpenRouterRequest: (compiled) => {
|
|
91
|
+
const summary = summarizeCompiledTools(compiled);
|
|
92
|
+
options.logger?.debug('OpenRouter runtime compiled request before fetch', {
|
|
93
|
+
...summary,
|
|
94
|
+
url: compiled.url,
|
|
95
|
+
});
|
|
96
|
+
if (debugOpenRouterBody) {
|
|
97
|
+
const redactedBody = redactRawOpenRouterPayload(compiled.body);
|
|
98
|
+
options.logger?.debug('OpenRouter runtime compiled request body before fetch', {
|
|
99
|
+
apiMode: compiled.apiMode,
|
|
100
|
+
url: compiled.url,
|
|
101
|
+
body: redactedBody,
|
|
102
|
+
bodyJson: JSON.stringify(redactedBody, null, 2),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
: undefined,
|
|
12
108
|
});
|
|
13
109
|
return {
|
|
14
110
|
name: 'openrouter',
|
|
@@ -22,6 +118,21 @@ export function createOpenRouterRuntimeProvider(options) {
|
|
|
22
118
|
throw new Error(`Unsupported OpenRouter runtime operation: ${spec.operation}`);
|
|
23
119
|
}
|
|
24
120
|
const runtimeRequest = spec.args;
|
|
121
|
+
const rawOverrideKeys = runtimeRequest.rawOpenRouterOverrides
|
|
122
|
+
? Object.keys(runtimeRequest.rawOpenRouterOverrides)
|
|
123
|
+
: [];
|
|
124
|
+
const rawOverrides = runtimeRequest.rawOpenRouterOverrides ?? {};
|
|
125
|
+
const rawOverrideHasStream = hasOwn(rawOverrides, 'stream');
|
|
126
|
+
options.logger?.debug('OpenRouter runtime request server tools', {
|
|
127
|
+
requestId: spec.requestId,
|
|
128
|
+
model: runtimeRequest.model,
|
|
129
|
+
apiMode: runtimeRequest.apiMode,
|
|
130
|
+
serverTools: summarizeServerTools(runtimeRequest.serverTools),
|
|
131
|
+
rawOverrideKeys,
|
|
132
|
+
rawOverrideHasStream,
|
|
133
|
+
rawOverrideStream: rawOverrides.stream,
|
|
134
|
+
rawOverridesToolsPresent: Object.prototype.hasOwnProperty.call(rawOverrides, 'tools'),
|
|
135
|
+
});
|
|
25
136
|
try {
|
|
26
137
|
const response = await runtime.run(runtimeRequest);
|
|
27
138
|
if (response.status !== 'completed') {
|
|
@@ -22,14 +22,17 @@ function buildGenerationOverrides(config) {
|
|
|
22
22
|
overrides.stop = config.stop;
|
|
23
23
|
return overrides;
|
|
24
24
|
}
|
|
25
|
+
function buildRawOpenRouterOverrides(generationOverrides, trustedRaw) {
|
|
26
|
+
const merged = { ...generationOverrides, ...trustedRaw };
|
|
27
|
+
return Object.keys(merged).length ? merged : undefined;
|
|
28
|
+
}
|
|
25
29
|
export function mapGatewayRequestToRuntimeRequest(request, exec) {
|
|
26
30
|
const config = request.config ?? {};
|
|
27
31
|
const openrouter = config.openrouter;
|
|
28
32
|
const generationOverrides = buildGenerationOverrides(config);
|
|
29
33
|
const trustedRaw = openrouter?.rawOverrides ?? {};
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
: undefined;
|
|
34
|
+
const serverTools = mapGatewayServerTools(config.serverTools);
|
|
35
|
+
const rawOpenRouterOverrides = buildRawOpenRouterOverrides(generationOverrides, trustedRaw);
|
|
33
36
|
const aiRequestId = (typeof request.aiRequestId === 'string' && request.aiRequestId) ||
|
|
34
37
|
(typeof request.identity?.aiRequestId === 'string' ? request.identity.aiRequestId : undefined);
|
|
35
38
|
return {
|
|
@@ -42,7 +45,7 @@ export function mapGatewayRequestToRuntimeRequest(request, exec) {
|
|
|
42
45
|
reasoning: config.reasoning != null && typeof config.reasoning === 'object'
|
|
43
46
|
? config.reasoning
|
|
44
47
|
: undefined,
|
|
45
|
-
serverTools
|
|
48
|
+
serverTools,
|
|
46
49
|
execution: exec?.timeoutMs ? { timeoutMs: exec.timeoutMs } : undefined,
|
|
47
50
|
rawOpenRouterOverrides,
|
|
48
51
|
metadata: {
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { createOpenRouterRuntimeAdapter } from './create-openrouter-runtime-adapter.js';
|
|
2
2
|
import { createOpenRouterRuntimeProvider } from './create-openrouter-runtime-provider.js';
|
|
3
3
|
export function registerOpenRouterRuntime(router, options) {
|
|
4
|
-
const { apiKey, logger } = options;
|
|
5
|
-
const provider = createOpenRouterRuntimeProvider({
|
|
4
|
+
const { apiKey, logger, runtimeConfig } = options;
|
|
5
|
+
const provider = createOpenRouterRuntimeProvider({
|
|
6
|
+
apiKey,
|
|
7
|
+
logger,
|
|
8
|
+
debugOpenRouterBody: runtimeConfig?.debugOpenRouterBody,
|
|
9
|
+
});
|
|
6
10
|
router.registerProvider(provider);
|
|
7
11
|
const adapter = createOpenRouterRuntimeAdapter();
|
|
8
12
|
router.getAdapterRegistry().register(adapter);
|
|
9
13
|
logger?.info('Registered OpenRouter runtime provider and adapter', {
|
|
10
14
|
provider: 'openrouter',
|
|
11
15
|
runtimeEnabled: true,
|
|
16
|
+
debugOpenRouterBody: runtimeConfig?.debugOpenRouterBody,
|
|
12
17
|
});
|
|
13
18
|
}
|
|
14
19
|
export { shouldUseOpenRouterRuntime, resolveOpenRouterRuntimeDefaults } from './should-use-openrouter-runtime.js';
|
|
@@ -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/dist/types.d.ts
CHANGED
|
@@ -176,6 +176,12 @@ export interface GatewayOpenRouterRuntimeConfig {
|
|
|
176
176
|
enabled?: boolean;
|
|
177
177
|
apiMode?: 'chat' | 'responses' | 'auto';
|
|
178
178
|
includeRawInTrace?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Emit the exact compiled OpenRouter request body at debug level before fetch.
|
|
181
|
+
* Intended for short-lived diagnostics; request prompts/content are included.
|
|
182
|
+
* Can also be enabled with AI_GATEWAY_DEBUG_OPENROUTER_BODY=1.
|
|
183
|
+
*/
|
|
184
|
+
debugOpenRouterBody?: boolean;
|
|
179
185
|
}
|
|
180
186
|
export interface GatewayServerToolUsage {
|
|
181
187
|
requested: boolean;
|
|
@@ -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,7 +1,75 @@
|
|
|
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
|
+
import { redactRawOpenRouterPayload } from './map-trace.js';
|
|
5
|
+
function hasOwn(obj, key) {
|
|
6
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
7
|
+
}
|
|
8
|
+
function summarizeStreamValue(value) {
|
|
9
|
+
if (value == null || ['boolean', 'number', 'string'].includes(typeof value))
|
|
10
|
+
return value;
|
|
11
|
+
if (Array.isArray(value))
|
|
12
|
+
return `[array:${value.length}]`;
|
|
13
|
+
return `[${typeof value}]`;
|
|
14
|
+
}
|
|
15
|
+
function collectStreamFields(value, path = 'body', depth = 0, out = []) {
|
|
16
|
+
if (out.length >= 20 || depth > 8 || value == null || typeof value !== 'object')
|
|
17
|
+
return out;
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
for (let i = 0; i < value.length && out.length < 20; i += 1) {
|
|
20
|
+
collectStreamFields(value[i], `${path}[${i}]`, depth + 1, out);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
for (const [key, child] of Object.entries(value)) {
|
|
25
|
+
const childPath = `${path}.${key}`;
|
|
26
|
+
if (key === 'stream') {
|
|
27
|
+
out.push({ path: childPath, value: summarizeStreamValue(child) });
|
|
28
|
+
if (out.length >= 20)
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
collectStreamFields(child, childPath, depth + 1, out);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function summarizeServerTools(serverTools) {
|
|
36
|
+
if (!serverTools)
|
|
37
|
+
return undefined;
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const [key, value] of Object.entries(serverTools)) {
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
out[key] = value.map((item) => item.mode).join(',');
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (value && typeof value === 'object' && 'mode' in value && typeof value.mode === 'string') {
|
|
45
|
+
out[key] = value.mode;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return Object.keys(out).length ? out : undefined;
|
|
49
|
+
}
|
|
50
|
+
function summarizeCompiledTools(compiled) {
|
|
51
|
+
const tools = Array.isArray(compiled.body.tools) ? compiled.body.tools : [];
|
|
52
|
+
const toolTypes = tools
|
|
53
|
+
.map((tool) => (tool && typeof tool === 'object' && 'type' in tool ? String(tool.type) : undefined))
|
|
54
|
+
.filter((type) => typeof type === 'string');
|
|
55
|
+
return {
|
|
56
|
+
apiMode: compiled.apiMode,
|
|
57
|
+
toolCount: tools.length,
|
|
58
|
+
toolTypes,
|
|
59
|
+
bodyKeys: Object.keys(compiled.body),
|
|
60
|
+
hasTopLevelStream: hasOwn(compiled.body, 'stream'),
|
|
61
|
+
topLevelStream: compiled.body.stream,
|
|
62
|
+
streamFields: collectStreamFields(compiled.body),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function shouldDebugOpenRouterBody(options) {
|
|
66
|
+
if (options.debugOpenRouterBody === true)
|
|
67
|
+
return true;
|
|
68
|
+
const env = process.env.AI_GATEWAY_DEBUG_OPENROUTER_BODY?.toLowerCase();
|
|
69
|
+
return env === '1' || env === 'true' || env === 'yes';
|
|
70
|
+
}
|
|
4
71
|
export function createOpenRouterRuntimeProvider(options) {
|
|
72
|
+
const debugOpenRouterBody = shouldDebugOpenRouterBody(options);
|
|
5
73
|
const runtime = options.runtime ??
|
|
6
74
|
createOpenRouterRuntime({
|
|
7
75
|
apiKey: options.apiKey,
|
|
@@ -9,6 +77,34 @@ export function createOpenRouterRuntimeProvider(options) {
|
|
|
9
77
|
retry: { enabled: false },
|
|
10
78
|
onPolicyViolation: 'throw',
|
|
11
79
|
},
|
|
80
|
+
logger: options.logger
|
|
81
|
+
? {
|
|
82
|
+
debug: (event, data) => options.logger?.debug(event, data),
|
|
83
|
+
info: (event, data) => options.logger?.info(event, data),
|
|
84
|
+
warn: (event, data) => options.logger?.warn(event, data),
|
|
85
|
+
error: (event, data) => options.logger?.error(event, data),
|
|
86
|
+
}
|
|
87
|
+
: undefined,
|
|
88
|
+
hooks: options.logger
|
|
89
|
+
? {
|
|
90
|
+
beforeOpenRouterRequest: (compiled) => {
|
|
91
|
+
const summary = summarizeCompiledTools(compiled);
|
|
92
|
+
options.logger?.debug('OpenRouter runtime compiled request before fetch', {
|
|
93
|
+
...summary,
|
|
94
|
+
url: compiled.url,
|
|
95
|
+
});
|
|
96
|
+
if (debugOpenRouterBody) {
|
|
97
|
+
const redactedBody = redactRawOpenRouterPayload(compiled.body);
|
|
98
|
+
options.logger?.debug('OpenRouter runtime compiled request body before fetch', {
|
|
99
|
+
apiMode: compiled.apiMode,
|
|
100
|
+
url: compiled.url,
|
|
101
|
+
body: redactedBody,
|
|
102
|
+
bodyJson: JSON.stringify(redactedBody, null, 2),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
: undefined,
|
|
12
108
|
});
|
|
13
109
|
return {
|
|
14
110
|
name: 'openrouter',
|
|
@@ -22,6 +118,21 @@ export function createOpenRouterRuntimeProvider(options) {
|
|
|
22
118
|
throw new Error(`Unsupported OpenRouter runtime operation: ${spec.operation}`);
|
|
23
119
|
}
|
|
24
120
|
const runtimeRequest = spec.args;
|
|
121
|
+
const rawOverrideKeys = runtimeRequest.rawOpenRouterOverrides
|
|
122
|
+
? Object.keys(runtimeRequest.rawOpenRouterOverrides)
|
|
123
|
+
: [];
|
|
124
|
+
const rawOverrides = runtimeRequest.rawOpenRouterOverrides ?? {};
|
|
125
|
+
const rawOverrideHasStream = hasOwn(rawOverrides, 'stream');
|
|
126
|
+
options.logger?.debug('OpenRouter runtime request server tools', {
|
|
127
|
+
requestId: spec.requestId,
|
|
128
|
+
model: runtimeRequest.model,
|
|
129
|
+
apiMode: runtimeRequest.apiMode,
|
|
130
|
+
serverTools: summarizeServerTools(runtimeRequest.serverTools),
|
|
131
|
+
rawOverrideKeys,
|
|
132
|
+
rawOverrideHasStream,
|
|
133
|
+
rawOverrideStream: rawOverrides.stream,
|
|
134
|
+
rawOverridesToolsPresent: Object.prototype.hasOwnProperty.call(rawOverrides, 'tools'),
|
|
135
|
+
});
|
|
25
136
|
try {
|
|
26
137
|
const response = await runtime.run(runtimeRequest);
|
|
27
138
|
if (response.status !== 'completed') {
|
|
@@ -5,5 +5,6 @@ export type OpenRouterRuntimeProviderOptions = {
|
|
|
5
5
|
apiKey: string;
|
|
6
6
|
logger?: Logxer;
|
|
7
7
|
runtime?: OpenRouterRuntime;
|
|
8
|
+
debugOpenRouterBody?: boolean;
|
|
8
9
|
};
|
|
9
10
|
export declare function createOpenRouterRuntimeProvider(options: OpenRouterRuntimeProviderOptions): ProviderModule;
|
|
@@ -22,14 +22,17 @@ function buildGenerationOverrides(config) {
|
|
|
22
22
|
overrides.stop = config.stop;
|
|
23
23
|
return overrides;
|
|
24
24
|
}
|
|
25
|
+
function buildRawOpenRouterOverrides(generationOverrides, trustedRaw) {
|
|
26
|
+
const merged = { ...generationOverrides, ...trustedRaw };
|
|
27
|
+
return Object.keys(merged).length ? merged : undefined;
|
|
28
|
+
}
|
|
25
29
|
export function mapGatewayRequestToRuntimeRequest(request, exec) {
|
|
26
30
|
const config = request.config ?? {};
|
|
27
31
|
const openrouter = config.openrouter;
|
|
28
32
|
const generationOverrides = buildGenerationOverrides(config);
|
|
29
33
|
const trustedRaw = openrouter?.rawOverrides ?? {};
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
: undefined;
|
|
34
|
+
const serverTools = mapGatewayServerTools(config.serverTools);
|
|
35
|
+
const rawOpenRouterOverrides = buildRawOpenRouterOverrides(generationOverrides, trustedRaw);
|
|
33
36
|
const aiRequestId = (typeof request.aiRequestId === 'string' && request.aiRequestId) ||
|
|
34
37
|
(typeof request.identity?.aiRequestId === 'string' ? request.identity.aiRequestId : undefined);
|
|
35
38
|
return {
|
|
@@ -42,7 +45,7 @@ export function mapGatewayRequestToRuntimeRequest(request, exec) {
|
|
|
42
45
|
reasoning: config.reasoning != null && typeof config.reasoning === 'object'
|
|
43
46
|
? config.reasoning
|
|
44
47
|
: undefined,
|
|
45
|
-
serverTools
|
|
48
|
+
serverTools,
|
|
46
49
|
execution: exec?.timeoutMs ? { timeoutMs: exec.timeoutMs } : undefined,
|
|
47
50
|
rawOpenRouterOverrides,
|
|
48
51
|
metadata: {
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { createOpenRouterRuntimeAdapter } from './create-openrouter-runtime-adapter.js';
|
|
2
2
|
import { createOpenRouterRuntimeProvider } from './create-openrouter-runtime-provider.js';
|
|
3
3
|
export function registerOpenRouterRuntime(router, options) {
|
|
4
|
-
const { apiKey, logger } = options;
|
|
5
|
-
const provider = createOpenRouterRuntimeProvider({
|
|
4
|
+
const { apiKey, logger, runtimeConfig } = options;
|
|
5
|
+
const provider = createOpenRouterRuntimeProvider({
|
|
6
|
+
apiKey,
|
|
7
|
+
logger,
|
|
8
|
+
debugOpenRouterBody: runtimeConfig?.debugOpenRouterBody,
|
|
9
|
+
});
|
|
6
10
|
router.registerProvider(provider);
|
|
7
11
|
const adapter = createOpenRouterRuntimeAdapter();
|
|
8
12
|
router.getAdapterRegistry().register(adapter);
|
|
9
13
|
logger?.info('Registered OpenRouter runtime provider and adapter', {
|
|
10
14
|
provider: 'openrouter',
|
|
11
15
|
runtimeEnabled: true,
|
|
16
|
+
debugOpenRouterBody: runtimeConfig?.debugOpenRouterBody,
|
|
12
17
|
});
|
|
13
18
|
}
|
|
14
19
|
export { shouldUseOpenRouterRuntime, resolveOpenRouterRuntimeDefaults } from './should-use-openrouter-runtime.js';
|
|
@@ -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/dist-cjs/types.d.ts
CHANGED
|
@@ -176,6 +176,12 @@ export interface GatewayOpenRouterRuntimeConfig {
|
|
|
176
176
|
enabled?: boolean;
|
|
177
177
|
apiMode?: 'chat' | 'responses' | 'auto';
|
|
178
178
|
includeRawInTrace?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Emit the exact compiled OpenRouter request body at debug level before fetch.
|
|
181
|
+
* Intended for short-lived diagnostics; request prompts/content are included.
|
|
182
|
+
* Can also be enabled with AI_GATEWAY_DEBUG_OPENROUTER_BODY=1.
|
|
183
|
+
*/
|
|
184
|
+
debugOpenRouterBody?: boolean;
|
|
179
185
|
}
|
|
180
186
|
export interface GatewayServerToolUsage {
|
|
181
187
|
requested: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x12i/ai-gateway",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.2",
|
|
4
4
|
"description": "AI Gateway - Unified interface for LLM provider routing and management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -44,11 +44,11 @@
|
|
|
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.1",
|
|
48
48
|
"@x12i/ai-tools": "^3.3.5",
|
|
49
49
|
"@x12i/flex-md": "^4.8.0",
|
|
50
50
|
"@x12i/logxer": "^5.1.0",
|
|
51
|
-
"@x12i/openrouter-runtime": "^1.0.
|
|
51
|
+
"@x12i/openrouter-runtime": "^1.0.5",
|
|
52
52
|
"@x12i/rendrix": "^4.3.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|