@x12i/ai-providers-router 4.6.5 → 4.7.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/dist/errors.d.ts +7 -0
- package/dist/errors.js +11 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/openrouter-catalog.js +5 -0
- package/dist/providers/OpenRouterProvider.js +85 -19
- package/package.json +2 -2
package/dist/errors.d.ts
CHANGED
|
@@ -25,3 +25,10 @@ export declare class ProviderNotInstalledError extends Error {
|
|
|
25
25
|
packageName: string;
|
|
26
26
|
constructor(providerName: ProviderId | string, packageName: string);
|
|
27
27
|
}
|
|
28
|
+
export declare class ProviderTimeoutError extends Error {
|
|
29
|
+
provider: ProviderId | string;
|
|
30
|
+
timeoutMs: number;
|
|
31
|
+
operation?: string | undefined;
|
|
32
|
+
code: "ETIMEDOUT";
|
|
33
|
+
constructor(provider: ProviderId | string, timeoutMs: number, operation?: string | undefined);
|
|
34
|
+
}
|
package/dist/errors.js
CHANGED
|
@@ -31,3 +31,14 @@ export class ProviderNotInstalledError extends Error {
|
|
|
31
31
|
Object.setPrototypeOf(this, ProviderNotInstalledError.prototype);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
export class ProviderTimeoutError extends Error {
|
|
35
|
+
constructor(provider, timeoutMs, operation) {
|
|
36
|
+
super(`Provider '${provider}' request timed out after ${timeoutMs}ms`);
|
|
37
|
+
this.provider = provider;
|
|
38
|
+
this.timeoutMs = timeoutMs;
|
|
39
|
+
this.operation = operation;
|
|
40
|
+
this.code = 'ETIMEDOUT';
|
|
41
|
+
this.name = 'ProviderTimeoutError';
|
|
42
|
+
Object.setPrototypeOf(this, ProviderTimeoutError.prototype);
|
|
43
|
+
}
|
|
44
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { LLMProviderRouter } from './router.js';
|
|
|
2
2
|
export type { RouterConfig, HealthCheckResult, ProviderId, AIRouterRequest, AIResponse, AIStreamEvent, AIBatchResponse, AIBatchRequestItem, } from './router.js';
|
|
3
3
|
export { createRouter, createRouterFromConfig } from './factory.js';
|
|
4
4
|
export type { CreateRouterConfig } from './factory.js';
|
|
5
|
-
export { ProviderNotFoundError, FallbackExhaustedError, ProviderNotInstalledError } from './errors.js';
|
|
5
|
+
export { ProviderNotFoundError, FallbackExhaustedError, ProviderNotInstalledError, ProviderTimeoutError } from './errors.js';
|
|
6
6
|
export type { RequestInterceptor, ResponseInterceptor } from './interceptors.js';
|
|
7
7
|
export type { UsageTracker, AdapterLoader, ProviderInit } from './types.js';
|
|
8
8
|
export { Logger, getLogger, createLogger } from './logger.js';
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,6 @@ export {
|
|
|
37
37
|
// Factory functions
|
|
38
38
|
export { createRouter, createRouterFromConfig } from './factory.js';
|
|
39
39
|
// Error classes
|
|
40
|
-
export { ProviderNotFoundError, FallbackExhaustedError, ProviderNotInstalledError } from './errors.js';
|
|
40
|
+
export { ProviderNotFoundError, FallbackExhaustedError, ProviderNotInstalledError, ProviderTimeoutError } from './errors.js';
|
|
41
41
|
// Logger
|
|
42
42
|
export { Logger, getLogger, createLogger } from './logger.js';
|
|
@@ -214,6 +214,11 @@ export class OpenRouterCatalogLoader {
|
|
|
214
214
|
return model.openrouterId;
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
+
// Best-effort fallback: if we have a provider hint and the model is bare,
|
|
218
|
+
// still prefix it (helps with very new models like `gpt-5.5` before catalog refresh).
|
|
219
|
+
if (providerHint && !modelName.includes('/')) {
|
|
220
|
+
return `${providerHint}/${modelName}`;
|
|
221
|
+
}
|
|
217
222
|
// Last resort: return as-is (might be a direct model name)
|
|
218
223
|
return modelName;
|
|
219
224
|
}
|
|
@@ -3,17 +3,20 @@
|
|
|
3
3
|
* This properly implements the ProviderModule interface for OpenRouter
|
|
4
4
|
*/
|
|
5
5
|
import { OpenRouter } from '@openrouter/sdk';
|
|
6
|
+
import { ProviderTimeoutError } from '../errors.js';
|
|
6
7
|
/**
|
|
7
8
|
* Create OpenRouter ProviderModule using @openrouter/sdk directly
|
|
8
9
|
*/
|
|
9
10
|
export function createOpenRouterProvider(config) {
|
|
10
|
-
const
|
|
11
|
+
const defaultClient = new OpenRouter({
|
|
11
12
|
apiKey: config.apiKey,
|
|
12
13
|
serverURL: config.baseURL || 'https://openrouter.ai/api/v1',
|
|
13
14
|
httpReferer: config.httpReferer,
|
|
14
15
|
xTitle: config.xTitle,
|
|
15
16
|
timeoutMs: config.timeoutMs,
|
|
16
17
|
});
|
|
18
|
+
// Optional test seam (non-breaking): allow injecting a client override
|
|
19
|
+
const client = config.client || defaultClient;
|
|
17
20
|
return {
|
|
18
21
|
name: 'openrouter',
|
|
19
22
|
capabilities: {
|
|
@@ -31,16 +34,42 @@ export function createOpenRouterProvider(config) {
|
|
|
31
34
|
},
|
|
32
35
|
async execute(spec) {
|
|
33
36
|
const { operation, args, exec } = spec;
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
const timeoutMs = exec?.timeoutMs;
|
|
38
|
+
let timedOut = false;
|
|
39
|
+
// Only create a controller if we need to enforce a timeout or bridge signals.
|
|
40
|
+
const controller = timeoutMs ? new AbortController() : null;
|
|
36
41
|
let timeoutId = null;
|
|
37
|
-
if (
|
|
42
|
+
if (timeoutMs && controller) {
|
|
38
43
|
timeoutId = setTimeout(() => {
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
timedOut = true;
|
|
45
|
+
try {
|
|
46
|
+
controller.abort();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Defensive: abort() should not be able to crash the process.
|
|
50
|
+
}
|
|
51
|
+
}, timeoutMs);
|
|
41
52
|
}
|
|
42
|
-
//
|
|
43
|
-
|
|
53
|
+
// If caller provided a signal, bridge it into our controller (if we have one).
|
|
54
|
+
// Otherwise, just use the caller signal directly.
|
|
55
|
+
let bridgedAbortListener = null;
|
|
56
|
+
if (exec?.signal && controller) {
|
|
57
|
+
bridgedAbortListener = () => {
|
|
58
|
+
try {
|
|
59
|
+
controller.abort();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// ignore
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
if (exec.signal.aborted) {
|
|
66
|
+
bridgedAbortListener();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
exec.signal.addEventListener('abort', bridgedAbortListener, { once: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const signal = controller?.signal || exec?.signal;
|
|
44
73
|
try {
|
|
45
74
|
let rawResponse;
|
|
46
75
|
if (operation === 'openai.responses.create' || operation === 'openai.chat.completions.create') {
|
|
@@ -92,24 +121,54 @@ export function createOpenRouterProvider(config) {
|
|
|
92
121
|
};
|
|
93
122
|
}
|
|
94
123
|
catch (error) {
|
|
95
|
-
if (
|
|
96
|
-
|
|
124
|
+
if (timedOut && typeof timeoutMs === 'number') {
|
|
125
|
+
throw new ProviderTimeoutError('openrouter', timeoutMs, operation);
|
|
97
126
|
}
|
|
98
127
|
throw error;
|
|
99
128
|
}
|
|
129
|
+
finally {
|
|
130
|
+
if (timeoutId)
|
|
131
|
+
clearTimeout(timeoutId);
|
|
132
|
+
if (exec?.signal && bridgedAbortListener) {
|
|
133
|
+
exec.signal.removeEventListener('abort', bridgedAbortListener);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
100
136
|
},
|
|
101
137
|
async *stream(spec) {
|
|
102
138
|
const { operation, args, exec } = spec;
|
|
103
|
-
|
|
104
|
-
|
|
139
|
+
const timeoutMs = exec?.timeoutMs;
|
|
140
|
+
let timedOut = false;
|
|
141
|
+
const controller = timeoutMs ? new AbortController() : null;
|
|
105
142
|
let timeoutId = null;
|
|
106
|
-
if (
|
|
143
|
+
if (timeoutMs && controller) {
|
|
107
144
|
timeoutId = setTimeout(() => {
|
|
108
|
-
|
|
109
|
-
|
|
145
|
+
timedOut = true;
|
|
146
|
+
try {
|
|
147
|
+
controller.abort();
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// ignore
|
|
151
|
+
}
|
|
152
|
+
}, timeoutMs);
|
|
110
153
|
}
|
|
111
|
-
|
|
112
|
-
|
|
154
|
+
let bridgedAbortListener = null;
|
|
155
|
+
if (exec?.signal && controller) {
|
|
156
|
+
bridgedAbortListener = () => {
|
|
157
|
+
try {
|
|
158
|
+
controller.abort();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// ignore
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
if (exec.signal.aborted) {
|
|
165
|
+
bridgedAbortListener();
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
exec.signal.addEventListener('abort', bridgedAbortListener, { once: true });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const signal = controller?.signal || exec?.signal;
|
|
113
172
|
try {
|
|
114
173
|
if (operation === 'openai.responses.stream' || operation === 'openai.chat.completions.stream') {
|
|
115
174
|
// Convert args to OpenRouter SDK format
|
|
@@ -161,11 +220,18 @@ export function createOpenRouterProvider(config) {
|
|
|
161
220
|
}
|
|
162
221
|
}
|
|
163
222
|
catch (error) {
|
|
164
|
-
if (
|
|
165
|
-
|
|
223
|
+
if (timedOut && typeof timeoutMs === 'number') {
|
|
224
|
+
throw new ProviderTimeoutError('openrouter', timeoutMs, operation);
|
|
166
225
|
}
|
|
167
226
|
throw error;
|
|
168
227
|
}
|
|
228
|
+
finally {
|
|
229
|
+
if (timeoutId)
|
|
230
|
+
clearTimeout(timeoutId);
|
|
231
|
+
if (exec?.signal && bridgedAbortListener) {
|
|
232
|
+
exec.signal.removeEventListener('abort', bridgedAbortListener);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
169
235
|
},
|
|
170
236
|
};
|
|
171
237
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x12i/ai-providers-router",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"description": "Unified router for all LLM provider implementations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"build": "tsc",
|
|
18
18
|
"fix:imports": "node fix-import-extensions.js",
|
|
19
19
|
"prepublishOnly": "rm -rf dist && npm run build && node -e \"import('fs').then(fs => fs.accessSync('dist/index.js'))\"",
|
|
20
|
-
"test": "
|
|
20
|
+
"test": "npm run build && node --test .tests/**/*.test.js",
|
|
21
21
|
"test:openai": "ts-node .tests/callOpenAI.ts",
|
|
22
22
|
"test:grok": "ts-node .tests/testOpenRouter.ts",
|
|
23
23
|
"test:catalog": "ts-node .tests/testCatalog.ts",
|