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