@x12i/ai-providers-router 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.metadata/anthropic.response-map.json +1 -0
- package/.metadata/google.response-map.json +1 -0
- package/.metadata/groq.response-map.json +1 -0
- package/.metadata/llm-request-config-registry.json +111 -0
- package/.metadata/llm-response-config-registry.json +1 -0
- package/.metadata/model-aliases.json +1 -0
- package/.metadata/model-normalization.json +1 -0
- package/.metadata/moonshot.response-map.json +1 -0
- package/.metadata/openai.response-map.json +1 -0
- package/.metadata/openrouter_catalog_with_vendor_mapping.json +15781 -0
- package/.metadata/reasoning-support.json +159 -0
- package/.metadata/xai.response-map.json +1 -0
- package/README.md +480 -0
- package/dist/adapters/grok/GrokAdapter.d.ts +50 -0
- package/dist/adapters/grok/GrokAdapter.js +342 -0
- package/dist/adapters/openai/OpenAIAdapter.d.ts +50 -0
- package/dist/adapters/openai/OpenAIAdapter.js +401 -0
- package/dist/adapters/openrouter/OpenRouterAdapter.d.ts +87 -0
- package/dist/adapters/openrouter/OpenRouterAdapter.js +1449 -0
- package/dist/adapters/openrouter/reasoning-capabilities.d.ts +26 -0
- package/dist/adapters/openrouter/reasoning-capabilities.js +79 -0
- package/dist/discovery.d.ts +6 -0
- package/dist/discovery.js +114 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +33 -0
- package/dist/factory.d.ts +15 -0
- package/dist/factory.js +206 -0
- package/dist/gateway.d.ts +22 -0
- package/dist/gateway.js +154 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +42 -0
- package/dist/interceptors.d.ts +10 -0
- package/dist/interceptors.js +1 -0
- package/dist/logger.d.ts +70 -0
- package/dist/logger.js +222 -0
- package/dist/openrouter-catalog.d.ts +119 -0
- package/dist/openrouter-catalog.js +222 -0
- package/dist/providers/OpenRouterProvider.d.ts +16 -0
- package/dist/providers/OpenRouterProvider.js +171 -0
- package/dist/registry/AdapterRegistry.d.ts +86 -0
- package/dist/registry/AdapterRegistry.js +36 -0
- package/dist/registry/ProviderRegistry.d.ts +24 -0
- package/dist/registry/ProviderRegistry.js +46 -0
- package/dist/router/Router.d.ts +33 -0
- package/dist/router/Router.js +258 -0
- package/dist/router/RouterTypes.d.ts +138 -0
- package/dist/router/RouterTypes.js +5 -0
- package/dist/router/RouterWrapper.d.ts +83 -0
- package/dist/router/RouterWrapper.js +744 -0
- package/dist/router.d.ts +13 -0
- package/dist/router.js +8 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +1 -0
- package/dist/utils/esm-compat.d.ts +9 -0
- package/dist/utils/esm-compat.js +13 -0
- package/dist/utils/ids.d.ts +4 -0
- package/dist/utils/ids.js +6 -0
- package/package.json +66 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { createXai } from '@ai-sdk/xai';
|
|
2
|
+
// Import ai-io-normalizer functions
|
|
3
|
+
// Note: Type declaration in src/types/ai-io-normalizer.d.ts allows this import
|
|
4
|
+
import { normalizeProviderResponse, loadResponseMaps } from 'ai-io-normalizer/dist/response-normalizer.js';
|
|
5
|
+
const normalizerAvailable = true;
|
|
6
|
+
// Lazy-load response maps (loaded once, cached)
|
|
7
|
+
let cachedResponseMaps = null;
|
|
8
|
+
function getResponseMaps() {
|
|
9
|
+
if (!normalizerAvailable || !loadResponseMaps) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (!cachedResponseMaps) {
|
|
13
|
+
try {
|
|
14
|
+
const registry = {
|
|
15
|
+
schemaVersion: '1.0.0',
|
|
16
|
+
standard: 'chat_completions_like_v1',
|
|
17
|
+
providers: {
|
|
18
|
+
openai: { ref: 'openai' },
|
|
19
|
+
xai: { ref: 'xai' },
|
|
20
|
+
groq: { ref: 'groq' },
|
|
21
|
+
anthropic: { ref: 'anthropic' },
|
|
22
|
+
google: { ref: 'google' },
|
|
23
|
+
moonshot: { ref: 'moonshot' },
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
cachedResponseMaps = loadResponseMaps(registry);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.warn('[GrokAdapter] Failed to load response maps:', error.message);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return cachedResponseMaps;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Router-side adapter for Grok/xAI provider
|
|
37
|
+
* Converts router requests to ProviderSDKCallSpec and parses responses
|
|
38
|
+
*/
|
|
39
|
+
export class GrokAdapter {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.provider = 'grok'; // Note: ProviderModule name might be 'grok' or 'xai', adapter handles both
|
|
42
|
+
}
|
|
43
|
+
async buildCallSpec(input) {
|
|
44
|
+
const { requestId, mode, request, exec } = input;
|
|
45
|
+
// Extract model from request
|
|
46
|
+
const model = request.model || request.config?.model || 'grok-2-1212';
|
|
47
|
+
// Determine operation (Grok provider uses xai.generateText/xai.streamText)
|
|
48
|
+
const operation = mode === 'stream' ? 'xai.streamText' : 'xai.generateText';
|
|
49
|
+
// Build args for ai package generateText/streamText
|
|
50
|
+
// These functions expect: { model: xai('model-name'), messages: [...] } or { model: xai('model-name'), prompt: '...' }
|
|
51
|
+
// Prefer messages format as shown in Grok provider README
|
|
52
|
+
// Create xAI provider instance with API key from environment
|
|
53
|
+
// The factory sets XAI_API_KEY before importing, so it should be available here
|
|
54
|
+
const apiKey = process.env.XAI_API_KEY;
|
|
55
|
+
if (!apiKey) {
|
|
56
|
+
throw new Error('XAI_API_KEY environment variable is not set. Ensure GROK_API_KEY is configured.');
|
|
57
|
+
}
|
|
58
|
+
const xaiProvider = createXai({ apiKey });
|
|
59
|
+
// Build args - prefer messages format if available
|
|
60
|
+
const args = {
|
|
61
|
+
model: xaiProvider(model),
|
|
62
|
+
};
|
|
63
|
+
// Handle different request formats
|
|
64
|
+
if (request.messages && Array.isArray(request.messages)) {
|
|
65
|
+
// Use messages directly (preferred format)
|
|
66
|
+
args.messages = request.messages.map((msg) => ({
|
|
67
|
+
role: msg.role || 'user',
|
|
68
|
+
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
else if (request.instructions || request.inputData) {
|
|
72
|
+
// Convert instructions/inputData to messages format
|
|
73
|
+
const messages = [];
|
|
74
|
+
if (request.instructions) {
|
|
75
|
+
messages.push({
|
|
76
|
+
role: 'system',
|
|
77
|
+
content: request.instructions,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (request.inputData) {
|
|
81
|
+
const input = typeof request.inputData === 'string' ? request.inputData : JSON.stringify(request.inputData);
|
|
82
|
+
messages.push({
|
|
83
|
+
role: 'user',
|
|
84
|
+
content: input,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
args.messages = messages;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Fallback: use prompt format
|
|
91
|
+
args.prompt = JSON.stringify(request);
|
|
92
|
+
}
|
|
93
|
+
// Add config parameters (ai package format)
|
|
94
|
+
if (request.config) {
|
|
95
|
+
if (request.config.maxTokens !== undefined) {
|
|
96
|
+
args.maxTokens = request.config.maxTokens;
|
|
97
|
+
}
|
|
98
|
+
if (request.config.temperature !== undefined) {
|
|
99
|
+
args.temperature = request.config.temperature;
|
|
100
|
+
}
|
|
101
|
+
if (request.config.topP !== undefined) {
|
|
102
|
+
args.topP = request.config.topP;
|
|
103
|
+
}
|
|
104
|
+
if (request.config.stop !== undefined) {
|
|
105
|
+
args.stop = request.config.stop;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
requestId,
|
|
110
|
+
provider: 'grok', // This should match ProviderModule.name
|
|
111
|
+
mode,
|
|
112
|
+
operation,
|
|
113
|
+
args,
|
|
114
|
+
exec: exec ? {
|
|
115
|
+
timeoutMs: exec.timeoutMs,
|
|
116
|
+
retries: exec.retries,
|
|
117
|
+
idempotencyKey: exec.idempotencyKey,
|
|
118
|
+
signal: exec.signal,
|
|
119
|
+
} : undefined,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
parseResponse(input) {
|
|
123
|
+
const { requestId, execResult } = input;
|
|
124
|
+
const rawResponse = execResult.rawResponse;
|
|
125
|
+
// Use ai-io-normalizer for parsing (router stays orchestration-only)
|
|
126
|
+
// xAI/Grok uses 'xai.chat_completions' variant
|
|
127
|
+
let outputText;
|
|
128
|
+
let usage;
|
|
129
|
+
if (normalizerAvailable && normalizeProviderResponse) {
|
|
130
|
+
try {
|
|
131
|
+
const maps = getResponseMaps();
|
|
132
|
+
if (maps) {
|
|
133
|
+
const normalized = normalizeProviderResponse(rawResponse, {
|
|
134
|
+
provider: 'xai',
|
|
135
|
+
apiVariant: 'xai.chat_completions',
|
|
136
|
+
mode: 'standard',
|
|
137
|
+
keepRawOverride: false,
|
|
138
|
+
}, maps);
|
|
139
|
+
if (normalized.choices && normalized.choices.length > 0) {
|
|
140
|
+
outputText = normalized.choices[0].message?.content;
|
|
141
|
+
}
|
|
142
|
+
if (normalized.usage) {
|
|
143
|
+
usage = {
|
|
144
|
+
inputTokens: normalized.usage.prompt_tokens,
|
|
145
|
+
outputTokens: normalized.usage.completion_tokens,
|
|
146
|
+
totalTokens: normalized.usage.total_tokens,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
// Fallback to manual extraction
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Fallback to manual extraction for ai package format if normalizer not available or failed
|
|
156
|
+
if (!outputText) {
|
|
157
|
+
if (rawResponse?.text) {
|
|
158
|
+
outputText = rawResponse.text;
|
|
159
|
+
}
|
|
160
|
+
else if (typeof rawResponse === 'string') {
|
|
161
|
+
outputText = rawResponse;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (!usage && rawResponse?.usage) {
|
|
165
|
+
usage = {
|
|
166
|
+
inputTokens: rawResponse.usage.promptTokens || rawResponse.usage.input_tokens || rawResponse.usage.prompt_tokens,
|
|
167
|
+
outputTokens: rawResponse.usage.completionTokens || rawResponse.usage.output_tokens || rawResponse.usage.completion_tokens,
|
|
168
|
+
totalTokens: rawResponse.usage.totalTokens || rawResponse.usage.total_tokens,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// Build complete response object for ai-activities storage
|
|
172
|
+
const fullResponse = {
|
|
173
|
+
requestId,
|
|
174
|
+
provider: 'grok',
|
|
175
|
+
rawResponse,
|
|
176
|
+
outputText,
|
|
177
|
+
usage,
|
|
178
|
+
reasoning: {
|
|
179
|
+
requested: { effort: 'none', visibility: 'none' },
|
|
180
|
+
applied: { effort: 'none', visibility: 'none' },
|
|
181
|
+
artifacts: {},
|
|
182
|
+
availability: {
|
|
183
|
+
supportsEffort: false,
|
|
184
|
+
supportsSummary: false,
|
|
185
|
+
supportsTrace: false,
|
|
186
|
+
supportsEncrypted: false,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
metadata: {
|
|
190
|
+
model: rawResponse?.model || input.request.config?.model,
|
|
191
|
+
finishReason: rawResponse?.finishReason,
|
|
192
|
+
...execResult.rawMeta,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
// IMPORTANT: Create a clean copy of fullResponse without circular reference
|
|
196
|
+
const fullResponseForActivities = {
|
|
197
|
+
requestId: fullResponse.requestId,
|
|
198
|
+
provider: fullResponse.provider,
|
|
199
|
+
rawResponse: fullResponse.rawResponse,
|
|
200
|
+
outputText: fullResponse.outputText,
|
|
201
|
+
usage: fullResponse.usage,
|
|
202
|
+
reasoning: fullResponse.reasoning,
|
|
203
|
+
// Don't include metadata in the copy to avoid circular reference
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
...fullResponse,
|
|
207
|
+
metadata: {
|
|
208
|
+
...fullResponse.metadata,
|
|
209
|
+
// Include full response in metadata for ai-activities storage (without circular ref)
|
|
210
|
+
'ai-activities-response': fullResponseForActivities,
|
|
211
|
+
// Include request for complete audit trail
|
|
212
|
+
'ai-activities-request': input.request,
|
|
213
|
+
'ai-activities-raw-response': rawResponse,
|
|
214
|
+
'ai-activities-exec-meta': execResult.rawMeta,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
parseStreamChunk(input) {
|
|
219
|
+
const { requestId, chunk } = input;
|
|
220
|
+
const raw = chunk.raw;
|
|
221
|
+
const events = [];
|
|
222
|
+
// Emit provider_raw event
|
|
223
|
+
events.push({
|
|
224
|
+
type: 'provider_raw',
|
|
225
|
+
requestId,
|
|
226
|
+
provider: 'grok',
|
|
227
|
+
raw,
|
|
228
|
+
});
|
|
229
|
+
// Parse ai package streamText format
|
|
230
|
+
// Stream format: { textDelta: "...", text: "...", finishReason: ... }
|
|
231
|
+
if (raw?.textDelta) {
|
|
232
|
+
events.push({
|
|
233
|
+
type: 'output_text_delta',
|
|
234
|
+
requestId,
|
|
235
|
+
delta: raw.textDelta,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else if (raw?.delta) {
|
|
239
|
+
// Alternative format
|
|
240
|
+
events.push({
|
|
241
|
+
type: 'output_text_delta',
|
|
242
|
+
requestId,
|
|
243
|
+
delta: raw.delta,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return events;
|
|
247
|
+
}
|
|
248
|
+
finalizeStream(input) {
|
|
249
|
+
const { requestId, collected, finalRaw } = input;
|
|
250
|
+
// Use finalRaw if available, otherwise use last raw event
|
|
251
|
+
const rawResponse = finalRaw || (collected.rawEvents.length > 0 ? collected.rawEvents[collected.rawEvents.length - 1] : {});
|
|
252
|
+
// Build complete response object for ai-activities storage
|
|
253
|
+
const fullResponse = {
|
|
254
|
+
requestId,
|
|
255
|
+
provider: 'grok',
|
|
256
|
+
rawResponse,
|
|
257
|
+
outputText: collected.outputText,
|
|
258
|
+
reasoning: {
|
|
259
|
+
requested: { effort: 'none', visibility: 'none' },
|
|
260
|
+
applied: { effort: 'none', visibility: 'none' },
|
|
261
|
+
artifacts: {},
|
|
262
|
+
availability: {
|
|
263
|
+
supportsEffort: false,
|
|
264
|
+
supportsSummary: false,
|
|
265
|
+
supportsTrace: false,
|
|
266
|
+
supportsEncrypted: false,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
metadata: {},
|
|
270
|
+
};
|
|
271
|
+
// IMPORTANT: Create a clean copy of fullResponse without circular reference
|
|
272
|
+
const fullResponseForActivities = {
|
|
273
|
+
requestId: fullResponse.requestId,
|
|
274
|
+
provider: fullResponse.provider,
|
|
275
|
+
rawResponse: fullResponse.rawResponse,
|
|
276
|
+
outputText: fullResponse.outputText,
|
|
277
|
+
reasoning: fullResponse.reasoning,
|
|
278
|
+
// Don't include metadata in the copy to avoid circular reference
|
|
279
|
+
};
|
|
280
|
+
// Add usage if available (may not be present in streaming responses)
|
|
281
|
+
if ('usage' in fullResponse && fullResponse.usage) {
|
|
282
|
+
fullResponseForActivities.usage = fullResponse.usage;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
...fullResponse,
|
|
286
|
+
metadata: {
|
|
287
|
+
...fullResponse.metadata,
|
|
288
|
+
// Include full response in metadata for ai-activities storage (without circular ref)
|
|
289
|
+
'ai-activities-response': fullResponseForActivities,
|
|
290
|
+
// Include request for complete audit trail
|
|
291
|
+
'ai-activities-request': input.request,
|
|
292
|
+
'ai-activities-raw-response': rawResponse,
|
|
293
|
+
'ai-activities-collected-events': collected.rawEvents,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
parseBatchItem(input) {
|
|
298
|
+
const { requestId, item } = input;
|
|
299
|
+
if (item.error) {
|
|
300
|
+
return {
|
|
301
|
+
requestId,
|
|
302
|
+
error: item.error,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
// Parse Grok batch item response using ai-io-normalizer
|
|
306
|
+
const rawResponse = item.rawResponse;
|
|
307
|
+
let outputText;
|
|
308
|
+
if (normalizerAvailable && normalizeProviderResponse) {
|
|
309
|
+
try {
|
|
310
|
+
const maps = getResponseMaps();
|
|
311
|
+
if (maps) {
|
|
312
|
+
const normalized = normalizeProviderResponse(rawResponse, {
|
|
313
|
+
provider: 'xai',
|
|
314
|
+
apiVariant: 'xai.chat_completions',
|
|
315
|
+
mode: 'batch',
|
|
316
|
+
keepRawOverride: false,
|
|
317
|
+
}, maps);
|
|
318
|
+
if (normalized.choices && normalized.choices.length > 0) {
|
|
319
|
+
outputText = normalized.choices[0].message?.content;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
// Fallback to manual extraction
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Fallback to manual extraction for ai package format if normalizer not available or failed
|
|
328
|
+
if (!outputText) {
|
|
329
|
+
if (rawResponse?.text) {
|
|
330
|
+
outputText = rawResponse.text;
|
|
331
|
+
}
|
|
332
|
+
else if (typeof rawResponse === 'string') {
|
|
333
|
+
outputText = rawResponse;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
requestId,
|
|
338
|
+
rawResponse,
|
|
339
|
+
outputText,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ProviderSDKCallSpec, ProviderSDKExecResult, ProviderSDKStreamChunk, ProviderBatchResults } from '@x12i/ai-provider-interface';
|
|
2
|
+
import type { RouterAdapter } from '../../registry/AdapterRegistry.js';
|
|
3
|
+
import type { AIResponse, AIStreamEvent } from '../../router/RouterTypes.js';
|
|
4
|
+
/**
|
|
5
|
+
* Router-side adapter for OpenAI provider
|
|
6
|
+
* Converts router requests to ProviderSDKCallSpec and parses responses
|
|
7
|
+
*/
|
|
8
|
+
export declare class OpenAIAdapter implements RouterAdapter {
|
|
9
|
+
provider: string;
|
|
10
|
+
buildCallSpec(input: {
|
|
11
|
+
requestId: string;
|
|
12
|
+
mode: 'sync' | 'stream';
|
|
13
|
+
request: any;
|
|
14
|
+
exec?: {
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
retries?: number;
|
|
17
|
+
idempotencyKey?: string;
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
};
|
|
20
|
+
}): Promise<ProviderSDKCallSpec>;
|
|
21
|
+
parseResponse(input: {
|
|
22
|
+
requestId: string;
|
|
23
|
+
request: any;
|
|
24
|
+
execResult: ProviderSDKExecResult;
|
|
25
|
+
}): AIResponse;
|
|
26
|
+
parseStreamChunk(input: {
|
|
27
|
+
requestId: string;
|
|
28
|
+
request: any;
|
|
29
|
+
chunk: ProviderSDKStreamChunk;
|
|
30
|
+
}): AIStreamEvent[];
|
|
31
|
+
finalizeStream(input: {
|
|
32
|
+
requestId: string;
|
|
33
|
+
request: any;
|
|
34
|
+
collected: {
|
|
35
|
+
rawEvents: unknown[];
|
|
36
|
+
outputText?: string;
|
|
37
|
+
};
|
|
38
|
+
finalRaw?: unknown;
|
|
39
|
+
}): AIResponse;
|
|
40
|
+
parseBatchItem(input: {
|
|
41
|
+
requestId: string;
|
|
42
|
+
request: any;
|
|
43
|
+
item: ProviderBatchResults['items'][number];
|
|
44
|
+
}): {
|
|
45
|
+
requestId: string;
|
|
46
|
+
rawResponse?: unknown;
|
|
47
|
+
outputText?: string;
|
|
48
|
+
error?: any;
|
|
49
|
+
};
|
|
50
|
+
}
|