@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.
Files changed (58) hide show
  1. package/.metadata/anthropic.response-map.json +1 -0
  2. package/.metadata/google.response-map.json +1 -0
  3. package/.metadata/groq.response-map.json +1 -0
  4. package/.metadata/llm-request-config-registry.json +111 -0
  5. package/.metadata/llm-response-config-registry.json +1 -0
  6. package/.metadata/model-aliases.json +1 -0
  7. package/.metadata/model-normalization.json +1 -0
  8. package/.metadata/moonshot.response-map.json +1 -0
  9. package/.metadata/openai.response-map.json +1 -0
  10. package/.metadata/openrouter_catalog_with_vendor_mapping.json +15781 -0
  11. package/.metadata/reasoning-support.json +159 -0
  12. package/.metadata/xai.response-map.json +1 -0
  13. package/README.md +480 -0
  14. package/dist/adapters/grok/GrokAdapter.d.ts +50 -0
  15. package/dist/adapters/grok/GrokAdapter.js +342 -0
  16. package/dist/adapters/openai/OpenAIAdapter.d.ts +50 -0
  17. package/dist/adapters/openai/OpenAIAdapter.js +401 -0
  18. package/dist/adapters/openrouter/OpenRouterAdapter.d.ts +87 -0
  19. package/dist/adapters/openrouter/OpenRouterAdapter.js +1449 -0
  20. package/dist/adapters/openrouter/reasoning-capabilities.d.ts +26 -0
  21. package/dist/adapters/openrouter/reasoning-capabilities.js +79 -0
  22. package/dist/discovery.d.ts +6 -0
  23. package/dist/discovery.js +114 -0
  24. package/dist/errors.d.ts +27 -0
  25. package/dist/errors.js +33 -0
  26. package/dist/factory.d.ts +15 -0
  27. package/dist/factory.js +206 -0
  28. package/dist/gateway.d.ts +22 -0
  29. package/dist/gateway.js +154 -0
  30. package/dist/index.d.ts +9 -0
  31. package/dist/index.js +42 -0
  32. package/dist/interceptors.d.ts +10 -0
  33. package/dist/interceptors.js +1 -0
  34. package/dist/logger.d.ts +70 -0
  35. package/dist/logger.js +222 -0
  36. package/dist/openrouter-catalog.d.ts +119 -0
  37. package/dist/openrouter-catalog.js +222 -0
  38. package/dist/providers/OpenRouterProvider.d.ts +16 -0
  39. package/dist/providers/OpenRouterProvider.js +171 -0
  40. package/dist/registry/AdapterRegistry.d.ts +86 -0
  41. package/dist/registry/AdapterRegistry.js +36 -0
  42. package/dist/registry/ProviderRegistry.d.ts +24 -0
  43. package/dist/registry/ProviderRegistry.js +46 -0
  44. package/dist/router/Router.d.ts +33 -0
  45. package/dist/router/Router.js +258 -0
  46. package/dist/router/RouterTypes.d.ts +138 -0
  47. package/dist/router/RouterTypes.js +5 -0
  48. package/dist/router/RouterWrapper.d.ts +83 -0
  49. package/dist/router/RouterWrapper.js +744 -0
  50. package/dist/router.d.ts +13 -0
  51. package/dist/router.js +8 -0
  52. package/dist/types.d.ts +33 -0
  53. package/dist/types.js +1 -0
  54. package/dist/utils/esm-compat.d.ts +9 -0
  55. package/dist/utils/esm-compat.js +13 -0
  56. package/dist/utils/ids.d.ts +4 -0
  57. package/dist/utils/ids.js +6 -0
  58. 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
+ }