@stabgan/openrouter-mcp-multimodal 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 +331 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/model-cache.d.ts +56 -0
- package/dist/model-cache.js +143 -0
- package/dist/model-cache.js.map +1 -0
- package/dist/openrouter-api.d.ts +26 -0
- package/dist/openrouter-api.js +119 -0
- package/dist/openrouter-api.js.map +1 -0
- package/dist/tool-handlers/analyze-image.d.ts +23 -0
- package/dist/tool-handlers/analyze-image.js +93 -0
- package/dist/tool-handlers/analyze-image.js.map +1 -0
- package/dist/tool-handlers/chat-completion.d.ts +24 -0
- package/dist/tool-handlers/chat-completion.js +111 -0
- package/dist/tool-handlers/chat-completion.js.map +1 -0
- package/dist/tool-handlers/get-model-info.d.ts +21 -0
- package/dist/tool-handlers/get-model-info.js +44 -0
- package/dist/tool-handlers/get-model-info.js.map +1 -0
- package/dist/tool-handlers/multi-image-analysis.d.ts +27 -0
- package/dist/tool-handlers/multi-image-analysis.js +136 -0
- package/dist/tool-handlers/multi-image-analysis.js.map +1 -0
- package/dist/tool-handlers/search-models.d.ts +34 -0
- package/dist/tool-handlers/search-models.js +44 -0
- package/dist/tool-handlers/search-models.js.map +1 -0
- package/dist/tool-handlers/validate-model.d.ts +21 -0
- package/dist/tool-handlers/validate-model.js +40 -0
- package/dist/tool-handlers/validate-model.js.map +1 -0
- package/dist/tool-handlers.d.ts +10 -0
- package/dist/tool-handlers.js +316 -0
- package/dist/tool-handlers.js.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# OpenRouter MCP Multimodal Server
|
|
2
|
+
|
|
3
|
+
[](https://github.com/stabgan/openrouter-mcp-multimodal/actions/workflows/publish.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@stabgan/openrouter-mcp-multimodal)
|
|
5
|
+
[](https://hub.docker.com/r/stabgandocker/openrouter-mcp-multimodal)
|
|
6
|
+
|
|
7
|
+
An MCP (Model Context Protocol) server that provides chat and image analysis capabilities through OpenRouter.ai's diverse model ecosystem. This server combines text chat functionality with powerful image analysis capabilities.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Text Chat:**
|
|
12
|
+
- Direct access to all OpenRouter.ai chat models
|
|
13
|
+
- Support for simple text and multimodal conversations
|
|
14
|
+
- Configurable temperature and other parameters
|
|
15
|
+
|
|
16
|
+
- **Image Analysis:**
|
|
17
|
+
- Analyze single images with custom questions
|
|
18
|
+
- Process multiple images simultaneously
|
|
19
|
+
- Automatic image resizing and optimization
|
|
20
|
+
- Support for various image sources (local files, URLs, data URLs)
|
|
21
|
+
|
|
22
|
+
- **Model Selection:**
|
|
23
|
+
- Search and filter available models
|
|
24
|
+
- Validate model IDs
|
|
25
|
+
- Get detailed model information
|
|
26
|
+
- Support for default model configuration
|
|
27
|
+
|
|
28
|
+
- **Performance Optimization:**
|
|
29
|
+
- Smart model information caching
|
|
30
|
+
- Exponential backoff for retries
|
|
31
|
+
- Automatic rate limit handling
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### Option 1: Install via npm
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g @stabgan/openrouter-mcp-multimodal
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Option 2: Run via Docker
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
docker run -i -e OPENROUTER_API_KEY=your-api-key-here stabgandocker/openrouter-mcp-multimodal:latest
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Start Configuration
|
|
48
|
+
|
|
49
|
+
### Prerequisites
|
|
50
|
+
|
|
51
|
+
1. Get your OpenRouter API key from [OpenRouter Keys](https://openrouter.ai/keys)
|
|
52
|
+
2. Choose a default model (optional)
|
|
53
|
+
|
|
54
|
+
### MCP Configuration Options
|
|
55
|
+
|
|
56
|
+
Add one of the following configurations to your MCP settings file (e.g., `cline_mcp_settings.json` or `claude_desktop_config.json`):
|
|
57
|
+
|
|
58
|
+
#### Option 1: Using npx (Node.js)
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"openrouter": {
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": [
|
|
66
|
+
"-y",
|
|
67
|
+
"@stabgan/openrouter-mcp-multimodal"
|
|
68
|
+
],
|
|
69
|
+
"env": {
|
|
70
|
+
"OPENROUTER_API_KEY": "your-api-key-here",
|
|
71
|
+
"OPENROUTER_DEFAULT_MODEL": "anthropic/claude-3.5-sonnet"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### Option 2: Using uv (Python Package Manager)
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"openrouter": {
|
|
84
|
+
"command": "uv",
|
|
85
|
+
"args": [
|
|
86
|
+
"run",
|
|
87
|
+
"-m",
|
|
88
|
+
"openrouter_mcp_multimodal"
|
|
89
|
+
],
|
|
90
|
+
"env": {
|
|
91
|
+
"OPENROUTER_API_KEY": "your-api-key-here",
|
|
92
|
+
"OPENROUTER_DEFAULT_MODEL": "anthropic/claude-3.5-sonnet"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Option 3: Using Docker
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"mcpServers": {
|
|
104
|
+
"openrouter": {
|
|
105
|
+
"command": "docker",
|
|
106
|
+
"args": [
|
|
107
|
+
"run",
|
|
108
|
+
"--rm",
|
|
109
|
+
"-i",
|
|
110
|
+
"-e", "OPENROUTER_API_KEY=your-api-key-here",
|
|
111
|
+
"-e", "OPENROUTER_DEFAULT_MODEL=anthropic/claude-3.5-sonnet",
|
|
112
|
+
"stabgandocker/openrouter-mcp-multimodal:latest"
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### Option 4: Using Smithery (recommended)
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"openrouter": {
|
|
125
|
+
"command": "smithery",
|
|
126
|
+
"args": [
|
|
127
|
+
"run",
|
|
128
|
+
"stabgan/openrouter-mcp-multimodal"
|
|
129
|
+
],
|
|
130
|
+
"env": {
|
|
131
|
+
"OPENROUTER_API_KEY": "your-api-key-here",
|
|
132
|
+
"OPENROUTER_DEFAULT_MODEL": "anthropic/claude-3.5-sonnet"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Available Tools
|
|
140
|
+
|
|
141
|
+
### chat_completion
|
|
142
|
+
|
|
143
|
+
Send text or multimodal messages to OpenRouter models:
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
use_mcp_tool({
|
|
147
|
+
server_name: "openrouter",
|
|
148
|
+
tool_name: "chat_completion",
|
|
149
|
+
arguments: {
|
|
150
|
+
model: "google/gemini-2.5-pro-exp-03-25:free", // Optional if default is set
|
|
151
|
+
messages: [
|
|
152
|
+
{
|
|
153
|
+
role: "system",
|
|
154
|
+
content: "You are a helpful assistant."
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
role: "user",
|
|
158
|
+
content: "What is the capital of France?"
|
|
159
|
+
}
|
|
160
|
+
],
|
|
161
|
+
temperature: 0.7 // Optional, defaults to 1.0
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
For multimodal messages with images:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
use_mcp_tool({
|
|
170
|
+
server_name: "openrouter",
|
|
171
|
+
tool_name: "chat_completion",
|
|
172
|
+
arguments: {
|
|
173
|
+
model: "anthropic/claude-3.5-sonnet",
|
|
174
|
+
messages: [
|
|
175
|
+
{
|
|
176
|
+
role: "user",
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: "text",
|
|
180
|
+
text: "What's in this image?"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
type: "image_url",
|
|
184
|
+
image_url: {
|
|
185
|
+
url: "https://example.com/image.jpg"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### analyze_image
|
|
196
|
+
|
|
197
|
+
Analyze a single image with an optional question:
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
use_mcp_tool({
|
|
201
|
+
server_name: "openrouter",
|
|
202
|
+
tool_name: "analyze_image",
|
|
203
|
+
arguments: {
|
|
204
|
+
image_path: "/absolute/path/to/image.jpg",
|
|
205
|
+
question: "What objects are in this image?", // Optional
|
|
206
|
+
model: "anthropic/claude-3.5-sonnet" // Optional if default is set
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### multi_image_analysis
|
|
212
|
+
|
|
213
|
+
Analyze multiple images with a single prompt:
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
use_mcp_tool({
|
|
217
|
+
server_name: "openrouter",
|
|
218
|
+
tool_name: "multi_image_analysis",
|
|
219
|
+
arguments: {
|
|
220
|
+
images: [
|
|
221
|
+
{ url: "https://example.com/image1.jpg" },
|
|
222
|
+
{ url: "file:///absolute/path/to/image2.jpg" },
|
|
223
|
+
{
|
|
224
|
+
url: "https://example.com/image3.jpg",
|
|
225
|
+
alt: "Optional description of image 3"
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
prompt: "Compare these images and tell me their similarities and differences",
|
|
229
|
+
markdown_response: true, // Optional, defaults to true
|
|
230
|
+
model: "anthropic/claude-3-opus" // Optional if default is set
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### search_models
|
|
236
|
+
|
|
237
|
+
Search and filter available models:
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
use_mcp_tool({
|
|
241
|
+
server_name: "openrouter",
|
|
242
|
+
tool_name: "search_models",
|
|
243
|
+
arguments: {
|
|
244
|
+
query: "claude", // Optional text search
|
|
245
|
+
provider: "anthropic", // Optional provider filter
|
|
246
|
+
capabilities: {
|
|
247
|
+
vision: true // Filter for models with vision capabilities
|
|
248
|
+
},
|
|
249
|
+
limit: 5 // Optional, defaults to 10
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### get_model_info
|
|
255
|
+
|
|
256
|
+
Get detailed information about a specific model:
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
use_mcp_tool({
|
|
260
|
+
server_name: "openrouter",
|
|
261
|
+
tool_name: "get_model_info",
|
|
262
|
+
arguments: {
|
|
263
|
+
model: "anthropic/claude-3.5-sonnet"
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### validate_model
|
|
269
|
+
|
|
270
|
+
Check if a model ID is valid:
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
use_mcp_tool({
|
|
274
|
+
server_name: "openrouter",
|
|
275
|
+
tool_name: "validate_model",
|
|
276
|
+
arguments: {
|
|
277
|
+
model: "google/gemini-2.5-pro-exp-03-25:free"
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Error Handling
|
|
283
|
+
|
|
284
|
+
The server provides detailed error messages for various failure cases:
|
|
285
|
+
|
|
286
|
+
- Invalid input parameters
|
|
287
|
+
- Network errors
|
|
288
|
+
- Rate limiting issues
|
|
289
|
+
- Invalid image formats
|
|
290
|
+
- Authentication problems
|
|
291
|
+
|
|
292
|
+
## Troubleshooting
|
|
293
|
+
|
|
294
|
+
### Common Issues
|
|
295
|
+
|
|
296
|
+
- **"fetch is not defined" error**: This often occurs when the Node.js environment doesn't have global fetch. Use Node.js v18+ or add the PATH environment variable to your configuration as shown below:
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
{
|
|
300
|
+
"mcpServers": {
|
|
301
|
+
"openrouter": {
|
|
302
|
+
"command": "npx",
|
|
303
|
+
"args": [
|
|
304
|
+
"-y",
|
|
305
|
+
"@stabgan/openrouter-mcp-multimodal"
|
|
306
|
+
],
|
|
307
|
+
"env": {
|
|
308
|
+
"OPENROUTER_API_KEY": "your-api-key-here",
|
|
309
|
+
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
- **Image analysis failures**: Make sure your image path is absolute and the file format is supported.
|
|
317
|
+
|
|
318
|
+
## Development
|
|
319
|
+
|
|
320
|
+
To build from source:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
git clone https://github.com/stabgan/openrouter-mcp-multimodal.git
|
|
324
|
+
cd openrouter-mcp-multimodal
|
|
325
|
+
npm install
|
|
326
|
+
npm run build
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## License
|
|
330
|
+
|
|
331
|
+
MIT License
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// OpenRouter Multimodal MCP Server
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { ToolHandlers } from './tool-handlers.js';
|
|
6
|
+
class OpenRouterMultimodalServer {
|
|
7
|
+
server;
|
|
8
|
+
toolHandlers; // Using definite assignment assertion
|
|
9
|
+
constructor() {
|
|
10
|
+
// Get API key and default model from environment variables
|
|
11
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
12
|
+
const defaultModel = process.env.OPENROUTER_DEFAULT_MODEL;
|
|
13
|
+
// Check if API key is provided
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
throw new Error('OPENROUTER_API_KEY environment variable is required');
|
|
16
|
+
}
|
|
17
|
+
// Initialize the server
|
|
18
|
+
this.server = new Server({
|
|
19
|
+
name: 'openrouter-multimodal-server',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
}, {
|
|
22
|
+
capabilities: {
|
|
23
|
+
tools: {},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
// Set up error handling
|
|
27
|
+
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
|
28
|
+
// Initialize tool handlers
|
|
29
|
+
this.toolHandlers = new ToolHandlers(this.server, apiKey, defaultModel);
|
|
30
|
+
process.on('SIGINT', async () => {
|
|
31
|
+
await this.server.close();
|
|
32
|
+
process.exit(0);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async run() {
|
|
36
|
+
const transport = new StdioServerTransport();
|
|
37
|
+
await this.server.connect(transport);
|
|
38
|
+
console.error('OpenRouter Multimodal MCP server running on stdio');
|
|
39
|
+
console.error('Using API key from environment variable');
|
|
40
|
+
console.error('Note: To use OpenRouter Multimodal, add the API key to your environment variables:');
|
|
41
|
+
console.error(' OPENROUTER_API_KEY=your-api-key');
|
|
42
|
+
if (process.env.OPENROUTER_DEFAULT_MODEL) {
|
|
43
|
+
console.error(` Using default model: ${process.env.OPENROUTER_DEFAULT_MODEL}`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.error(' No default model set. You will need to specify a model in each request.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const server = new OpenRouterMultimodalServer();
|
|
51
|
+
server.run().catch(console.error);
|
|
52
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,mCAAmC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,0BAA0B;IACtB,MAAM,CAAS;IACf,YAAY,CAAgB,CAAC,sCAAsC;IAE3E;QACE,2DAA2D;QAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;QAC9C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;QAE1D,+BAA+B;QAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACzE,CAAC;QAED,wBAAwB;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CACtB;YACE,IAAI,EAAE,8BAA8B;YACpC,OAAO,EAAE,OAAO;SACjB,EACD;YACE,YAAY,EAAE;gBACZ,KAAK,EAAE,EAAE;aACV;SACF,CACF,CAAC;QAEF,wBAAwB;QACxB,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;QAErE,2BAA2B;QAC3B,IAAI,CAAC,YAAY,GAAG,IAAI,YAAY,CAClC,IAAI,CAAC,MAAM,EACX,MAAM,EACN,YAAY,CACb,CAAC;QAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;YAC9B,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAG;QACP,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC7C,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACrC,OAAO,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACnE,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;QACzD,OAAO,CAAC,KAAK,CAAC,oFAAoF,CAAC,CAAC;QACpG,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;QACvD,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC;YACzC,OAAO,CAAC,KAAK,CAAC,8BAA8B,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC,CAAC;QACtF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,+EAA+E,CAAC,CAAC;QACjG,CAAC;IACH,CAAC;CACF;AAED,MAAM,MAAM,GAAG,IAAI,0BAA0B,EAAE,CAAC;AAChD,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModelCache - Caches OpenRouter model data to reduce API calls
|
|
3
|
+
*/
|
|
4
|
+
export declare class ModelCache {
|
|
5
|
+
private static instance;
|
|
6
|
+
private models;
|
|
7
|
+
private lastFetchTime;
|
|
8
|
+
private cacheExpiryTime;
|
|
9
|
+
private constructor();
|
|
10
|
+
/**
|
|
11
|
+
* Get singleton instance
|
|
12
|
+
*/
|
|
13
|
+
static getInstance(): ModelCache;
|
|
14
|
+
/**
|
|
15
|
+
* Check if the cache is valid
|
|
16
|
+
*/
|
|
17
|
+
isCacheValid(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Store all models
|
|
20
|
+
*/
|
|
21
|
+
setModels(models: any[]): void;
|
|
22
|
+
/**
|
|
23
|
+
* Get all cached models
|
|
24
|
+
*/
|
|
25
|
+
getAllModels(): any[];
|
|
26
|
+
/**
|
|
27
|
+
* Get a specific model by ID
|
|
28
|
+
*/
|
|
29
|
+
getModel(modelId: string): any | null;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a model exists
|
|
32
|
+
*/
|
|
33
|
+
hasModel(modelId: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Search models based on criteria
|
|
36
|
+
*/
|
|
37
|
+
searchModels(params: {
|
|
38
|
+
query?: string;
|
|
39
|
+
provider?: string;
|
|
40
|
+
minContextLength?: number | string;
|
|
41
|
+
maxContextLength?: number | string;
|
|
42
|
+
maxPromptPrice?: number | string;
|
|
43
|
+
maxCompletionPrice?: number | string;
|
|
44
|
+
capabilities?: {
|
|
45
|
+
functions?: boolean;
|
|
46
|
+
tools?: boolean;
|
|
47
|
+
vision?: boolean;
|
|
48
|
+
json_mode?: boolean;
|
|
49
|
+
};
|
|
50
|
+
limit?: number | string;
|
|
51
|
+
}): any[];
|
|
52
|
+
/**
|
|
53
|
+
* Reset the cache
|
|
54
|
+
*/
|
|
55
|
+
resetCache(): void;
|
|
56
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModelCache - Caches OpenRouter model data to reduce API calls
|
|
3
|
+
*/
|
|
4
|
+
export class ModelCache {
|
|
5
|
+
static instance;
|
|
6
|
+
models;
|
|
7
|
+
lastFetchTime;
|
|
8
|
+
cacheExpiryTime; // in milliseconds (1 hour = 3600000)
|
|
9
|
+
constructor() {
|
|
10
|
+
this.models = {};
|
|
11
|
+
this.lastFetchTime = 0;
|
|
12
|
+
this.cacheExpiryTime = 3600000; // 1 hour
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get singleton instance
|
|
16
|
+
*/
|
|
17
|
+
static getInstance() {
|
|
18
|
+
if (!ModelCache.instance) {
|
|
19
|
+
ModelCache.instance = new ModelCache();
|
|
20
|
+
}
|
|
21
|
+
return ModelCache.instance;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if the cache is valid
|
|
25
|
+
*/
|
|
26
|
+
isCacheValid() {
|
|
27
|
+
return (Object.keys(this.models).length > 0 &&
|
|
28
|
+
Date.now() - this.lastFetchTime < this.cacheExpiryTime);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Store all models
|
|
32
|
+
*/
|
|
33
|
+
setModels(models) {
|
|
34
|
+
this.models = {};
|
|
35
|
+
for (const model of models) {
|
|
36
|
+
this.models[model.id] = model;
|
|
37
|
+
}
|
|
38
|
+
this.lastFetchTime = Date.now();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get all cached models
|
|
42
|
+
*/
|
|
43
|
+
getAllModels() {
|
|
44
|
+
return Object.values(this.models);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get a specific model by ID
|
|
48
|
+
*/
|
|
49
|
+
getModel(modelId) {
|
|
50
|
+
return this.models[modelId] || null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if a model exists
|
|
54
|
+
*/
|
|
55
|
+
hasModel(modelId) {
|
|
56
|
+
return !!this.models[modelId];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Search models based on criteria
|
|
60
|
+
*/
|
|
61
|
+
searchModels(params) {
|
|
62
|
+
let results = this.getAllModels();
|
|
63
|
+
// Apply text search
|
|
64
|
+
if (params.query) {
|
|
65
|
+
const query = params.query.toLowerCase();
|
|
66
|
+
results = results.filter((model) => model.id.toLowerCase().includes(query) ||
|
|
67
|
+
(model.description && model.description.toLowerCase().includes(query)) ||
|
|
68
|
+
(model.provider && model.provider.toLowerCase().includes(query)));
|
|
69
|
+
}
|
|
70
|
+
// Filter by provider
|
|
71
|
+
if (params.provider) {
|
|
72
|
+
results = results.filter((model) => model.provider && model.provider.toLowerCase() === params.provider.toLowerCase());
|
|
73
|
+
}
|
|
74
|
+
// Filter by context length
|
|
75
|
+
if (params.minContextLength !== undefined) {
|
|
76
|
+
const minContextLength = typeof params.minContextLength === 'string'
|
|
77
|
+
? parseInt(params.minContextLength, 10)
|
|
78
|
+
: params.minContextLength;
|
|
79
|
+
if (!isNaN(minContextLength)) {
|
|
80
|
+
results = results.filter((model) => model.context_length >= minContextLength);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (params.maxContextLength !== undefined) {
|
|
84
|
+
const maxContextLength = typeof params.maxContextLength === 'string'
|
|
85
|
+
? parseInt(params.maxContextLength, 10)
|
|
86
|
+
: params.maxContextLength;
|
|
87
|
+
if (!isNaN(maxContextLength)) {
|
|
88
|
+
results = results.filter((model) => model.context_length <= maxContextLength);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Filter by price
|
|
92
|
+
if (params.maxPromptPrice !== undefined) {
|
|
93
|
+
const maxPromptPrice = typeof params.maxPromptPrice === 'string'
|
|
94
|
+
? parseFloat(params.maxPromptPrice)
|
|
95
|
+
: params.maxPromptPrice;
|
|
96
|
+
if (!isNaN(maxPromptPrice)) {
|
|
97
|
+
results = results.filter((model) => !model.pricing?.prompt || model.pricing.prompt <= maxPromptPrice);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (params.maxCompletionPrice !== undefined) {
|
|
101
|
+
const maxCompletionPrice = typeof params.maxCompletionPrice === 'string'
|
|
102
|
+
? parseFloat(params.maxCompletionPrice)
|
|
103
|
+
: params.maxCompletionPrice;
|
|
104
|
+
if (!isNaN(maxCompletionPrice)) {
|
|
105
|
+
results = results.filter((model) => !model.pricing?.completion ||
|
|
106
|
+
model.pricing.completion <= maxCompletionPrice);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Filter by capabilities
|
|
110
|
+
if (params.capabilities) {
|
|
111
|
+
if (params.capabilities.functions) {
|
|
112
|
+
results = results.filter((model) => model.capabilities?.function_calling);
|
|
113
|
+
}
|
|
114
|
+
if (params.capabilities.tools) {
|
|
115
|
+
results = results.filter((model) => model.capabilities?.tools);
|
|
116
|
+
}
|
|
117
|
+
if (params.capabilities.vision) {
|
|
118
|
+
results = results.filter((model) => model.capabilities?.vision);
|
|
119
|
+
}
|
|
120
|
+
if (params.capabilities.json_mode) {
|
|
121
|
+
results = results.filter((model) => model.capabilities?.json_mode);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Apply limit
|
|
125
|
+
if (params.limit !== undefined) {
|
|
126
|
+
const limit = typeof params.limit === 'string'
|
|
127
|
+
? parseInt(params.limit, 10)
|
|
128
|
+
: params.limit;
|
|
129
|
+
if (!isNaN(limit) && limit > 0) {
|
|
130
|
+
results = results.slice(0, limit);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Reset the cache
|
|
137
|
+
*/
|
|
138
|
+
resetCache() {
|
|
139
|
+
this.models = {};
|
|
140
|
+
this.lastFetchTime = 0;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=model-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-cache.js","sourceRoot":"","sources":["../src/model-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for interacting with the OpenRouter API
|
|
3
|
+
*/
|
|
4
|
+
export declare class OpenRouterAPIClient {
|
|
5
|
+
private apiKey;
|
|
6
|
+
private axiosInstance;
|
|
7
|
+
private retryCount;
|
|
8
|
+
private retryDelay;
|
|
9
|
+
constructor(apiKey: string);
|
|
10
|
+
/**
|
|
11
|
+
* Get all available models from OpenRouter
|
|
12
|
+
*/
|
|
13
|
+
getModels(): Promise<any[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Send a request to the OpenRouter API with retry functionality
|
|
16
|
+
*/
|
|
17
|
+
request(endpoint: string, method: string, data?: any): Promise<any>;
|
|
18
|
+
/**
|
|
19
|
+
* Handle retryable errors
|
|
20
|
+
*/
|
|
21
|
+
private handleRetryableError;
|
|
22
|
+
/**
|
|
23
|
+
* Handle request errors
|
|
24
|
+
*/
|
|
25
|
+
private handleRequestError;
|
|
26
|
+
}
|