@x12i/ai-gateway 10.3.1 → 10.4.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 +94 -2
- package/dist/activity-manager.js +33 -0
- package/dist/gateway-config.js +15 -0
- package/dist/gateway-utils.d.ts +8 -1
- package/dist/gateway-utils.js +73 -1
- package/dist/gateway.js +18 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/instruction-errors.d.ts +21 -0
- package/dist/instruction-errors.js +36 -0
- package/dist/openrouter-runtime-adapter/create-openrouter-runtime-adapter.d.ts +23 -0
- package/dist/openrouter-runtime-adapter/create-openrouter-runtime-adapter.js +31 -0
- package/dist/openrouter-runtime-adapter/create-openrouter-runtime-provider.d.ts +9 -0
- package/dist/openrouter-runtime-adapter/create-openrouter-runtime-provider.js +41 -0
- package/dist/openrouter-runtime-adapter/index.d.ts +8 -0
- package/dist/openrouter-runtime-adapter/index.js +8 -0
- package/dist/openrouter-runtime-adapter/map-gateway-request.d.ts +26 -0
- package/dist/openrouter-runtime-adapter/map-gateway-request.js +62 -0
- package/dist/openrouter-runtime-adapter/map-runtime-errors.d.ts +5 -0
- package/dist/openrouter-runtime-adapter/map-runtime-errors.js +60 -0
- package/dist/openrouter-runtime-adapter/map-runtime-response.d.ts +31 -0
- package/dist/openrouter-runtime-adapter/map-runtime-response.js +153 -0
- package/dist/openrouter-runtime-adapter/map-server-tools.d.ts +3 -0
- package/dist/openrouter-runtime-adapter/map-server-tools.js +143 -0
- package/dist/openrouter-runtime-adapter/map-trace.d.ts +5 -0
- package/dist/openrouter-runtime-adapter/map-trace.js +45 -0
- package/dist/openrouter-runtime-adapter/register-openrouter-runtime.d.ts +10 -0
- package/dist/openrouter-runtime-adapter/register-openrouter-runtime.js +14 -0
- package/dist/openrouter-runtime-adapter/should-use-openrouter-runtime.d.ts +14 -0
- package/dist/openrouter-runtime-adapter/should-use-openrouter-runtime.js +29 -0
- package/dist/openrouter-runtime-adapter/validate-server-tools.d.ts +30 -0
- package/dist/openrouter-runtime-adapter/validate-server-tools.js +151 -0
- package/dist/types.d.ts +234 -0
- package/dist-cjs/activity-manager.cjs +33 -0
- package/dist-cjs/gateway-config.cjs +15 -0
- package/dist-cjs/gateway-utils.cjs +73 -1
- package/dist-cjs/gateway-utils.d.ts +8 -1
- package/dist-cjs/gateway.cjs +18 -3
- package/dist-cjs/index.cjs +3 -2
- package/dist-cjs/index.d.ts +4 -3
- package/dist-cjs/instruction-errors.cjs +36 -0
- package/dist-cjs/instruction-errors.d.ts +21 -0
- package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-adapter.cjs +31 -0
- package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-adapter.d.ts +23 -0
- package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-provider.cjs +41 -0
- package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-provider.d.ts +9 -0
- package/dist-cjs/openrouter-runtime-adapter/index.cjs +8 -0
- package/dist-cjs/openrouter-runtime-adapter/index.d.ts +8 -0
- package/dist-cjs/openrouter-runtime-adapter/map-gateway-request.cjs +62 -0
- package/dist-cjs/openrouter-runtime-adapter/map-gateway-request.d.ts +26 -0
- package/dist-cjs/openrouter-runtime-adapter/map-runtime-errors.cjs +60 -0
- package/dist-cjs/openrouter-runtime-adapter/map-runtime-errors.d.ts +5 -0
- package/dist-cjs/openrouter-runtime-adapter/map-runtime-response.cjs +153 -0
- package/dist-cjs/openrouter-runtime-adapter/map-runtime-response.d.ts +31 -0
- package/dist-cjs/openrouter-runtime-adapter/map-server-tools.cjs +143 -0
- package/dist-cjs/openrouter-runtime-adapter/map-server-tools.d.ts +3 -0
- package/dist-cjs/openrouter-runtime-adapter/map-trace.cjs +45 -0
- package/dist-cjs/openrouter-runtime-adapter/map-trace.d.ts +5 -0
- package/dist-cjs/openrouter-runtime-adapter/register-openrouter-runtime.cjs +14 -0
- package/dist-cjs/openrouter-runtime-adapter/register-openrouter-runtime.d.ts +10 -0
- package/dist-cjs/openrouter-runtime-adapter/should-use-openrouter-runtime.cjs +29 -0
- package/dist-cjs/openrouter-runtime-adapter/should-use-openrouter-runtime.d.ts +14 -0
- package/dist-cjs/openrouter-runtime-adapter/validate-server-tools.cjs +151 -0
- package/dist-cjs/openrouter-runtime-adapter/validate-server-tools.d.ts +30 -0
- package/dist-cjs/types.d.ts +234 -0
- package/package.json +3 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { GatewayValidationError, ProviderConfigError, } from '../instruction-errors.js';
|
|
2
|
+
const TOOL_KEYS = [
|
|
3
|
+
'webSearch',
|
|
4
|
+
'webFetch',
|
|
5
|
+
'datetime',
|
|
6
|
+
'imageGeneration',
|
|
7
|
+
'applyPatch',
|
|
8
|
+
'fusion',
|
|
9
|
+
'advisor',
|
|
10
|
+
'subagent',
|
|
11
|
+
];
|
|
12
|
+
function getToolMode(serverTools, key) {
|
|
13
|
+
const tool = serverTools?.[key];
|
|
14
|
+
if (!tool)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (Array.isArray(tool)) {
|
|
17
|
+
return tool.some((t) => t.mode === 'required')
|
|
18
|
+
? 'required'
|
|
19
|
+
: tool.some((t) => t.mode === 'allowed')
|
|
20
|
+
? 'allowed'
|
|
21
|
+
: 'disabled';
|
|
22
|
+
}
|
|
23
|
+
return tool.mode;
|
|
24
|
+
}
|
|
25
|
+
export function hasAnyServerToolMode(serverTools, mode) {
|
|
26
|
+
if (!serverTools)
|
|
27
|
+
return false;
|
|
28
|
+
for (const key of TOOL_KEYS) {
|
|
29
|
+
const m = getToolMode(serverTools, key);
|
|
30
|
+
if (m === mode)
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
export function hasAnyActiveServerTool(serverTools) {
|
|
36
|
+
if (!serverTools)
|
|
37
|
+
return false;
|
|
38
|
+
for (const key of TOOL_KEYS) {
|
|
39
|
+
const m = getToolMode(serverTools, key);
|
|
40
|
+
if (m === 'allowed' || m === 'required')
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
export function hasRequiredServerTool(serverTools) {
|
|
46
|
+
return hasAnyServerToolMode(serverTools, 'required');
|
|
47
|
+
}
|
|
48
|
+
function resolveEffectiveApiMode(openrouter, serverTools) {
|
|
49
|
+
if (openrouter?.apiMode)
|
|
50
|
+
return openrouter.apiMode;
|
|
51
|
+
const applyPatch = serverTools?.applyPatch;
|
|
52
|
+
if (applyPatch && applyPatch.mode !== 'disabled')
|
|
53
|
+
return 'auto';
|
|
54
|
+
return 'auto';
|
|
55
|
+
}
|
|
56
|
+
export function validateApplyPatchConfig(serverTools, openrouter) {
|
|
57
|
+
const applyPatch = serverTools?.applyPatch;
|
|
58
|
+
if (!applyPatch || applyPatch.mode === 'disabled')
|
|
59
|
+
return;
|
|
60
|
+
if (applyPatch.behavior === 'apply_with_callback') {
|
|
61
|
+
throw new GatewayValidationError('PATCH_APPLIER_REQUIRED', 'applyPatch behavior apply_with_callback is not supported by ai-gateway; use return_only');
|
|
62
|
+
}
|
|
63
|
+
const apiMode = resolveEffectiveApiMode(openrouter, serverTools);
|
|
64
|
+
if (apiMode === 'chat') {
|
|
65
|
+
throw new GatewayValidationError('APPLY_PATCH_REQUIRES_RESPONSES_API', 'applyPatch requires openrouter.apiMode = "responses" or auto mode resolving to responses');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function applyPostRoutingServerToolsPolicy(input) {
|
|
69
|
+
const warnings = [];
|
|
70
|
+
const { serverTools, openrouter, openRouterApiKey } = input;
|
|
71
|
+
let provider = input.provider?.toLowerCase();
|
|
72
|
+
if (!hasAnyActiveServerTool(serverTools)) {
|
|
73
|
+
return { serverTools, warnings, forceOpenRouter: false };
|
|
74
|
+
}
|
|
75
|
+
validateApplyPatchConfig(serverTools, openrouter);
|
|
76
|
+
if (hasRequiredServerTool(serverTools)) {
|
|
77
|
+
if (!openRouterApiKey) {
|
|
78
|
+
throw new ProviderConfigError('OPENROUTER_SERVER_TOOL_REQUIRES_KEY', 'Required OpenRouter server tools need OPENROUTER_API_KEY or GatewayConfig.openrouter.apiKey');
|
|
79
|
+
}
|
|
80
|
+
if (provider !== 'openrouter') {
|
|
81
|
+
provider = 'openrouter';
|
|
82
|
+
return { serverTools, warnings, forceOpenRouter: true };
|
|
83
|
+
}
|
|
84
|
+
return { serverTools, warnings, forceOpenRouter: false };
|
|
85
|
+
}
|
|
86
|
+
if (provider !== 'openrouter') {
|
|
87
|
+
warnings.push({
|
|
88
|
+
code: 'SERVER_TOOLS_IGNORED_FOR_NON_OPENROUTER_PROVIDER',
|
|
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 };
|
|
92
|
+
}
|
|
93
|
+
return { serverTools, warnings, forceOpenRouter: false };
|
|
94
|
+
}
|
|
95
|
+
export function applyOnlineVariantMigration(model, serverTools) {
|
|
96
|
+
if (!model.endsWith(':online')) {
|
|
97
|
+
return { model, serverTools };
|
|
98
|
+
}
|
|
99
|
+
const stripped = model.slice(0, -':online'.length);
|
|
100
|
+
const merged = { ...(serverTools ?? {}) };
|
|
101
|
+
if (!merged.webSearch) {
|
|
102
|
+
merged.webSearch = { mode: 'allowed' };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
model: stripped,
|
|
106
|
+
serverTools: merged,
|
|
107
|
+
warning: {
|
|
108
|
+
code: 'OPENROUTER_ONLINE_VARIANT_DEPRECATED',
|
|
109
|
+
message: `Model suffix :online is deprecated; stripped to "${stripped}" and enabled webSearch.mode=allowed`,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export function mergeServerToolsConfig(...layers) {
|
|
114
|
+
const out = {};
|
|
115
|
+
for (const layer of layers) {
|
|
116
|
+
if (!layer)
|
|
117
|
+
continue;
|
|
118
|
+
for (const key of TOOL_KEYS) {
|
|
119
|
+
const val = layer[key];
|
|
120
|
+
if (val !== undefined) {
|
|
121
|
+
out[key] = val;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return Object.keys(out).length ? out : undefined;
|
|
126
|
+
}
|
|
127
|
+
export function mergeOpenRouterConfig(...layers) {
|
|
128
|
+
const out = {};
|
|
129
|
+
for (const layer of layers) {
|
|
130
|
+
if (!layer)
|
|
131
|
+
continue;
|
|
132
|
+
if (layer.apiMode !== undefined)
|
|
133
|
+
out.apiMode = layer.apiMode;
|
|
134
|
+
if (layer.includeRawInTrace !== undefined)
|
|
135
|
+
out.includeRawInTrace = layer.includeRawInTrace;
|
|
136
|
+
if (layer.rawOverrides !== undefined) {
|
|
137
|
+
out.rawOverrides = { ...(out.rawOverrides ?? {}), ...layer.rawOverrides };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return Object.keys(out).length ? out : undefined;
|
|
141
|
+
}
|
|
142
|
+
/** Default applyPatch behavior when not specified. */
|
|
143
|
+
export function normalizeApplyPatchDefaults(serverTools) {
|
|
144
|
+
if (!serverTools?.applyPatch)
|
|
145
|
+
return serverTools;
|
|
146
|
+
const applyPatch = {
|
|
147
|
+
behavior: 'return_only',
|
|
148
|
+
...serverTools.applyPatch,
|
|
149
|
+
};
|
|
150
|
+
return { ...serverTools, applyPatch };
|
|
151
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -51,6 +51,202 @@ export type GatewayTraceRequestIds = {
|
|
|
51
51
|
/** Allow additional stable ids without breaking contract. */
|
|
52
52
|
[key: string]: string | undefined;
|
|
53
53
|
};
|
|
54
|
+
export type GatewayServerToolMode = 'disabled' | 'allowed' | 'required';
|
|
55
|
+
export type GatewayNestedServerTool = {
|
|
56
|
+
type: 'openrouter:web_search';
|
|
57
|
+
parameters?: Record<string, unknown>;
|
|
58
|
+
} | {
|
|
59
|
+
type: 'openrouter:web_fetch';
|
|
60
|
+
parameters?: Record<string, unknown>;
|
|
61
|
+
} | {
|
|
62
|
+
type: 'openrouter:datetime';
|
|
63
|
+
parameters?: Record<string, unknown>;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'openrouter:image_generation';
|
|
66
|
+
parameters?: Record<string, unknown>;
|
|
67
|
+
};
|
|
68
|
+
export interface GatewayWebSearchToolConfig {
|
|
69
|
+
mode: GatewayServerToolMode;
|
|
70
|
+
engine?: 'auto' | 'native' | 'exa' | 'firecrawl' | 'parallel' | 'perplexity';
|
|
71
|
+
maxResults?: number;
|
|
72
|
+
maxTotalResults?: number;
|
|
73
|
+
searchContextSize?: 'low' | 'medium' | 'high';
|
|
74
|
+
maxCharacters?: number;
|
|
75
|
+
allowedDomains?: string[];
|
|
76
|
+
excludedDomains?: string[];
|
|
77
|
+
userLocation?: {
|
|
78
|
+
type: 'approximate';
|
|
79
|
+
city?: string;
|
|
80
|
+
region?: string;
|
|
81
|
+
country?: string;
|
|
82
|
+
timezone?: string;
|
|
83
|
+
};
|
|
84
|
+
requireCitations?: boolean;
|
|
85
|
+
onFailure?: 'fail' | 'warn' | 'continue';
|
|
86
|
+
}
|
|
87
|
+
export interface GatewayWebFetchToolConfig {
|
|
88
|
+
mode: GatewayServerToolMode;
|
|
89
|
+
engine?: 'auto' | 'native' | 'exa' | 'openrouter' | 'firecrawl' | 'parallel';
|
|
90
|
+
maxUses?: number;
|
|
91
|
+
maxContentTokens?: number;
|
|
92
|
+
allowedDomains?: string[];
|
|
93
|
+
blockedDomains?: string[];
|
|
94
|
+
onFailure?: 'fail' | 'warn' | 'continue';
|
|
95
|
+
}
|
|
96
|
+
export interface GatewayDatetimeToolConfig {
|
|
97
|
+
mode: GatewayServerToolMode;
|
|
98
|
+
timezone?: string;
|
|
99
|
+
}
|
|
100
|
+
export interface GatewayImageGenerationToolConfig {
|
|
101
|
+
mode: GatewayServerToolMode;
|
|
102
|
+
model?: string;
|
|
103
|
+
quality?: 'low' | 'medium' | 'high' | string;
|
|
104
|
+
size?: string;
|
|
105
|
+
aspectRatio?: string;
|
|
106
|
+
background?: string;
|
|
107
|
+
outputFormat?: 'png' | 'jpeg' | 'webp' | string;
|
|
108
|
+
outputCompression?: number;
|
|
109
|
+
moderation?: string;
|
|
110
|
+
}
|
|
111
|
+
export interface GatewayApplyPatchToolConfig {
|
|
112
|
+
mode: GatewayServerToolMode;
|
|
113
|
+
behavior?: 'return_only' | 'apply_with_callback';
|
|
114
|
+
allowCreate?: boolean;
|
|
115
|
+
allowUpdate?: boolean;
|
|
116
|
+
allowDelete?: boolean;
|
|
117
|
+
workspaceRoot?: string;
|
|
118
|
+
}
|
|
119
|
+
export interface GatewayFusionToolConfig {
|
|
120
|
+
mode: GatewayServerToolMode;
|
|
121
|
+
analysisModels?: string[];
|
|
122
|
+
judgeModel?: string;
|
|
123
|
+
maxToolCalls?: number;
|
|
124
|
+
maxCompletionTokens?: number;
|
|
125
|
+
temperature?: number;
|
|
126
|
+
reasoning?: {
|
|
127
|
+
effort?: 'low' | 'medium' | 'high';
|
|
128
|
+
maxTokens?: number;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export interface GatewayAdvisorToolConfig {
|
|
132
|
+
mode: GatewayServerToolMode;
|
|
133
|
+
name?: string;
|
|
134
|
+
model?: string;
|
|
135
|
+
instructions?: string;
|
|
136
|
+
tools?: GatewayNestedServerTool[];
|
|
137
|
+
forwardTranscript?: boolean;
|
|
138
|
+
stream?: boolean;
|
|
139
|
+
maxToolCalls?: number;
|
|
140
|
+
maxCompletionTokens?: number;
|
|
141
|
+
temperature?: number;
|
|
142
|
+
reasoning?: {
|
|
143
|
+
effort?: 'low' | 'medium' | 'high';
|
|
144
|
+
maxTokens?: number;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export interface GatewaySubagentToolConfig {
|
|
148
|
+
mode: GatewayServerToolMode;
|
|
149
|
+
model?: string;
|
|
150
|
+
instructions?: string;
|
|
151
|
+
tools?: GatewayNestedServerTool[];
|
|
152
|
+
maxToolCalls?: number;
|
|
153
|
+
maxCompletionTokens?: number;
|
|
154
|
+
temperature?: number;
|
|
155
|
+
reasoning?: {
|
|
156
|
+
effort?: 'low' | 'medium' | 'high';
|
|
157
|
+
maxTokens?: number;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export interface GatewayServerToolsConfig {
|
|
161
|
+
webSearch?: GatewayWebSearchToolConfig;
|
|
162
|
+
webFetch?: GatewayWebFetchToolConfig;
|
|
163
|
+
datetime?: GatewayDatetimeToolConfig;
|
|
164
|
+
imageGeneration?: GatewayImageGenerationToolConfig;
|
|
165
|
+
applyPatch?: GatewayApplyPatchToolConfig;
|
|
166
|
+
fusion?: GatewayFusionToolConfig;
|
|
167
|
+
advisor?: GatewayAdvisorToolConfig | GatewayAdvisorToolConfig[];
|
|
168
|
+
subagent?: GatewaySubagentToolConfig;
|
|
169
|
+
}
|
|
170
|
+
export interface GatewayOpenRouterConfig {
|
|
171
|
+
apiMode?: 'chat' | 'responses' | 'auto';
|
|
172
|
+
rawOverrides?: Record<string, unknown>;
|
|
173
|
+
includeRawInTrace?: boolean;
|
|
174
|
+
}
|
|
175
|
+
export interface GatewayOpenRouterRuntimeConfig {
|
|
176
|
+
enabled?: boolean;
|
|
177
|
+
apiMode?: 'chat' | 'responses' | 'auto';
|
|
178
|
+
includeRawInTrace?: boolean;
|
|
179
|
+
}
|
|
180
|
+
export interface GatewayServerToolUsage {
|
|
181
|
+
requested: boolean;
|
|
182
|
+
required: boolean;
|
|
183
|
+
used: boolean;
|
|
184
|
+
callCount?: number;
|
|
185
|
+
policyViolation?: string;
|
|
186
|
+
}
|
|
187
|
+
export interface GatewayServerToolUsageMap {
|
|
188
|
+
webSearch?: GatewayServerToolUsage;
|
|
189
|
+
webFetch?: GatewayServerToolUsage;
|
|
190
|
+
datetime?: GatewayServerToolUsage;
|
|
191
|
+
imageGeneration?: GatewayServerToolUsage;
|
|
192
|
+
applyPatch?: GatewayServerToolUsage;
|
|
193
|
+
fusion?: GatewayServerToolUsage;
|
|
194
|
+
advisor?: GatewayServerToolUsage;
|
|
195
|
+
subagent?: GatewayServerToolUsage;
|
|
196
|
+
}
|
|
197
|
+
export interface GatewayCitation {
|
|
198
|
+
type: 'url';
|
|
199
|
+
url: string;
|
|
200
|
+
title?: string;
|
|
201
|
+
excerpt?: string;
|
|
202
|
+
startIndex?: number;
|
|
203
|
+
endIndex?: number;
|
|
204
|
+
sourceProvider: 'openrouter';
|
|
205
|
+
}
|
|
206
|
+
export interface GatewayGeneratedImage {
|
|
207
|
+
imageUrl: string;
|
|
208
|
+
status: 'ok' | 'error';
|
|
209
|
+
error?: string;
|
|
210
|
+
raw?: unknown;
|
|
211
|
+
}
|
|
212
|
+
export interface GatewayPatchProposal {
|
|
213
|
+
callId: string;
|
|
214
|
+
status: 'completed' | 'failed';
|
|
215
|
+
operation: {
|
|
216
|
+
type: 'create_file';
|
|
217
|
+
path: string;
|
|
218
|
+
diff: string;
|
|
219
|
+
} | {
|
|
220
|
+
type: 'update_file';
|
|
221
|
+
path: string;
|
|
222
|
+
diff: string;
|
|
223
|
+
} | {
|
|
224
|
+
type: 'delete_file';
|
|
225
|
+
path: string;
|
|
226
|
+
};
|
|
227
|
+
raw?: unknown;
|
|
228
|
+
}
|
|
229
|
+
export interface GatewayWarning {
|
|
230
|
+
code: string;
|
|
231
|
+
message: string;
|
|
232
|
+
details?: unknown;
|
|
233
|
+
}
|
|
234
|
+
export interface GatewayPolicyViolation {
|
|
235
|
+
code: string;
|
|
236
|
+
message: string;
|
|
237
|
+
details?: unknown;
|
|
238
|
+
}
|
|
239
|
+
export interface GatewayOpenRouterRuntimeMetadata {
|
|
240
|
+
apiMode: 'chat' | 'responses';
|
|
241
|
+
warnings: GatewayWarning[];
|
|
242
|
+
policyViolations: GatewayPolicyViolation[];
|
|
243
|
+
requestIds?: string[];
|
|
244
|
+
serverToolsRequested?: GatewayServerToolUsageMap;
|
|
245
|
+
serverToolsUsed?: GatewayServerToolUsageMap;
|
|
246
|
+
citations?: GatewayCitation[];
|
|
247
|
+
generatedImages?: GatewayGeneratedImage[];
|
|
248
|
+
patchProposals?: GatewayPatchProposal[];
|
|
249
|
+
}
|
|
54
250
|
export type GatewayTraceAttempt = {
|
|
55
251
|
timing: {
|
|
56
252
|
startedAt: number;
|
|
@@ -95,6 +291,13 @@ export type GatewayTraceAttempt = {
|
|
|
95
291
|
* The exact shape is intentionally loose to avoid locking downstream to provider schemas.
|
|
96
292
|
*/
|
|
97
293
|
rawProviderPayload?: unknown;
|
|
294
|
+
openrouterRuntime?: {
|
|
295
|
+
apiMode?: 'chat' | 'responses';
|
|
296
|
+
serverTools?: GatewayServerToolUsageMap;
|
|
297
|
+
citationCount?: number;
|
|
298
|
+
generatedImageCount?: number;
|
|
299
|
+
patchProposalCount?: number;
|
|
300
|
+
};
|
|
98
301
|
};
|
|
99
302
|
/**
|
|
100
303
|
* Allowlisted merged router/generation config returned in {@link EnhancedLLMResponse.metadata}
|
|
@@ -115,6 +318,8 @@ export type GatewayTraceUsageSummary = {
|
|
|
115
318
|
cost?: number;
|
|
116
319
|
costStatus?: 'priced' | 'unpriced';
|
|
117
320
|
costBreakdown?: GatewayTraceAttempt['costBreakdown'];
|
|
321
|
+
serverTools?: GatewayServerToolUsageMap;
|
|
322
|
+
citations?: GatewayCitation[];
|
|
118
323
|
};
|
|
119
324
|
export type GatewayTraceMergedConfig = Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP' | 'frequencyPenalty' | 'presencePenalty' | 'stop'>>;
|
|
120
325
|
/**
|
|
@@ -316,11 +521,21 @@ export interface ModelConfig {
|
|
|
316
521
|
* Generation stops when any stop sequence is encountered
|
|
317
522
|
*/
|
|
318
523
|
stop?: string[];
|
|
524
|
+
/**
|
|
525
|
+
* OpenRouter-only server tools. Ignored unless final provider is openrouter.
|
|
526
|
+
*/
|
|
527
|
+
serverTools?: GatewayServerToolsConfig;
|
|
528
|
+
/**
|
|
529
|
+
* OpenRouter-specific execution controls.
|
|
530
|
+
*/
|
|
531
|
+
openrouter?: GatewayOpenRouterConfig;
|
|
319
532
|
/**
|
|
320
533
|
* Additional provider-specific parameters
|
|
321
534
|
*/
|
|
322
535
|
[key: string]: any;
|
|
323
536
|
}
|
|
537
|
+
/** Alias for {@link ModelConfig} in gateway documentation. */
|
|
538
|
+
export type GatewayModelConfig = ModelConfig;
|
|
324
539
|
export interface GatewayConfig extends Omit<RouterConfig, 'defaultEngine' | 'logger'> {
|
|
325
540
|
/**
|
|
326
541
|
* Default target (engine + model) for routing
|
|
@@ -397,6 +612,15 @@ export interface GatewayConfig extends Omit<RouterConfig, 'defaultEngine' | 'log
|
|
|
397
612
|
openRouter?: {
|
|
398
613
|
prefer?: boolean;
|
|
399
614
|
};
|
|
615
|
+
/**
|
|
616
|
+
* Default OpenRouter server tool policy merged into every invoke (lowest priority).
|
|
617
|
+
*/
|
|
618
|
+
defaultServerTools?: GatewayServerToolsConfig;
|
|
619
|
+
/**
|
|
620
|
+
* OpenRouter runtime integration defaults.
|
|
621
|
+
* @default `{ enabled: true, apiMode: 'auto', includeRawInTrace: false }`
|
|
622
|
+
*/
|
|
623
|
+
openrouterRuntime?: GatewayOpenRouterRuntimeConfig;
|
|
400
624
|
/**
|
|
401
625
|
* Operational mode override (`process.env.mode` / `MODE` when omitted; default `debug`).
|
|
402
626
|
* Downstream hosts (ai-skills, ai-tasks, graph-engine) should expose this to their clients.
|
|
@@ -1035,6 +1259,16 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
|
|
|
1035
1259
|
* Reflects gateway merge order: modelConfig / request.config / defaults.
|
|
1036
1260
|
*/
|
|
1037
1261
|
effectiveModelConfig?: Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>>;
|
|
1262
|
+
/** OpenRouter server tool usage summary (additive). */
|
|
1263
|
+
serverTools?: GatewayServerToolUsageMap;
|
|
1264
|
+
/** Citations extracted by OpenRouter runtime (additive). */
|
|
1265
|
+
citations?: GatewayCitation[];
|
|
1266
|
+
/** Generated image metadata from OpenRouter runtime (additive). */
|
|
1267
|
+
generatedImages?: GatewayGeneratedImage[];
|
|
1268
|
+
/** Patch proposals from OpenRouter runtime — never applied by the gateway (additive). */
|
|
1269
|
+
patchProposals?: GatewayPatchProposal[];
|
|
1270
|
+
/** OpenRouter runtime execution metadata (additive). */
|
|
1271
|
+
openrouterRuntime?: GatewayOpenRouterRuntimeMetadata;
|
|
1038
1272
|
/**
|
|
1039
1273
|
* Stable request/correlation identifiers across gateway/router/provider layers.
|
|
1040
1274
|
* Only populated when diagnostics trace mode is enabled.
|
|
@@ -230,6 +230,39 @@ function pickActivixCompletionRoutingMetadata(response) {
|
|
|
230
230
|
if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
|
|
231
231
|
out.effectiveModelConfig = m.effectiveModelConfig;
|
|
232
232
|
}
|
|
233
|
+
const citations = m.citations;
|
|
234
|
+
if (Array.isArray(citations) && citations.length) {
|
|
235
|
+
out.citations = citations
|
|
236
|
+
.filter((c) => c != null && typeof c === 'object')
|
|
237
|
+
.map((c) => {
|
|
238
|
+
const row = c;
|
|
239
|
+
return {
|
|
240
|
+
...(typeof row.url === 'string' ? { url: row.url } : {}),
|
|
241
|
+
...(typeof row.title === 'string' ? { title: row.title } : {}),
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
out.citationCount = citations.length;
|
|
245
|
+
}
|
|
246
|
+
const serverTools = m.serverTools;
|
|
247
|
+
const generatedImages = m.generatedImages;
|
|
248
|
+
const patchProposals = m.patchProposals;
|
|
249
|
+
const openrouterRuntime = m.openrouterRuntime;
|
|
250
|
+
if (serverTools != null ||
|
|
251
|
+
openrouterRuntime != null ||
|
|
252
|
+
(Array.isArray(generatedImages) && generatedImages.length) ||
|
|
253
|
+
(Array.isArray(patchProposals) && patchProposals.length)) {
|
|
254
|
+
out.openrouterRuntime = {
|
|
255
|
+
...(openrouterRuntime != null && typeof openrouterRuntime === 'object'
|
|
256
|
+
? {
|
|
257
|
+
apiMode: openrouterRuntime.apiMode,
|
|
258
|
+
serverTools,
|
|
259
|
+
}
|
|
260
|
+
: { serverTools }),
|
|
261
|
+
citationCount: Array.isArray(citations) ? citations.length : 0,
|
|
262
|
+
generatedImageCount: Array.isArray(generatedImages) ? generatedImages.length : 0,
|
|
263
|
+
patchProposalCount: Array.isArray(patchProposals) ? patchProposals.length : 0,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
233
266
|
}
|
|
234
267
|
}
|
|
235
268
|
return out;
|
|
@@ -11,6 +11,7 @@ import { createGatewayLogger } from './logger-factory.js';
|
|
|
11
11
|
import { ActivityManager } from './activity-manager.js';
|
|
12
12
|
import { UsageTracker } from './usage-tracker.js';
|
|
13
13
|
import { mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
14
|
+
import { registerOpenRouterRuntime, shouldUseOpenRouterRuntime, } from './openrouter-runtime-adapter/index.js';
|
|
14
15
|
/** Resolve current module directory across ESM/CJS builds. */
|
|
15
16
|
function getModuleDir() {
|
|
16
17
|
if (typeof __dirname !== 'undefined') {
|
|
@@ -167,6 +168,20 @@ export function initializeGatewayComponents(config) {
|
|
|
167
168
|
}
|
|
168
169
|
const router = new LLMProviderRouter(routerConfig);
|
|
169
170
|
setupRequestInterceptor(router, logger);
|
|
171
|
+
if (openRouterKey && shouldUseOpenRouterRuntime(config)) {
|
|
172
|
+
registerOpenRouterRuntime(router, {
|
|
173
|
+
apiKey: openRouterKey,
|
|
174
|
+
logger,
|
|
175
|
+
runtimeConfig: config.openrouterRuntime,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else if (openRouterKey) {
|
|
179
|
+
logger.debug('OpenRouter legacy provider path enabled (runtime disabled)', {
|
|
180
|
+
AI_GATEWAY_USE_OPENROUTER_RUNTIME: process.env.AI_GATEWAY_USE_OPENROUTER_RUNTIME,
|
|
181
|
+
AI_GATEWAY_USE_LEGACY_OPENROUTER: process.env.AI_GATEWAY_USE_LEGACY_OPENROUTER,
|
|
182
|
+
openrouterRuntimeEnabled: config.openrouterRuntime?.enabled,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
170
185
|
const usageTracker = new UsageTracker({
|
|
171
186
|
enableUsageTracking: config.enableUsageTracking ?? true,
|
|
172
187
|
usageTier: config.usageTier,
|
|
@@ -9,6 +9,7 @@ import { extractHttpStatusCode } from './gateway-retry.js';
|
|
|
9
9
|
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
10
10
|
import { MaxTokensRequiredError, ModelRequiredError, } from './instruction-errors.js';
|
|
11
11
|
import { normalizeInvokeModelAtIngress } from './invoke-model-ingress.js';
|
|
12
|
+
import { applyOnlineVariantMigration, applyPostRoutingServerToolsPolicy, mergeOpenRouterConfig, mergeServerToolsConfig, normalizeApplyPatchDefaults, resolveOpenRouterRuntimeDefaults, pickOpenRouterRuntimeMetadataSlice, enrichTraceOpenRouterRuntimeMetadata, } from './openrouter-runtime-adapter/index.js';
|
|
12
13
|
import { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P } from './gateway-defaults.js';
|
|
13
14
|
function getPreParsedInstructions(instructions) {
|
|
14
15
|
return instructions ?? '';
|
|
@@ -115,6 +116,31 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
115
116
|
merged.model = ingress.model;
|
|
116
117
|
originalProvider = merged.provider;
|
|
117
118
|
originalModel = merged.model;
|
|
119
|
+
const runtimeDefaults = resolveOpenRouterRuntimeDefaults(config);
|
|
120
|
+
let mergedServerTools = normalizeApplyPatchDefaults(mergeServerToolsConfig(config.defaultServerTools, request.config?.serverTools, request.modelConfig?.serverTools));
|
|
121
|
+
const mergedOpenRouter = mergeOpenRouterConfig({ apiMode: runtimeDefaults.apiMode, includeRawInTrace: runtimeDefaults.includeRawInTrace }, request.config?.openrouter, request.modelConfig?.openrouter);
|
|
122
|
+
const onlineMigration = applyOnlineVariantMigration(merged.model, mergedServerTools);
|
|
123
|
+
if (onlineMigration.model !== merged.model) {
|
|
124
|
+
merged.model = onlineMigration.model;
|
|
125
|
+
originalModel = merged.model;
|
|
126
|
+
mergedServerTools = onlineMigration.serverTools;
|
|
127
|
+
logger.warn('OpenRouter :online model variant migrated', {
|
|
128
|
+
jobId: request.identity.jobId,
|
|
129
|
+
code: onlineMigration.warning?.code,
|
|
130
|
+
message: onlineMigration.warning?.message,
|
|
131
|
+
});
|
|
132
|
+
const warnings = request._openrouterWarnings;
|
|
133
|
+
if (onlineMigration.warning) {
|
|
134
|
+
request._openrouterWarnings = [
|
|
135
|
+
...(warnings ?? []),
|
|
136
|
+
onlineMigration.warning,
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (mergedServerTools)
|
|
141
|
+
merged.serverTools = mergedServerTools;
|
|
142
|
+
if (mergedOpenRouter)
|
|
143
|
+
merged.openrouter = mergedOpenRouter;
|
|
118
144
|
if (resolveModels && mergeOptions?.catalog) {
|
|
119
145
|
try {
|
|
120
146
|
const resolved = await resolveInvokeModel({ provider: merged.provider, model: merged.model }, {
|
|
@@ -175,6 +201,34 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
|
|
|
175
201
|
merged.providerProxy = resolved.router.providerProxy;
|
|
176
202
|
}
|
|
177
203
|
}
|
|
204
|
+
const serverToolsPolicy = applyPostRoutingServerToolsPolicy({
|
|
205
|
+
provider: merged.provider,
|
|
206
|
+
serverTools: merged.serverTools,
|
|
207
|
+
openrouter: merged.openrouter,
|
|
208
|
+
openRouterApiKey: mergeOptions?.openRouterApiKey,
|
|
209
|
+
});
|
|
210
|
+
if (serverToolsPolicy.forceOpenRouter) {
|
|
211
|
+
merged.provider = 'openrouter';
|
|
212
|
+
logger.verbose('Required OpenRouter server tool forced provider=openrouter', {
|
|
213
|
+
jobId: request.identity.jobId,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (serverToolsPolicy.serverTools) {
|
|
217
|
+
merged.serverTools = serverToolsPolicy.serverTools;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
delete merged.serverTools;
|
|
221
|
+
}
|
|
222
|
+
if (serverToolsPolicy.warnings.length) {
|
|
223
|
+
const existing = request._openrouterWarnings;
|
|
224
|
+
request._openrouterWarnings = [
|
|
225
|
+
...(existing ?? []),
|
|
226
|
+
...serverToolsPolicy.warnings,
|
|
227
|
+
];
|
|
228
|
+
for (const w of serverToolsPolicy.warnings) {
|
|
229
|
+
logger.warn(w.message, { jobId: request.identity.jobId, code: w.code });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
178
232
|
if (!merged.model) {
|
|
179
233
|
throw new ModelRequiredError();
|
|
180
234
|
}
|
|
@@ -571,7 +625,7 @@ function buildTraceAttemptPricingRecord(attempt, mergedConfig) {
|
|
|
571
625
|
/**
|
|
572
626
|
* Trace-mode summary: final token usage + resolved billing (after catalog pricing when applicable).
|
|
573
627
|
*/
|
|
574
|
-
export function buildTraceUsageSummary(tokens, billing, maxTokensRequested) {
|
|
628
|
+
export function buildTraceUsageSummary(tokens, billing, maxTokensRequested, openrouterExtras) {
|
|
575
629
|
if (!hasNonZeroTokenUsage(tokens) && !billing.costStatus) {
|
|
576
630
|
return undefined;
|
|
577
631
|
}
|
|
@@ -589,8 +643,26 @@ export function buildTraceUsageSummary(tokens, billing, maxTokensRequested) {
|
|
|
589
643
|
if (billing.costBreakdown) {
|
|
590
644
|
summary.costBreakdown = billing.costBreakdown;
|
|
591
645
|
}
|
|
646
|
+
if (openrouterExtras?.serverTools)
|
|
647
|
+
summary.serverTools = openrouterExtras.serverTools;
|
|
648
|
+
if (openrouterExtras?.citations?.length)
|
|
649
|
+
summary.citations = openrouterExtras.citations;
|
|
592
650
|
return summary;
|
|
593
651
|
}
|
|
652
|
+
/**
|
|
653
|
+
* Additive OpenRouter runtime metadata for gateway responses.
|
|
654
|
+
*/
|
|
655
|
+
export function pickEnhancedOpenRouterMetadata(routerResponse, traceEnabled = false) {
|
|
656
|
+
const slice = pickOpenRouterRuntimeMetadataSlice(routerResponse);
|
|
657
|
+
if (!slice.openrouterRuntime && !slice.serverTools && !slice.citations?.length) {
|
|
658
|
+
return slice;
|
|
659
|
+
}
|
|
660
|
+
const openrouterRuntime = enrichTraceOpenRouterRuntimeMetadata(slice.openrouterRuntime, slice, traceEnabled);
|
|
661
|
+
return {
|
|
662
|
+
...slice,
|
|
663
|
+
...(openrouterRuntime ? { openrouterRuntime } : {}),
|
|
664
|
+
};
|
|
665
|
+
}
|
|
594
666
|
/**
|
|
595
667
|
* Apply resolved billing to trace attempts: final successful attempt gets aggregate billing;
|
|
596
668
|
* other successful attempts without router cost get per-attempt catalog pricing when enabled.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayFallbackAttempt, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
|
|
6
6
|
import type { Logxer } from '@x12i/logxer';
|
|
7
7
|
import { type AiCostResult, type AiModelsCatalogClient, type CostCalculator, type OpenRouterRoutingConfig } from '@x12i/ai-tools';
|
|
8
|
+
import { pickOpenRouterRuntimeMetadataSlice, enrichTraceOpenRouterRuntimeMetadata } from './openrouter-runtime-adapter/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* Generates MD5 hash of a string
|
|
10
11
|
*/
|
|
@@ -132,7 +133,13 @@ export declare function buildTraceUsageSummary(tokens: {
|
|
|
132
133
|
prompt: number;
|
|
133
134
|
completion: number;
|
|
134
135
|
total: number;
|
|
135
|
-
}, billing: ResolvedActivityCost, maxTokensRequested?: number): GatewayTraceUsageSummary | undefined;
|
|
136
|
+
}, billing: ResolvedActivityCost, maxTokensRequested?: number, openrouterExtras?: Pick<GatewayTraceUsageSummary, 'serverTools' | 'citations'>): GatewayTraceUsageSummary | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* Additive OpenRouter runtime metadata for gateway responses.
|
|
139
|
+
*/
|
|
140
|
+
export declare function pickEnhancedOpenRouterMetadata(routerResponse: unknown, traceEnabled?: boolean): ReturnType<typeof pickOpenRouterRuntimeMetadataSlice> & {
|
|
141
|
+
openrouterRuntime?: ReturnType<typeof enrichTraceOpenRouterRuntimeMetadata>;
|
|
142
|
+
};
|
|
136
143
|
/**
|
|
137
144
|
* Apply resolved billing to trace attempts: final successful attempt gets aggregate billing;
|
|
138
145
|
* other successful attempts without router cost get per-attempt catalog pricing when enabled.
|