@stabgan/openrouter-mcp-multimodal 4.7.0 → 5.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.
Files changed (63) hide show
  1. package/README.md +95 -41
  2. package/dist/errors.d.ts +5 -20
  3. package/dist/errors.js +1 -10
  4. package/dist/index.js +8 -2
  5. package/dist/logger.js +54 -24
  6. package/dist/model-cache.d.ts +13 -0
  7. package/dist/model-cache.js +62 -8
  8. package/dist/openrouter-api.d.ts +14 -15
  9. package/dist/openrouter-api.js +68 -22
  10. package/dist/tool-definitions.d.ts +24 -0
  11. package/dist/tool-definitions.js +280 -177
  12. package/dist/tool-descriptions.d.ts +0 -4
  13. package/dist/tool-descriptions.js +30 -21
  14. package/dist/tool-handlers/analyze-audio.js +4 -1
  15. package/dist/tool-handlers/analyze-image.js +11 -6
  16. package/dist/tool-handlers/analyze-video.js +9 -5
  17. package/dist/tool-handlers/async-chat.d.ts +17 -0
  18. package/dist/tool-handlers/async-chat.js +104 -30
  19. package/dist/tool-handlers/audio-utils.d.ts +19 -4
  20. package/dist/tool-handlers/audio-utils.js +170 -16
  21. package/dist/tool-handlers/cache.d.ts +3 -3
  22. package/dist/tool-handlers/cache.js +56 -4
  23. package/dist/tool-handlers/chat-completion.js +16 -7
  24. package/dist/tool-handlers/chat-request.d.ts +4 -1
  25. package/dist/tool-handlers/chat-request.js +29 -1
  26. package/dist/tool-handlers/completion-utils.d.ts +5 -11
  27. package/dist/tool-handlers/completion-utils.js +76 -47
  28. package/dist/tool-handlers/fetch-utils.d.ts +14 -0
  29. package/dist/tool-handlers/fetch-utils.js +329 -77
  30. package/dist/tool-handlers/generate-audio.d.ts +4 -15
  31. package/dist/tool-handlers/generate-audio.js +21 -53
  32. package/dist/tool-handlers/generate-image-dedicated.d.ts +1 -1
  33. package/dist/tool-handlers/generate-image-dedicated.js +50 -31
  34. package/dist/tool-handlers/generate-image.d.ts +1 -1
  35. package/dist/tool-handlers/generate-image.js +19 -22
  36. package/dist/tool-handlers/generate-video.d.ts +4 -3
  37. package/dist/tool-handlers/generate-video.js +42 -18
  38. package/dist/tool-handlers/get-model-info.js +1 -1
  39. package/dist/tool-handlers/health-check.js +39 -15
  40. package/dist/tool-handlers/image-utils.js +2 -2
  41. package/dist/tool-handlers/openrouter-errors.d.ts +2 -0
  42. package/dist/tool-handlers/openrouter-errors.js +138 -31
  43. package/dist/tool-handlers/path-safety.js +49 -17
  44. package/dist/tool-handlers/path-utils.d.ts +2 -0
  45. package/dist/tool-handlers/path-utils.js +13 -0
  46. package/dist/tool-handlers/provider-routing.d.ts +2 -0
  47. package/dist/tool-handlers/provider-routing.js +11 -1
  48. package/dist/tool-handlers/rerank.d.ts +1 -4
  49. package/dist/tool-handlers/rerank.js +44 -15
  50. package/dist/tool-handlers/search-models.js +3 -3
  51. package/dist/tool-handlers/speech-to-text.d.ts +1 -0
  52. package/dist/tool-handlers/speech-to-text.js +23 -56
  53. package/dist/tool-handlers/text-to-speech.d.ts +1 -1
  54. package/dist/tool-handlers/text-to-speech.js +33 -20
  55. package/dist/tool-handlers/tool-result-payload.js +11 -9
  56. package/dist/tool-handlers/validate-model.js +1 -1
  57. package/dist/tool-handlers.d.ts +9 -0
  58. package/dist/tool-handlers.js +17 -5
  59. package/dist/tts-defaults.d.ts +4 -0
  60. package/dist/tts-defaults.js +4 -0
  61. package/dist/version.d.ts +3 -2
  62. package/dist/version.js +4 -2
  63. package/package.json +11 -13
@@ -1,8 +1,84 @@
1
- import { TOOL_DESCRIPTIONS } from './tool-descriptions.js';
2
- /** Shared JSON-schema fragment for save_path on generate/write tools. */
3
- const SAVE_PATH_PROPERTY = {
1
+ import { TOOL_DESCRIPTIONS, TOOL_NAMES } from './tool-descriptions.js';
2
+ import { DEFAULT_TTS_MODEL, DEFAULT_TTS_RESPONSE_FORMAT, DEFAULT_TTS_VOICE, } from './tts-defaults.js';
3
+ /** Aspect ratios accepted by generate_image and generate_image_dedicated handlers. */
4
+ export const IMAGE_ASPECT_RATIOS = [
5
+ '1:1',
6
+ '2:3',
7
+ '3:2',
8
+ '3:4',
9
+ '4:3',
10
+ '4:5',
11
+ '5:4',
12
+ '9:16',
13
+ '16:9',
14
+ '21:9',
15
+ '1:4',
16
+ '4:1',
17
+ '1:8',
18
+ '8:1',
19
+ ];
20
+ export const IMAGE_SIZES = ['0.5K', '1K', '2K', '4K'];
21
+ export const IMAGE_DEDICATED_RESOLUTIONS = ['512', '0.5K', '1K', '2K', '4K'];
22
+ export const IMAGE_DEDICATED_QUALITIES = ['auto', 'low', 'medium', 'high'];
23
+ export const IMAGE_OUTPUT_FORMATS = ['png', 'jpeg', 'webp', 'svg'];
24
+ /** generate_audio handler VALID_FORMATS */
25
+ export const GENERATE_AUDIO_FORMATS = ['wav', 'mp3', 'flac', 'opus', 'pcm16'];
26
+ /** text_to_speech handler VALID_FORMATS */
27
+ export const TTS_RESPONSE_FORMATS = ['mp3', 'pcm'];
28
+ /** speech_to_text handler VALID_RESPONSE_FORMATS */
29
+ export const STT_RESPONSE_FORMATS = ['json', 'text', 'srt', 'verbose_json', 'vtt'];
30
+ export const CHAT_MESSAGE_ROLES = ['system', 'user', 'assistant'];
31
+ export const PROVIDER_SORT_VALUES = ['price', 'throughput', 'latency'];
32
+ export const PROVIDER_DATA_COLLECTION = ['allow', 'deny'];
33
+ /** Tools whose handlers accept save_path (binary artifact output). */
34
+ export const TOOLS_WITH_SAVE_PATH = [
35
+ 'generate_image',
36
+ 'generate_image_dedicated',
37
+ 'generate_audio',
38
+ 'text_to_speech',
39
+ 'generate_video',
40
+ 'generate_video_from_image',
41
+ 'get_video_status',
42
+ ];
43
+ /** Shared save_path fragment for binary-output tools. */
44
+ export const SAVE_PATH_PROPERTY = {
4
45
  type: 'string',
5
- description: 'Write the artifact under OPENROUTER_OUTPUT_DIR (path-sandboxed). When set, the tool result is text-only with _meta.save_path — no inline media block. Without save_path, inline image/audio/video is returned only if under OPENROUTER_*_INLINE_MAX_BYTES (see .env.example).',
46
+ description: 'Write the artifact under OPENROUTER_OUTPUT_DIR (path-sandboxed). When set, the tool result is text-only with _meta.save_path — no inline media block. ' +
47
+ 'When unset, inline image/audio (default 1 MiB) or video (default 10 MiB) is returned only if under the per-kind ceiling: ' +
48
+ 'OPENROUTER_IMAGE_INLINE_MAX_BYTES, OPENROUTER_AUDIO_INLINE_MAX_BYTES, OPENROUTER_VIDEO_INLINE_MAX_BYTES ' +
49
+ '(global fallback OPENROUTER_INLINE_MAX_BYTES). See .env.example.',
50
+ };
51
+ const SAVE_PATH_WITH_PREFIX = (prefix) => ({
52
+ ...SAVE_PATH_PROPERTY,
53
+ description: `${prefix} ${SAVE_PATH_PROPERTY.description}`,
54
+ });
55
+ const CHAT_MESSAGE_SCHEMA = {
56
+ type: 'array',
57
+ minItems: 1,
58
+ items: {
59
+ type: 'object',
60
+ properties: {
61
+ role: { type: 'string', enum: [...CHAT_MESSAGE_ROLES] },
62
+ content: {
63
+ oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'object' } }],
64
+ },
65
+ },
66
+ required: ['role', 'content'],
67
+ },
68
+ };
69
+ const CACHE_PROPERTIES = {
70
+ cache: {
71
+ type: 'boolean',
72
+ description: 'Enable OpenRouter response caching via `X-OpenRouter-Cache: true`. Server default: `OPENROUTER_CACHE_RESPONSES=1`.',
73
+ },
74
+ cache_ttl: {
75
+ type: 'string',
76
+ description: 'Cache TTL as integer seconds (1–86400) or a duration string such as "30s", "5m", or "1h". Sent upstream as seconds.',
77
+ },
78
+ cache_clear: {
79
+ type: 'boolean',
80
+ description: 'Bust the cache entry for this exact request.',
81
+ },
6
82
  };
7
83
  export const TOOL_DEFINITIONS = [
8
84
  {
@@ -20,26 +96,10 @@ export const TOOL_DEFINITIONS = [
20
96
  properties: {
21
97
  model: {
22
98
  type: 'string',
23
- description: 'Model ID (optional, uses default). Append `:nitro` for the fastest variant, ' +
24
- '`:floor` for the cheapest, `:free` for the free tier, `:online` for web search, ' +
25
- 'or `:exacto` for the best tool-calling accuracy. ' +
26
- 'Example: `openai/gpt-4o:nitro`. Or pass `online: true` for programmatic web search control.',
99
+ description: 'Model ID (optional, uses server default). Append `:nitro` (fastest), `:floor` (cheapest), `:free`, `:online` (web search), or `:exacto` (tool accuracy). Example: `openai/gpt-4o:nitro`. Or pass `online: true` for programmatic web search.',
27
100
  },
28
- messages: {
29
- type: 'array',
30
- minItems: 1,
31
- items: {
32
- type: 'object',
33
- properties: {
34
- role: { type: 'string', enum: ['system', 'user', 'assistant'] },
35
- content: {
36
- oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'object' } }],
37
- },
38
- },
39
- required: ['role', 'content'],
40
- },
41
- },
42
- temperature: { type: 'number', minimum: 0, maximum: 2 },
101
+ messages: CHAT_MESSAGE_SCHEMA,
102
+ temperature: { type: 'number', minimum: 0, maximum: 2, description: 'Default: 1.' },
43
103
  max_tokens: {
44
104
  type: 'number',
45
105
  minimum: 1,
@@ -47,8 +107,7 @@ export const TOOL_DEFINITIONS = [
47
107
  },
48
108
  provider: {
49
109
  type: 'object',
50
- description: 'OpenRouter provider-routing overrides. Merges on top of `OPENROUTER_PROVIDER_*` env defaults. ' +
51
- 'See https://openrouter.ai/docs/features/provider-routing',
110
+ description: 'OpenRouter provider-routing overrides. Merges on top of `OPENROUTER_PROVIDER_*` env defaults. See https://openrouter.ai/docs/features/provider-routing',
52
111
  properties: {
53
112
  quantizations: {
54
113
  type: 'array',
@@ -60,19 +119,16 @@ export const TOOL_DEFINITIONS = [
60
119
  items: { type: 'string' },
61
120
  description: 'Exclude these provider slugs.',
62
121
  },
63
- sort: {
64
- type: 'string',
65
- enum: ['price', 'throughput', 'latency'],
66
- },
122
+ sort: { type: 'string', enum: [...PROVIDER_SORT_VALUES] },
67
123
  order: { type: 'array', items: { type: 'string' } },
68
124
  require_parameters: { type: 'boolean' },
69
- data_collection: { type: 'string', enum: ['allow', 'deny'] },
125
+ data_collection: { type: 'string', enum: [...PROVIDER_DATA_COLLECTION] },
70
126
  allow_fallbacks: { type: 'boolean' },
71
127
  },
72
128
  },
73
129
  include_reasoning: {
74
130
  type: 'boolean',
75
- description: "Surface the model's chain-of-thought on `_meta.reasoning` for R1 / Opus 4.7 / Gemini Thinking.",
131
+ description: "Surface the model's chain-of-thought on `_meta.reasoning` for R1 / Opus / Gemini Thinking models.",
76
132
  },
77
133
  online: {
78
134
  type: 'boolean',
@@ -83,19 +139,7 @@ export const TOOL_DEFINITIONS = [
83
139
  minimum: 1,
84
140
  description: 'Max web-search results when `online: true` (default 5).',
85
141
  },
86
- cache: {
87
- type: 'boolean',
88
- description: 'Enable OpenRouter response caching via `X-OpenRouter-Cache: true`. ' +
89
- 'Server-wide default settable via `OPENROUTER_CACHE_RESPONSES=1`.',
90
- },
91
- cache_ttl: {
92
- type: 'string',
93
- description: 'Cache TTL (e.g. `"5m"`, `"1h"`, `"24h"`; 1s-24h range).',
94
- },
95
- cache_clear: {
96
- type: 'boolean',
97
- description: 'Bust the cache entry for this exact request.',
98
- },
142
+ ...CACHE_PROPERTIES,
99
143
  },
100
144
  required: ['messages'],
101
145
  },
@@ -113,30 +157,15 @@ export const TOOL_DEFINITIONS = [
113
157
  inputSchema: {
114
158
  type: 'object',
115
159
  properties: {
116
- model: { type: 'string', description: 'Model ID (same as chat_completion).' },
117
- messages: {
118
- type: 'array',
119
- minItems: 1,
120
- items: {
121
- type: 'object',
122
- properties: {
123
- role: { type: 'string', enum: ['system', 'user', 'assistant'] },
124
- content: {
125
- oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'object' } }],
126
- },
127
- },
128
- required: ['role', 'content'],
129
- },
130
- },
160
+ model: { type: 'string', description: 'Model ID (same options as chat_completion).' },
161
+ messages: CHAT_MESSAGE_SCHEMA,
131
162
  temperature: { type: 'number', minimum: 0, maximum: 2 },
132
163
  max_tokens: { type: 'number', minimum: 1 },
133
164
  provider: { type: 'object' },
134
165
  include_reasoning: { type: 'boolean' },
135
166
  online: { type: 'boolean' },
136
167
  web_max_results: { type: 'number', minimum: 1 },
137
- cache: { type: 'boolean' },
138
- cache_ttl: { type: 'string' },
139
- cache_clear: { type: 'boolean' },
168
+ ...CACHE_PROPERTIES,
140
169
  },
141
170
  required: ['messages'],
142
171
  },
@@ -177,23 +206,21 @@ export const TOOL_DEFINITIONS = [
177
206
  properties: {
178
207
  image_path: {
179
208
  type: 'string',
180
- description: 'Required. Local path (inside OPENROUTER_INPUT_DIR sandbox), https URL, or data URL. ' +
181
- 'Good: `"photo.jpg"`. Bad: `"url": "..."` (wrong key), `"/etc/passwd"` (UNSAFE_PATH).',
209
+ description: 'Local path (OPENROUTER_INPUT_DIR sandbox), https URL, or data URL. Good: `"photo.jpg"`. Bad: `"url": "..."` (wrong key), `"/etc/passwd"` (UNSAFE_PATH).',
182
210
  },
183
211
  question: {
184
212
  type: 'string',
185
- description: 'Optional question about the image. Defaults to "What\'s in this image?" if omitted. ' +
186
- 'Good: `"List all text"`. Bad: using `prompt` key (wrong name for this tool).',
213
+ description: 'Optional question. Default: "What\'s in this image?". Bad: using `prompt` (wrong key for this tool).',
214
+ },
215
+ model: {
216
+ type: 'string',
217
+ description: 'Vision model ID (optional; server default is a free multimodal model).',
187
218
  },
188
- model: { type: 'string' },
189
219
  cache_input: {
190
220
  type: 'boolean',
191
- description: 'Attach `cache_control: ephemeral` to the image block so Anthropic / Gemini prompt-cache it. ' +
192
- 'Repeat questions about the same image save ~10x on Anthropic.',
221
+ description: 'Attach `cache_control: ephemeral` to the image block for Anthropic / Gemini prompt caching.',
193
222
  },
194
- cache: { type: 'boolean' },
195
- cache_ttl: { type: 'string' },
196
- cache_clear: { type: 'boolean' },
223
+ ...CACHE_PROPERTIES,
197
224
  },
198
225
  required: ['image_path'],
199
226
  },
@@ -213,18 +240,18 @@ export const TOOL_DEFINITIONS = [
213
240
  properties: {
214
241
  audio_path: {
215
242
  type: 'string',
216
- description: 'Local file path (sandboxed to OPENROUTER_INPUT_DIR / OPENROUTER_OUTPUT_DIR / cwd), ' +
217
- 'http(s) URL, or data URL (base64-encoded audio)',
243
+ description: 'Local file path (sandboxed), http(s) URL, or data URL (base64-encoded audio).',
218
244
  },
219
245
  question: {
220
246
  type: 'string',
221
- description: 'Question or instruction about the audio (default: transcribe)',
247
+ description: 'Question or instruction. Default: "Please transcribe and analyze this audio file."',
248
+ },
249
+ model: {
250
+ type: 'string',
251
+ description: 'Multimodal model ID (default: google/gemini-2.5-flash).',
222
252
  },
223
- model: { type: 'string' },
224
253
  cache_input: { type: 'boolean' },
225
- cache: { type: 'boolean' },
226
- cache_ttl: { type: 'string' },
227
- cache_clear: { type: 'boolean' },
254
+ ...CACHE_PROPERTIES,
228
255
  },
229
256
  required: ['audio_path'],
230
257
  },
@@ -244,15 +271,18 @@ export const TOOL_DEFINITIONS = [
244
271
  properties: {
245
272
  video_path: {
246
273
  type: 'string',
247
- description: 'Local file path (sandboxed to OPENROUTER_INPUT_DIR / OPENROUTER_OUTPUT_DIR / cwd), ' +
248
- 'http(s) URL, or base64 data URL. Supported: mp4 / mpeg / mov / webm.',
274
+ description: 'Local file path (sandboxed), http(s) URL, or base64 data URL. Supported containers: mp4, mpeg, mov, webm.',
275
+ },
276
+ question: {
277
+ type: 'string',
278
+ description: 'Optional question. Default: "Describe what happens in this video, step by step."',
279
+ },
280
+ model: {
281
+ type: 'string',
282
+ description: 'Video-capable model ID (default: google/gemini-2.5-flash).',
249
283
  },
250
- question: { type: 'string' },
251
- model: { type: 'string' },
252
284
  cache_input: { type: 'boolean' },
253
- cache: { type: 'boolean' },
254
- cache_ttl: { type: 'string' },
255
- cache_clear: { type: 'boolean' },
285
+ ...CACHE_PROPERTIES,
256
286
  },
257
287
  required: ['video_path'],
258
288
  },
@@ -270,8 +300,11 @@ export const TOOL_DEFINITIONS = [
270
300
  inputSchema: {
271
301
  type: 'object',
272
302
  properties: {
273
- query: { type: 'string' },
274
- provider: { type: 'string' },
303
+ query: { type: 'string', description: 'Substring match against model id or name.' },
304
+ provider: {
305
+ type: 'string',
306
+ description: 'Filter by provider slug prefix (e.g. `google`).',
307
+ },
275
308
  capabilities: {
276
309
  type: 'object',
277
310
  properties: {
@@ -280,8 +313,13 @@ export const TOOL_DEFINITIONS = [
280
313
  video: { type: 'boolean' },
281
314
  },
282
315
  },
283
- limit: { type: 'number', minimum: 1, maximum: 50 },
284
- offset: { type: 'number', minimum: 0 },
316
+ limit: {
317
+ type: 'number',
318
+ minimum: 1,
319
+ maximum: 50,
320
+ description: 'Page size (default 20, max 50).',
321
+ },
322
+ offset: { type: 'number', minimum: 0, description: 'Pagination offset (default 0).' },
285
323
  },
286
324
  },
287
325
  outputSchema: {
@@ -309,7 +347,12 @@ export const TOOL_DEFINITIONS = [
309
347
  },
310
348
  inputSchema: {
311
349
  type: 'object',
312
- properties: { model: { type: 'string' } },
350
+ properties: {
351
+ model: {
352
+ type: 'string',
353
+ description: 'Full OpenRouter model slug (e.g. `openai/gpt-4o`).',
354
+ },
355
+ },
313
356
  required: ['model'],
314
357
  },
315
358
  outputSchema: {
@@ -335,7 +378,9 @@ export const TOOL_DEFINITIONS = [
335
378
  },
336
379
  inputSchema: {
337
380
  type: 'object',
338
- properties: { model: { type: 'string' } },
381
+ properties: {
382
+ model: { type: 'string', description: 'Full OpenRouter model slug to check.' },
383
+ },
339
384
  required: ['model'],
340
385
  },
341
386
  outputSchema: {
@@ -360,32 +405,33 @@ export const TOOL_DEFINITIONS = [
360
405
  inputSchema: {
361
406
  type: 'object',
362
407
  properties: {
363
- prompt: { type: 'string' },
364
- model: { type: 'string' },
408
+ prompt: { type: 'string', description: 'Text prompt describing the image to generate.' },
409
+ model: {
410
+ type: 'string',
411
+ description: 'Image model ID (default: google/gemini-2.5-flash-image). Chat-completions route.',
412
+ },
365
413
  aspect_ratio: {
366
414
  type: 'string',
367
- enum: [
368
- '1:1',
369
- '2:3',
370
- '3:2',
371
- '3:4',
372
- '4:3',
373
- '4:5',
374
- '5:4',
375
- '9:16',
376
- '16:9',
377
- '21:9',
378
- '1:4',
379
- '4:1',
380
- '1:8',
381
- '8:1',
382
- ],
383
- },
384
- image_size: { type: 'string', enum: ['0.5K', '1K', '2K', '4K'] },
385
- max_tokens: { type: 'number', minimum: 1 },
415
+ enum: [...IMAGE_ASPECT_RATIOS],
416
+ description: 'Optional aspect ratio (provider-dependent).',
417
+ },
418
+ image_size: {
419
+ type: 'string',
420
+ enum: [...IMAGE_SIZES],
421
+ description: 'Optional resolution tier for supported models.',
422
+ },
423
+ max_tokens: { type: 'number', minimum: 1, description: 'Optional completion token cap.' },
386
424
  save_path: SAVE_PATH_PROPERTY,
387
- input_images: { type: 'array', items: { type: 'string' } },
388
- modalities: { type: 'array', items: { type: 'string' } },
425
+ input_images: {
426
+ type: 'array',
427
+ items: { type: 'string' },
428
+ description: 'Reference images (local path, URL, or data URL) for style/identity conditioning.',
429
+ },
430
+ modalities: {
431
+ type: 'array',
432
+ items: { type: 'string' },
433
+ description: 'Response modalities (default: `["image","text"]`).',
434
+ },
389
435
  },
390
436
  required: ['prompt'],
391
437
  },
@@ -409,49 +455,45 @@ export const TOOL_DEFINITIONS = [
409
455
  },
410
456
  model: {
411
457
  type: 'string',
412
- description: 'Image model ID. Default: google/gemini-2.5-flash-image. Browse available at https://openrouter.ai/collections/image-models',
458
+ description: 'Image model ID. Default: google/gemini-2.5-flash-image. Browse: https://openrouter.ai/collections/image-models',
413
459
  },
414
460
  resolution: {
415
461
  type: 'string',
416
- enum: ['512', '0.5K', '1K', '2K', '4K'],
462
+ enum: [...IMAGE_DEDICATED_RESOLUTIONS],
417
463
  description: 'Normalized resolution tier. Provider maps to closest supported size.',
418
464
  },
419
465
  aspect_ratio: {
420
466
  type: 'string',
421
- description: 'Aspect ratio (e.g. "1:1", "16:9", "9:16", "4:3", "21:9").',
467
+ enum: [...IMAGE_ASPECT_RATIOS],
468
+ description: 'Aspect ratio (same enum as generate_image).',
422
469
  },
423
470
  quality: {
424
471
  type: 'string',
425
- enum: ['auto', 'low', 'medium', 'high'],
426
- description: 'Image quality level.',
472
+ enum: [...IMAGE_DEDICATED_QUALITIES],
473
+ description: 'Image quality level (default: auto).',
427
474
  },
428
475
  output_format: {
429
476
  type: 'string',
430
- enum: ['png', 'jpeg', 'webp', 'svg'],
477
+ enum: [...IMAGE_OUTPUT_FORMATS],
431
478
  description: 'Output image format.',
432
479
  },
433
480
  n: {
434
481
  type: 'number',
435
482
  minimum: 1,
436
483
  maximum: 10,
437
- description: 'Number of images to generate (model-dependent, default 1).',
484
+ description: 'Number of images to request (default 1; only images[0] is saved/inlined).',
438
485
  },
439
486
  input_references: {
440
487
  type: 'array',
441
488
  items: { type: 'string' },
442
- description: 'Reference images for image-to-image workflows. Each entry: local path, http(s) URL, or data URL.',
443
- },
444
- save_path: {
445
- ...SAVE_PATH_PROPERTY,
446
- description: 'Save generated image to this path. ' + SAVE_PATH_PROPERTY.description,
489
+ description: 'Reference images for image-to-image. Each entry: local path, http(s) URL, or data URL.',
447
490
  },
491
+ save_path: SAVE_PATH_WITH_PREFIX('Save generated image to this path.'),
448
492
  provider: {
449
493
  type: 'object',
450
494
  description: 'Provider routing overrides (order, sort, allow_fallbacks, etc.).',
451
495
  },
452
- cache: { type: 'boolean' },
453
- cache_ttl: { type: 'string' },
454
- cache_clear: { type: 'boolean' },
496
+ ...CACHE_PROPERTIES,
455
497
  },
456
498
  required: ['prompt'],
457
499
  },
@@ -469,10 +511,20 @@ export const TOOL_DEFINITIONS = [
469
511
  inputSchema: {
470
512
  type: 'object',
471
513
  properties: {
472
- prompt: { type: 'string' },
473
- model: { type: 'string' },
474
- voice: { type: 'string' },
475
- format: { type: 'string' },
514
+ prompt: { type: 'string', description: 'Text prompt for speech or music generation.' },
515
+ model: {
516
+ type: 'string',
517
+ description: 'Chat-completions audio model (default: openai/gpt-audio).',
518
+ },
519
+ voice: {
520
+ type: 'string',
521
+ description: 'Voice ID (default: alloy). Model-specific.',
522
+ },
523
+ format: {
524
+ type: 'string',
525
+ enum: [...GENERATE_AUDIO_FORMATS],
526
+ description: 'Output audio format (default: pcm16, auto-wrapped as WAV when needed).',
527
+ },
476
528
  save_path: SAVE_PATH_PROPERTY,
477
529
  },
478
530
  required: ['prompt'],
@@ -497,34 +549,29 @@ export const TOOL_DEFINITIONS = [
497
549
  },
498
550
  model: {
499
551
  type: 'string',
500
- description: 'TTS model. Default: openai/gpt-4o-mini-tts-2025-12-15. Also: google/gemini-flash-tts, mistral/voxtral-mini-tts.',
552
+ description: `TTS model for OpenRouter POST /audio/speech. Default: ${DEFAULT_TTS_MODEL}. Discover current models with GET /api/v1/models?output_modalities=speech.`,
501
553
  },
502
554
  voice: {
503
555
  type: 'string',
504
- description: 'Voice ID (model-specific). Default: alloy. OpenAI voices: alloy, echo, fable, onyx, nova, shimmer.',
556
+ description: `Voice ID (model-specific). Default: ${DEFAULT_TTS_VOICE} for ${DEFAULT_TTS_MODEL}; pass a voice supported by another model.`,
505
557
  },
506
558
  response_format: {
507
559
  type: 'string',
508
- enum: ['mp3', 'opus', 'aac', 'flac', 'wav', 'pcm'],
509
- description: 'Output audio format. Default: mp3.',
560
+ enum: [...TTS_RESPONSE_FORMATS],
561
+ description: `Output audio format (mp3 or pcm). Default: ${DEFAULT_TTS_RESPONSE_FORMAT}.`,
510
562
  },
511
563
  speed: {
512
564
  type: 'number',
513
565
  minimum: 0.25,
514
566
  maximum: 4.0,
515
- description: 'Speed of speech (0.25 to 4.0). Default: 1.0.',
567
+ description: 'Speed of speech (0.25–4.0). Default: 1.0.',
516
568
  },
517
569
  instructions: {
518
570
  type: 'string',
519
571
  description: 'Tone/style instructions (e.g. "speak in a warm, friendly tone"). OpenAI models only.',
520
572
  },
521
- save_path: {
522
- ...SAVE_PATH_PROPERTY,
523
- description: 'Save audio to this path. ' + SAVE_PATH_PROPERTY.description,
524
- },
525
- cache: { type: 'boolean' },
526
- cache_ttl: { type: 'string' },
527
- cache_clear: { type: 'boolean' },
573
+ save_path: SAVE_PATH_WITH_PREFIX('Save audio to this path.'),
574
+ ...CACHE_PROPERTIES,
528
575
  },
529
576
  required: ['input'],
530
577
  },
@@ -544,7 +591,7 @@ export const TOOL_DEFINITIONS = [
544
591
  properties: {
545
592
  audio_path: {
546
593
  type: 'string',
547
- description: 'Audio file: local path (sandboxed), http(s) URL, or base64 data URL. Formats: mp3, wav, flac, ogg, webm, mp4.',
594
+ description: 'Audio file: local path (sandboxed), http(s) URL, or base64 data URL. Formats: mp3, wav, flac, ogg, webm, mp4, m4a.',
548
595
  },
549
596
  model: {
550
597
  type: 'string',
@@ -556,18 +603,16 @@ export const TOOL_DEFINITIONS = [
556
603
  },
557
604
  response_format: {
558
605
  type: 'string',
559
- enum: ['json', 'text', 'srt', 'verbose_json', 'vtt'],
606
+ enum: [...STT_RESPONSE_FORMATS],
560
607
  description: 'Output format for transcription. Default: json.',
561
608
  },
562
609
  temperature: {
563
610
  type: 'number',
564
611
  minimum: 0,
565
612
  maximum: 1,
566
- description: 'Sampling temperature for transcription (0-1).',
613
+ description: 'Sampling temperature for transcription (0–1).',
567
614
  },
568
- cache: { type: 'boolean' },
569
- cache_ttl: { type: 'string' },
570
- cache_clear: { type: 'boolean' },
615
+ ...CACHE_PROPERTIES,
571
616
  },
572
617
  required: ['audio_path'],
573
618
  },
@@ -585,19 +630,50 @@ export const TOOL_DEFINITIONS = [
585
630
  inputSchema: {
586
631
  type: 'object',
587
632
  properties: {
588
- prompt: { type: 'string' },
589
- model: { type: 'string' },
590
- resolution: { type: 'string' },
591
- aspect_ratio: { type: 'string' },
592
- duration: { type: 'number', minimum: 1 },
593
- seed: { type: 'number' },
594
- first_frame_image: { type: 'string' },
595
- last_frame_image: { type: 'string' },
596
- reference_images: { type: 'array', items: { type: 'string' } },
597
- provider: { type: 'object' },
633
+ prompt: { type: 'string', description: 'Text prompt describing the video to generate.' },
634
+ model: {
635
+ type: 'string',
636
+ description: 'Video model ID (default: google/veo-3.1). Passed through to provider.',
637
+ },
638
+ resolution: {
639
+ type: 'string',
640
+ description: 'Provider-specific resolution (e.g. "720p", "1080p"). No server-side enum.',
641
+ },
642
+ aspect_ratio: {
643
+ type: 'string',
644
+ description: 'Provider-specific aspect ratio (e.g. "16:9", "9:16"). No server-side enum.',
645
+ },
646
+ duration: {
647
+ type: 'number',
648
+ minimum: 1,
649
+ description: 'Clip duration in seconds (provider-dependent).',
650
+ },
651
+ seed: { type: 'number', description: 'Optional reproducibility seed.' },
652
+ first_frame_image: {
653
+ type: 'string',
654
+ description: 'Optional first-frame image (path, URL, or data URL).',
655
+ },
656
+ last_frame_image: {
657
+ type: 'string',
658
+ description: 'Optional last-frame image (path, URL, or data URL).',
659
+ },
660
+ reference_images: {
661
+ type: 'array',
662
+ items: { type: 'string' },
663
+ description: 'Optional reference images for style/subject guidance.',
664
+ },
665
+ provider: { type: 'object', description: 'Provider routing overrides.' },
598
666
  save_path: SAVE_PATH_PROPERTY,
599
- max_wait_ms: { type: 'number', minimum: 10000 },
600
- poll_interval_ms: { type: 'number', minimum: 2000 },
667
+ max_wait_ms: {
668
+ type: 'number',
669
+ minimum: 100,
670
+ description: 'Max time to poll before returning JOB_STILL_RUNNING (ms). Default: 600000 (10 min) via OPENROUTER_VIDEO_MAX_WAIT_MS.',
671
+ },
672
+ poll_interval_ms: {
673
+ type: 'number',
674
+ minimum: 50,
675
+ description: 'Poll interval while waiting (ms). Default: 15000 via OPENROUTER_VIDEO_POLL_INTERVAL_MS.',
676
+ },
601
677
  },
602
678
  required: ['prompt'],
603
679
  },
@@ -619,15 +695,23 @@ export const TOOL_DEFINITIONS = [
619
695
  type: 'string',
620
696
  description: 'First-frame image (path, URL, or data URL). Required.',
621
697
  },
622
- prompt: { type: 'string' },
623
- model: { type: 'string' },
624
- resolution: { type: 'string' },
625
- aspect_ratio: { type: 'string' },
626
- duration: { type: 'number', minimum: 1 },
627
- seed: { type: 'number' },
698
+ prompt: { type: 'string', description: 'Motion/scene prompt describing the video.' },
699
+ model: { type: 'string', description: 'Video model ID (default: google/veo-3.1).' },
700
+ resolution: { type: 'string', description: 'Provider-specific resolution.' },
701
+ aspect_ratio: { type: 'string', description: 'Provider-specific aspect ratio.' },
702
+ duration: { type: 'number', minimum: 1, description: 'Clip duration in seconds.' },
703
+ seed: { type: 'number', description: 'Optional reproducibility seed.' },
628
704
  save_path: SAVE_PATH_PROPERTY,
629
- max_wait_ms: { type: 'number', minimum: 10000 },
630
- poll_interval_ms: { type: 'number', minimum: 2000 },
705
+ max_wait_ms: {
706
+ type: 'number',
707
+ minimum: 100,
708
+ description: 'Max poll wait (ms) before JOB_STILL_RUNNING. Default: 600000.',
709
+ },
710
+ poll_interval_ms: {
711
+ type: 'number',
712
+ minimum: 50,
713
+ description: 'Poll interval (ms). Default: 15000.',
714
+ },
631
715
  },
632
716
  required: ['image', 'prompt'],
633
717
  },
@@ -645,7 +729,10 @@ export const TOOL_DEFINITIONS = [
645
729
  inputSchema: {
646
730
  type: 'object',
647
731
  properties: {
648
- video_id: { type: 'string' },
732
+ video_id: {
733
+ type: 'string',
734
+ description: 'Video job id from generate_video / generate_video_from_image.',
735
+ },
649
736
  save_path: SAVE_PATH_PROPERTY,
650
737
  },
651
738
  required: ['video_id'],
@@ -664,11 +751,17 @@ export const TOOL_DEFINITIONS = [
664
751
  inputSchema: {
665
752
  type: 'object',
666
753
  properties: {
667
- query: { type: 'string' },
754
+ query: { type: 'string', description: 'Search query to rank documents against.' },
668
755
  documents: { type: 'array', items: { type: 'string' }, minItems: 1 },
669
- model: { type: 'string' },
670
- top_n: { type: 'number', minimum: 1 },
671
- return_documents: { type: 'boolean' },
756
+ model: {
757
+ type: 'string',
758
+ description: 'Reranker model (default: cohere/rerank-v3.5).',
759
+ },
760
+ top_n: { type: 'number', minimum: 1, description: 'Return only the top N results.' },
761
+ return_documents: {
762
+ type: 'boolean',
763
+ description: 'When true, include original document text in each result.',
764
+ },
672
765
  },
673
766
  required: ['query', 'documents'],
674
767
  },
@@ -717,3 +810,13 @@ export const TOOL_DEFINITIONS = [
717
810
  },
718
811
  },
719
812
  ];
813
+ /** Tool names from definitions — must match TOOL_NAMES in tool-descriptions.ts. */
814
+ export const TOOL_DEFINITION_NAMES = TOOL_DEFINITIONS.map((t) => t.name);
815
+ if (TOOL_DEFINITION_NAMES.length !== TOOL_NAMES.length) {
816
+ throw new Error(`Tool count mismatch: definitions=${TOOL_DEFINITION_NAMES.length} descriptions=${TOOL_NAMES.length}`);
817
+ }
818
+ for (const name of TOOL_NAMES) {
819
+ if (!TOOL_DEFINITION_NAMES.includes(name)) {
820
+ throw new Error(`Missing tool definition for ${name}`);
821
+ }
822
+ }