search-mcp-rotator 1.0.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/README.md +177 -0
- package/config.example.json +97 -0
- package/dist/auth-injector.d.ts +15 -0
- package/dist/auth-injector.js +47 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +53 -0
- package/dist/detector.d.ts +91 -0
- package/dist/detector.js +344 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +80 -0
- package/dist/key-pool.d.ts +68 -0
- package/dist/key-pool.js +230 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.js +43 -0
- package/dist/proxy.d.ts +26 -0
- package/dist/proxy.js +218 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +1 -0
- package/package.json +56 -0
- package/poster.png +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Search MCP Rotator
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
A local MCP (Model Context Protocol) proxy server that provides transparent API key rotation for search providers. When one key is exhausted, rate-limited, or returns any server-side error, the proxy automatically rotates to the next available key and retries the request — transparently, without the MCP client knowing a rotation occurred.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Transparent Key Rotation**: Automatic failover between API keys
|
|
10
|
+
- **Multi-Provider Support**: Supports 9 search providers with different auth patterns
|
|
11
|
+
- **Flexible Rotation Strategies**: Round-robin, priority, and random selection
|
|
12
|
+
- **Circuit Breaker Pattern**: Prevents cascading failures
|
|
13
|
+
- **Configurable Cooldowns**: Smart recovery timing per provider
|
|
14
|
+
- **Strategy-as-Tool-Argument**: LLMs can choose rotation strategy per request
|
|
15
|
+
- **Comprehensive Error Detection**: Provider-specific exhaustion patterns
|
|
16
|
+
|
|
17
|
+
## Supported Providers
|
|
18
|
+
|
|
19
|
+
| Provider | Auth Pattern | Specialty |
|
|
20
|
+
|----------|--------------|-----------|
|
|
21
|
+
| **Exa** | Bearer header | Neural/semantic web search, code search |
|
|
22
|
+
| **Firecrawl** | Bearer header | Web scraping, deep crawl, structured extraction |
|
|
23
|
+
| **Linkup** | Bearer header | Real-time web search, source-cited answers |
|
|
24
|
+
| **Bright Data** | Bearer header | 40+ scraping tools, Google SERP, Amazon |
|
|
25
|
+
| **Olostep** | Bearer header | Search + extract + AI answers with citations |
|
|
26
|
+
| **Tavily** | Query param | Real-time web search, extract, map, crawl |
|
|
27
|
+
| **Dappier** | Query param | Real-time news, finance, sports, weather |
|
|
28
|
+
| **Parallel** | Custom header | Highest-accuracy general web search |
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install
|
|
34
|
+
npm run build
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
Copy `config.example.json` to `config.json` and add your API keys:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"logLevel": "info",
|
|
44
|
+
"providers": {
|
|
45
|
+
"exa": {
|
|
46
|
+
"enabled": true,
|
|
47
|
+
"url": "https://mcp.exa.ai/mcp",
|
|
48
|
+
"authPattern": "bearer",
|
|
49
|
+
"keys": ["your_exa_key_1", "your_exa_key_2"],
|
|
50
|
+
"strategy": "round-robin",
|
|
51
|
+
"cooldownMs": 60000
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Environment Variable Overrides
|
|
58
|
+
|
|
59
|
+
Keys can be overridden via environment variables:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export EXA_KEYS="key1,key2,key3"
|
|
63
|
+
export FIRECRAWL_KEYS="key1,key2"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
Start a rotator for a specific provider:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Using built binary
|
|
72
|
+
node dist/index.js --provider=exa --config=config.json
|
|
73
|
+
|
|
74
|
+
# Or using npm script
|
|
75
|
+
npm run dev -- --provider=exa --config=config.json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## OpenCode Integration
|
|
79
|
+
|
|
80
|
+
Register each rotator as a local MCP server in `~/.config/opencode/opencode.json`:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"exa-rotator": {
|
|
86
|
+
"type": "local",
|
|
87
|
+
"command": "node",
|
|
88
|
+
"args": [
|
|
89
|
+
"/path/to/search-mcp-rotator/dist/index.js",
|
|
90
|
+
"--provider=exa"
|
|
91
|
+
],
|
|
92
|
+
"env": {
|
|
93
|
+
"MCP_ROTATOR_CONFIG": "/path/to/config.json"
|
|
94
|
+
},
|
|
95
|
+
"enabled": true
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Rotation Strategies
|
|
102
|
+
|
|
103
|
+
Every tool call accepts an optional `strategy` parameter:
|
|
104
|
+
|
|
105
|
+
- **`round-robin`** (default): Distribute requests evenly across keys
|
|
106
|
+
- **`priority`**: Always use first healthy key, fallback to others
|
|
107
|
+
- **`random`**: Random key selection to avoid patterns
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// Example tool call with strategy override
|
|
111
|
+
{
|
|
112
|
+
"query": "AI news",
|
|
113
|
+
"strategy": "priority" // Optional: overrides provider default
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Architecture
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
OpenCode (MCP client)
|
|
121
|
+
│ stdio transport
|
|
122
|
+
▼
|
|
123
|
+
┌─────────────────────────┐
|
|
124
|
+
│ Search MCP Rotator Proxy │
|
|
125
|
+
│ (local stdio server) │
|
|
126
|
+
│ │
|
|
127
|
+
│ ┌─────────────────┐ │
|
|
128
|
+
│ │ KeyPool │ │
|
|
129
|
+
│ │ [key1, key2, │ │
|
|
130
|
+
│ │ key3, key4] │ │
|
|
131
|
+
│ │ activeIdx = 0 │ │
|
|
132
|
+
│ │ degraded: Map │ │
|
|
133
|
+
│ └─────────────────┘ │
|
|
134
|
+
│ │
|
|
135
|
+
│ On tool call: │
|
|
136
|
+
│ 1. pick active key │
|
|
137
|
+
│ 2. inject into request │
|
|
138
|
+
│ 3. forward upstream │
|
|
139
|
+
│ 4. on rate-limit: │
|
|
140
|
+
│ mark degraded │
|
|
141
|
+
│ rotate key │
|
|
142
|
+
│ retry same call │
|
|
143
|
+
└─────────────────────────┘
|
|
144
|
+
│ HTTP (Streamable HTTP transport)
|
|
145
|
+
▼
|
|
146
|
+
Remote MCP Provider
|
|
147
|
+
(Exa / Firecrawl / etc.)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Error Detection
|
|
151
|
+
|
|
152
|
+
The rotator detects exhaustion signals specific to each provider:
|
|
153
|
+
|
|
154
|
+
- **HTTP Status Codes**: 401, 402, 429, etc.
|
|
155
|
+
- **JSON-RPC Error Codes**: Provider-specific codes
|
|
156
|
+
- **Message Patterns**: "rate limit", "quota", "credits exhausted"
|
|
157
|
+
- **Special Cases**: Provider-specific error formats
|
|
158
|
+
|
|
159
|
+
## Development
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Install dependencies
|
|
163
|
+
npm install
|
|
164
|
+
|
|
165
|
+
# Development with auto-reload
|
|
166
|
+
npm run dev -- --provider=exa
|
|
167
|
+
|
|
168
|
+
# Type checking
|
|
169
|
+
npm run typecheck
|
|
170
|
+
|
|
171
|
+
# Build for production
|
|
172
|
+
npm run build
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"logLevel": "info",
|
|
3
|
+
"providers": {
|
|
4
|
+
"exa": {
|
|
5
|
+
"enabled": true,
|
|
6
|
+
"url": "https://mcp.exa.ai/mcp",
|
|
7
|
+
"authPattern": "bearer",
|
|
8
|
+
"keys": [
|
|
9
|
+
"exa_key_1",
|
|
10
|
+
"exa_key_2"
|
|
11
|
+
],
|
|
12
|
+
"strategy": "round-robin",
|
|
13
|
+
"cooldownMs": 60000
|
|
14
|
+
},
|
|
15
|
+
"firecrawl": {
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"url": "https://mcp.firecrawl.dev/mcp",
|
|
18
|
+
"authPattern": "bearer",
|
|
19
|
+
"keys": [
|
|
20
|
+
"fc-key_1",
|
|
21
|
+
"fc-key_2"
|
|
22
|
+
],
|
|
23
|
+
"strategy": "round-robin",
|
|
24
|
+
"cooldownMs": 60000
|
|
25
|
+
},
|
|
26
|
+
"tavily": {
|
|
27
|
+
"enabled": true,
|
|
28
|
+
"url": "https://mcp.tavily.com/mcp/",
|
|
29
|
+
"authPattern": "queryparam",
|
|
30
|
+
"queryParamName": "tavilyApiKey",
|
|
31
|
+
"keys": [
|
|
32
|
+
"tvly-key_1",
|
|
33
|
+
"tvly-key_2"
|
|
34
|
+
],
|
|
35
|
+
"strategy": "round-robin",
|
|
36
|
+
"cooldownMs": 60000
|
|
37
|
+
},
|
|
38
|
+
"linkup": {
|
|
39
|
+
"enabled": true,
|
|
40
|
+
"url": "https://mcp.linkup.so/mcp",
|
|
41
|
+
"authPattern": "bearer",
|
|
42
|
+
"keys": [
|
|
43
|
+
"linkup_key_1",
|
|
44
|
+
"linkup_key_2"
|
|
45
|
+
],
|
|
46
|
+
"strategy": "round-robin",
|
|
47
|
+
"cooldownMs": 60000
|
|
48
|
+
},
|
|
49
|
+
"brightdata": {
|
|
50
|
+
"enabled": true,
|
|
51
|
+
"url": "https://mcp.brightdata.com/mcp",
|
|
52
|
+
"authPattern": "bearer",
|
|
53
|
+
"keys": [
|
|
54
|
+
"bd_key_1",
|
|
55
|
+
"bd_key_2"
|
|
56
|
+
],
|
|
57
|
+
"strategy": "round-robin",
|
|
58
|
+
"cooldownMs": 60000
|
|
59
|
+
},
|
|
60
|
+
"olostep": {
|
|
61
|
+
"enabled": true,
|
|
62
|
+
"url": "https://mcp.olostep.com/mcp",
|
|
63
|
+
"authPattern": "bearer",
|
|
64
|
+
"keys": [
|
|
65
|
+
"olostep_key_1",
|
|
66
|
+
"olostep_key_2"
|
|
67
|
+
],
|
|
68
|
+
"strategy": "round-robin",
|
|
69
|
+
"cooldownMs": 300000
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
"dappier": {
|
|
73
|
+
"enabled": true,
|
|
74
|
+
"url": "https://mcp.dappier.com/mcp",
|
|
75
|
+
"authPattern": "queryparam",
|
|
76
|
+
"queryParamName": "apiKey",
|
|
77
|
+
"keys": [
|
|
78
|
+
"dappier_key_1",
|
|
79
|
+
"dappier_key_2"
|
|
80
|
+
],
|
|
81
|
+
"strategy": "round-robin",
|
|
82
|
+
"cooldownMs": 300000
|
|
83
|
+
},
|
|
84
|
+
"parallel": {
|
|
85
|
+
"enabled": true,
|
|
86
|
+
"url": "https://search.parallel.ai/mcp",
|
|
87
|
+
"authPattern": "customheader",
|
|
88
|
+
"headerName": "x-api-key",
|
|
89
|
+
"keys": [
|
|
90
|
+
"parallel_key_1",
|
|
91
|
+
"parallel_key_2"
|
|
92
|
+
],
|
|
93
|
+
"strategy": "round-robin",
|
|
94
|
+
"cooldownMs": 60000
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProviderConfig, AuthInjectionResult } from './types.js';
|
|
2
|
+
export declare class AuthInjector {
|
|
3
|
+
private authPattern;
|
|
4
|
+
private headerName?;
|
|
5
|
+
private queryParamName?;
|
|
6
|
+
constructor(config: Pick<ProviderConfig, 'authPattern' | 'headerName' | 'queryParamName'>);
|
|
7
|
+
/**
|
|
8
|
+
* Inject key into HTTP headers or URL
|
|
9
|
+
*/
|
|
10
|
+
inject(key: string, baseUrl: string): AuthInjectionResult;
|
|
11
|
+
/**
|
|
12
|
+
* For query-param providers: rebuild URL with new key (for rotation)
|
|
13
|
+
*/
|
|
14
|
+
updateKey(currentUrl: string, newKey: string): string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export class AuthInjector {
|
|
2
|
+
authPattern;
|
|
3
|
+
headerName;
|
|
4
|
+
queryParamName;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.authPattern = config.authPattern;
|
|
7
|
+
this.headerName = config.headerName;
|
|
8
|
+
this.queryParamName = config.queryParamName;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Inject key into HTTP headers or URL
|
|
12
|
+
*/
|
|
13
|
+
inject(key, baseUrl) {
|
|
14
|
+
const headers = {};
|
|
15
|
+
let url = baseUrl;
|
|
16
|
+
switch (this.authPattern) {
|
|
17
|
+
case 'bearer':
|
|
18
|
+
headers['Authorization'] = `Bearer ${key}`;
|
|
19
|
+
break;
|
|
20
|
+
case 'queryparam':
|
|
21
|
+
if (!this.queryParamName) {
|
|
22
|
+
throw new Error('queryParamName required for queryparam auth pattern');
|
|
23
|
+
}
|
|
24
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
25
|
+
url = `${url}${separator}${this.queryParamName}=${key}`;
|
|
26
|
+
break;
|
|
27
|
+
case 'customheader':
|
|
28
|
+
if (!this.headerName) {
|
|
29
|
+
throw new Error('headerName required for customheader auth pattern');
|
|
30
|
+
}
|
|
31
|
+
headers[this.headerName] = key;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
return { url, headers };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* For query-param providers: rebuild URL with new key (for rotation)
|
|
38
|
+
*/
|
|
39
|
+
updateKey(currentUrl, newKey) {
|
|
40
|
+
if (this.authPattern !== 'queryparam' || !this.queryParamName) {
|
|
41
|
+
return currentUrl;
|
|
42
|
+
}
|
|
43
|
+
const url = new URL(currentUrl);
|
|
44
|
+
url.searchParams.set(this.queryParamName, newKey);
|
|
45
|
+
return url.toString();
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const AuthPatternSchema = z.enum(['bearer', 'queryparam', 'customheader']);
|
|
4
|
+
const RotationStrategySchema = z.enum(['round-robin', 'priority', 'random']);
|
|
5
|
+
const LogLevelSchema = z.enum(['debug', 'info', 'warn', 'error']);
|
|
6
|
+
// JSON object keys are always strings — coerce them to numbers at runtime
|
|
7
|
+
const CooldownOverridesSchema = z.record(z.string(), z.number()).transform((obj) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [Number(k), v])));
|
|
8
|
+
const ExhaustionPatternSchema = z.object({
|
|
9
|
+
httpStatusCodes: z.array(z.number()).default([]),
|
|
10
|
+
jsonRpcErrorCodes: z.array(z.number()).default([]),
|
|
11
|
+
messagePatterns: z.array(z.string()).default([]),
|
|
12
|
+
cooldownOverrides: CooldownOverridesSchema.default({}),
|
|
13
|
+
hasRetryAfterHeader: z.boolean().default(false)
|
|
14
|
+
});
|
|
15
|
+
const ProviderConfigSchema = z.object({
|
|
16
|
+
enabled: z.boolean().default(true),
|
|
17
|
+
url: z.string().url(),
|
|
18
|
+
authPattern: AuthPatternSchema,
|
|
19
|
+
// Auth pattern specific fields
|
|
20
|
+
headerName: z.string().optional(), // For 'customheader'
|
|
21
|
+
queryParamName: z.string().optional(), // For 'queryparam'
|
|
22
|
+
keys: z.array(z.string()).min(1),
|
|
23
|
+
strategy: RotationStrategySchema.default('round-robin'),
|
|
24
|
+
cooldownMs: z.number().default(60000),
|
|
25
|
+
exhaustionPatterns: ExhaustionPatternSchema.optional()
|
|
26
|
+
});
|
|
27
|
+
const ConfigSchema = z.object({
|
|
28
|
+
logLevel: LogLevelSchema.default('info'),
|
|
29
|
+
providers: z.record(ProviderConfigSchema)
|
|
30
|
+
});
|
|
31
|
+
export async function loadConfig(filePath) {
|
|
32
|
+
// 1. Read config file
|
|
33
|
+
const fileContent = await readFile(filePath, 'utf-8');
|
|
34
|
+
const rawConfig = JSON.parse(fileContent);
|
|
35
|
+
// 2. Merge with env vars (keys can be overridden via env)
|
|
36
|
+
const mergedConfig = mergeWithEnv(rawConfig);
|
|
37
|
+
// 3. Validate with Zod
|
|
38
|
+
const config = ConfigSchema.parse(mergedConfig);
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
function mergeWithEnv(config) {
|
|
42
|
+
// Allow keys to be overridden via env vars like:
|
|
43
|
+
// EXA_KEYS="key1,key2,key3"
|
|
44
|
+
// FIRECRAWL_KEYS="key1,key2"
|
|
45
|
+
for (const providerName in config.providers) {
|
|
46
|
+
const envVarName = `${providerName.toUpperCase()}_KEYS`;
|
|
47
|
+
const envValue = process.env[envVarName];
|
|
48
|
+
if (envValue) {
|
|
49
|
+
config.providers[providerName].keys = envValue.split(',').map((k) => k.trim());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return config;
|
|
53
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ExhaustionError, ExhaustionPatternConfig } from './types.js';
|
|
2
|
+
export declare const PROVIDER_EXHAUSTION_CONFIG: {
|
|
3
|
+
readonly exa: {
|
|
4
|
+
readonly httpStatusCodes: readonly [401, 402, 429];
|
|
5
|
+
readonly jsonRpcErrorCodes: readonly [-32000];
|
|
6
|
+
readonly messagePatterns: readonly ["invalid api key", "no_more_credits", "api_key_budget_exceeded", "team_budget_exceeded", "credits exhausted", "payment required", "account credits", "spending budget", "rate limit", "too many requests", "you've exceeded your exa rate limit", "you've hit exa's free mcp rate limit", "quota"];
|
|
7
|
+
readonly cooldownOverrides: {
|
|
8
|
+
readonly 402: 3600000;
|
|
9
|
+
readonly 401: 31536000000;
|
|
10
|
+
};
|
|
11
|
+
readonly hasRetryAfterHeader: true;
|
|
12
|
+
};
|
|
13
|
+
readonly firecrawl: {
|
|
14
|
+
readonly httpStatusCodes: readonly [401, 402, 429, 500, 502, 503, 504];
|
|
15
|
+
readonly jsonRpcErrorCodes: readonly [];
|
|
16
|
+
readonly messagePatterns: readonly ["rate limit exceeded", "request rate limit exceeded", "concurrency limit reached", "payment required", "insufficient credits", "payment required to access", "unauthorized: invalid token", "unauthorized: token missing", "unauthorized", "retry after", "error creating server"];
|
|
17
|
+
readonly cooldownOverrides: {
|
|
18
|
+
readonly 402: 86400000;
|
|
19
|
+
readonly 401: 604800000;
|
|
20
|
+
};
|
|
21
|
+
readonly hasRetryAfterHeader: true;
|
|
22
|
+
};
|
|
23
|
+
readonly tavily: {
|
|
24
|
+
readonly httpStatusCodes: readonly [401, 429, 432, 433];
|
|
25
|
+
readonly jsonRpcErrorCodes: readonly [];
|
|
26
|
+
readonly messagePatterns: readonly ["authentication required", "unauthorized", "invalid api key", "missing or invalid", "excessive requests", "rate of requests", "usage limit", "pay-as-you-go", "paygo limit", "search failed"];
|
|
27
|
+
readonly cooldownOverrides: {
|
|
28
|
+
readonly 432: 86400000;
|
|
29
|
+
readonly 433: 3600000;
|
|
30
|
+
readonly 401: 31536000000;
|
|
31
|
+
};
|
|
32
|
+
readonly hasRetryAfterHeader: true;
|
|
33
|
+
};
|
|
34
|
+
readonly linkup: {
|
|
35
|
+
readonly httpStatusCodes: readonly [401, 403, 429];
|
|
36
|
+
readonly jsonRpcErrorCodes: readonly [-32000];
|
|
37
|
+
readonly messagePatterns: readonly ["unauthorized action", "api key is required", "insufficient", "too many requests", "out of credit", "credit", "rate limit"];
|
|
38
|
+
readonly cooldownOverrides: {};
|
|
39
|
+
readonly hasRetryAfterHeader: false;
|
|
40
|
+
};
|
|
41
|
+
readonly brightdata: {
|
|
42
|
+
readonly httpStatusCodes: readonly [401, 402, 407, 429];
|
|
43
|
+
readonly jsonRpcErrorCodes: readonly [];
|
|
44
|
+
readonly messagePatterns: readonly ["http 401: auth method is not supported", "http 401: token expired", "http 401", "5,000 request monthly limit", "monthly limit for bright data mcp", "usage limit", "zone has reached usage limit", "http 502", "http 429", "http 407", "account is suspended", "kyc required", "http 402"];
|
|
45
|
+
readonly cooldownOverrides: {};
|
|
46
|
+
readonly hasRetryAfterHeader: false;
|
|
47
|
+
};
|
|
48
|
+
readonly olostep: {
|
|
49
|
+
readonly httpStatusCodes: readonly [401, 402, 403];
|
|
50
|
+
readonly jsonRpcErrorCodes: readonly [];
|
|
51
|
+
readonly messagePatterns: readonly ["invalid_api_key", "invalid api key", "your api key is invalid", "credits exhausted", "payment required", "olostep api error: 401", "olostep api error: 402", "olostep api error: 403"];
|
|
52
|
+
readonly cooldownOverrides: {};
|
|
53
|
+
readonly hasRetryAfterHeader: false;
|
|
54
|
+
};
|
|
55
|
+
readonly dappier: {
|
|
56
|
+
readonly httpStatusCodes: readonly [401, 402];
|
|
57
|
+
readonly jsonRpcErrorCodes: readonly [];
|
|
58
|
+
readonly messagePatterns: readonly ["error: failed to retrieve real-time information", "error: failed to retrieve", "error: unable to retrieve", "error: authentication", "error: unauthorized", "missing authentication"];
|
|
59
|
+
readonly cooldownOverrides: {};
|
|
60
|
+
readonly hasRetryAfterHeader: false;
|
|
61
|
+
};
|
|
62
|
+
readonly parallel: {
|
|
63
|
+
readonly httpStatusCodes: readonly [401, 402, 429];
|
|
64
|
+
readonly jsonRpcErrorCodes: readonly [];
|
|
65
|
+
readonly messagePatterns: readonly ["oauth.v2.invalidapikey", "steps.oauth.v2.failedtoresolveapikey", "oauth.v2.apikeyexpired", "oauth.v2.apikeynotapproved", "invalid apikey", "failedtoresolveapikey", "invalid api key", "rate limit", "too many requests", "insufficient credit", "quota exceeded", "code\":16"];
|
|
66
|
+
readonly cooldownOverrides: {};
|
|
67
|
+
readonly hasRetryAfterHeader: false;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
export declare class ExhaustionDetector {
|
|
71
|
+
private patterns;
|
|
72
|
+
private providerName;
|
|
73
|
+
constructor(providerName: string, customPatterns?: Partial<ExhaustionPatternConfig>);
|
|
74
|
+
/**
|
|
75
|
+
* Determine if an error indicates key exhaustion
|
|
76
|
+
*/
|
|
77
|
+
isExhausted(error: ExhaustionError): boolean;
|
|
78
|
+
private isExaExhausted;
|
|
79
|
+
private isFirecrawlExhausted;
|
|
80
|
+
private isTavilyExhausted;
|
|
81
|
+
private isLinkupExhausted;
|
|
82
|
+
private isBrightDataExhausted;
|
|
83
|
+
private isOlostepExhausted;
|
|
84
|
+
private isDappierExhausted;
|
|
85
|
+
private isParallelExhausted;
|
|
86
|
+
private isGenericExhausted;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Extract cooldown duration from error (if provider specifies it)
|
|
90
|
+
*/
|
|
91
|
+
export declare function extractCooldown(error: ExhaustionError, providerName: string, defaultCooldownMs: number): number;
|