@stabgan/openrouter-mcp-multimodal 4.6.1 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +78 -53
  2. package/dist/errors.d.ts +3 -12
  3. package/dist/errors.js +2 -6
  4. package/dist/index.js +2 -10
  5. package/dist/logger.d.ts +1 -18
  6. package/dist/logger.js +0 -7
  7. package/dist/model-cache.d.ts +1 -20
  8. package/dist/model-cache.js +1 -20
  9. package/dist/openrouter-api.d.ts +4 -18
  10. package/dist/openrouter-api.js +4 -20
  11. package/dist/openrouter-openai-client.d.ts +9 -0
  12. package/dist/openrouter-openai-client.js +15 -0
  13. package/dist/tool-definitions.d.ts +1 -0
  14. package/dist/tool-definitions.js +719 -0
  15. package/dist/tool-handlers/analyze-audio.d.ts +0 -5
  16. package/dist/tool-handlers/analyze-image.d.ts +0 -6
  17. package/dist/tool-handlers/analyze-image.js +1 -8
  18. package/dist/tool-handlers/analyze-video.d.ts +0 -5
  19. package/dist/tool-handlers/analyze-video.js +0 -7
  20. package/dist/tool-handlers/async-chat.d.ts +17 -13
  21. package/dist/tool-handlers/async-chat.js +49 -65
  22. package/dist/tool-handlers/audio-utils.d.ts +1 -4
  23. package/dist/tool-handlers/audio-utils.js +4 -14
  24. package/dist/tool-handlers/cache.d.ts +2 -18
  25. package/dist/tool-handlers/cache.js +1 -19
  26. package/dist/tool-handlers/chat-completion.d.ts +2 -30
  27. package/dist/tool-handlers/chat-completion.js +13 -33
  28. package/dist/tool-handlers/chat-request.d.ts +25 -0
  29. package/dist/tool-handlers/chat-request.js +38 -0
  30. package/dist/tool-handlers/completion-utils.d.ts +1 -18
  31. package/dist/tool-handlers/completion-utils.js +0 -2
  32. package/dist/tool-handlers/fetch-utils.d.ts +2 -18
  33. package/dist/tool-handlers/fetch-utils.js +3 -51
  34. package/dist/tool-handlers/generate-audio.d.ts +3 -37
  35. package/dist/tool-handlers/generate-audio.js +23 -47
  36. package/dist/tool-handlers/generate-image-dedicated.d.ts +1 -11
  37. package/dist/tool-handlers/generate-image-dedicated.js +65 -87
  38. package/dist/tool-handlers/generate-image-input.d.ts +0 -1
  39. package/dist/tool-handlers/generate-image-input.js +2 -18
  40. package/dist/tool-handlers/generate-image.d.ts +2 -45
  41. package/dist/tool-handlers/generate-image.js +16 -37
  42. package/dist/tool-handlers/generate-video.d.ts +1 -7
  43. package/dist/tool-handlers/generate-video.js +34 -142
  44. package/dist/tool-handlers/health-check.d.ts +1 -9
  45. package/dist/tool-handlers/health-check.js +1 -12
  46. package/dist/tool-handlers/image-source.d.ts +14 -0
  47. package/dist/tool-handlers/image-source.js +23 -0
  48. package/dist/tool-handlers/image-utils.d.ts +5 -0
  49. package/dist/tool-handlers/image-utils.js +23 -0
  50. package/dist/tool-handlers/openai-withresponse.d.ts +1 -10
  51. package/dist/tool-handlers/openai-withresponse.js +0 -7
  52. package/dist/tool-handlers/openrouter-errors.d.ts +2 -18
  53. package/dist/tool-handlers/openrouter-errors.js +2 -34
  54. package/dist/tool-handlers/path-safety.d.ts +15 -14
  55. package/dist/tool-handlers/path-safety.js +61 -55
  56. package/dist/tool-handlers/path-utils.d.ts +2 -0
  57. package/dist/tool-handlers/path-utils.js +7 -0
  58. package/dist/tool-handlers/provider-routing.d.ts +0 -9
  59. package/dist/tool-handlers/provider-routing.js +1 -14
  60. package/dist/tool-handlers/rerank.js +0 -2
  61. package/dist/tool-handlers/search-models.d.ts +0 -6
  62. package/dist/tool-handlers/speech-to-text.js +3 -21
  63. package/dist/tool-handlers/structured-output.d.ts +1 -4
  64. package/dist/tool-handlers/structured-output.js +2 -16
  65. package/dist/tool-handlers/text-to-speech.d.ts +1 -11
  66. package/dist/tool-handlers/text-to-speech.js +20 -41
  67. package/dist/tool-handlers/tool-result-payload.d.ts +47 -0
  68. package/dist/tool-handlers/tool-result-payload.js +96 -0
  69. package/dist/tool-handlers/video-utils.d.ts +1 -6
  70. package/dist/tool-handlers/video-utils.js +2 -17
  71. package/dist/tool-handlers.js +6 -739
  72. package/dist/tool-icons.d.ts +0 -6
  73. package/dist/tool-icons.js +1 -8
  74. package/dist/version.d.ts +1 -15
  75. package/dist/version.js +1 -15
  76. package/package.json +3 -2
@@ -0,0 +1,47 @@
1
+ export type InlineMediaKind = 'image' | 'audio' | 'video';
2
+ type TextContent = {
3
+ type: 'text';
4
+ text: string;
5
+ };
6
+ type ImageContent = {
7
+ type: 'image';
8
+ mimeType: string;
9
+ data: string;
10
+ };
11
+ type AudioContent = {
12
+ type: 'audio';
13
+ mimeType: string;
14
+ data: string;
15
+ };
16
+ type ResourceContent = {
17
+ type: 'resource';
18
+ resource: {
19
+ uri: string;
20
+ mimeType?: string;
21
+ blob: string;
22
+ };
23
+ };
24
+ export type BinaryToolContent = TextContent | ImageContent | AudioContent | ResourceContent;
25
+ export interface BinaryArtifact {
26
+ kind: InlineMediaKind;
27
+ buffer: Buffer;
28
+ mimeType: string;
29
+ }
30
+ export interface BuildBinaryToolResultOptions {
31
+ savedPath?: string | null;
32
+ /** Overrides default saved/too-large message */
33
+ summaryText?: string;
34
+ /** Shown alongside inline media when not using inlineOnly */
35
+ prefixText?: string;
36
+ /** When inline fits and no save_path: return media block only (image UX) */
37
+ inlineOnly?: boolean;
38
+ remoteUrl?: string;
39
+ meta?: Record<string, unknown>;
40
+ maxInlineBytes?: number;
41
+ }
42
+ export declare function getMaxInlineBytes(kind: InlineMediaKind): number;
43
+ export declare function buildBinaryToolResult(artifact: BinaryArtifact, opts?: BuildBinaryToolResultOptions): {
44
+ content: BinaryToolContent[];
45
+ _meta: Record<string, unknown>;
46
+ };
47
+ export {};
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Canonical MCP tool-result policy for binary artifacts:
3
+ * - saved to disk -> text pointer only (never duplicate inline media)
4
+ * - not saved -> inline only when under byte ceiling, else text + save_path hint
5
+ * - video uses MCP `resource` blocks (spec has no `video` content type)
6
+ */
7
+ import { readEnvInt } from './fetch-utils.js';
8
+ const DEFAULT_INLINE_MAX_BYTES = 1024 * 1024;
9
+ const DEFAULT_VIDEO_INLINE_MAX_BYTES = 10 * 1024 * 1024;
10
+ const INLINE_VIDEO_URI = 'inline://openrouter-mcp-multimodal/video';
11
+ const KIND_ENV_KEYS = {
12
+ image: 'OPENROUTER_IMAGE_INLINE_MAX_BYTES',
13
+ audio: 'OPENROUTER_AUDIO_INLINE_MAX_BYTES',
14
+ video: 'OPENROUTER_VIDEO_INLINE_MAX_BYTES',
15
+ };
16
+ const KIND_DEFAULT_BYTES = {
17
+ image: DEFAULT_INLINE_MAX_BYTES,
18
+ audio: DEFAULT_INLINE_MAX_BYTES,
19
+ video: DEFAULT_VIDEO_INLINE_MAX_BYTES,
20
+ };
21
+ export function getMaxInlineBytes(kind) {
22
+ const globalFallback = readEnvInt('OPENROUTER_INLINE_MAX_BYTES', KIND_DEFAULT_BYTES[kind], 4096);
23
+ return readEnvInt(KIND_ENV_KEYS[kind], globalFallback, 4096);
24
+ }
25
+ function kindLabel(kind) {
26
+ switch (kind) {
27
+ case 'image':
28
+ return 'Image';
29
+ case 'audio':
30
+ return 'Audio';
31
+ case 'video':
32
+ return 'Video';
33
+ default: {
34
+ const _exhaustive = kind;
35
+ return _exhaustive;
36
+ }
37
+ }
38
+ }
39
+ function buildInlineBlock(kind, mimeType, data, remoteUrl) {
40
+ if (kind === 'video') {
41
+ return {
42
+ type: 'resource',
43
+ resource: {
44
+ uri: remoteUrl ?? INLINE_VIDEO_URI,
45
+ mimeType,
46
+ blob: data,
47
+ },
48
+ };
49
+ }
50
+ return { type: kind, mimeType, data };
51
+ }
52
+ export function buildBinaryToolResult(artifact, opts = {}) {
53
+ const { kind, buffer, mimeType } = artifact;
54
+ const maxInline = opts.maxInlineBytes ?? getMaxInlineBytes(kind);
55
+ const meta = {
56
+ ...opts.meta,
57
+ mime: mimeType,
58
+ size_bytes: buffer.length,
59
+ };
60
+ if (opts.savedPath) {
61
+ const text = opts.summaryText ??
62
+ `${kindLabel(kind)} saved to: ${opts.savedPath} (${buffer.length} bytes, ${mimeType})`;
63
+ return {
64
+ content: [{ type: 'text', text }],
65
+ _meta: { ...meta, save_path: opts.savedPath },
66
+ };
67
+ }
68
+ if (buffer.length <= maxInline) {
69
+ const data = buffer.toString('base64');
70
+ if (opts.inlineOnly) {
71
+ return {
72
+ content: [buildInlineBlock(kind, mimeType, data, opts.remoteUrl)],
73
+ _meta: meta,
74
+ };
75
+ }
76
+ const text = opts.prefixText ?? `${kindLabel(kind)} generated (${buffer.length} bytes, ${mimeType}).`;
77
+ return {
78
+ content: [textBlock(text), buildInlineBlock(kind, mimeType, data, opts.remoteUrl)],
79
+ _meta: meta,
80
+ };
81
+ }
82
+ const urlHint = opts.remoteUrl ? ` URL: ${opts.remoteUrl}` : '';
83
+ return {
84
+ content: [
85
+ {
86
+ type: 'text',
87
+ text: opts.summaryText ??
88
+ `${kindLabel(kind)} generated (${buffer.length} bytes, ${mimeType}). Too large to inline; pass save_path to persist.${urlHint}`,
89
+ },
90
+ ],
91
+ _meta: meta,
92
+ };
93
+ }
94
+ function textBlock(text) {
95
+ return { type: 'text', text };
96
+ }
@@ -20,10 +20,5 @@ export interface VideoData {
20
20
  mediaType: string;
21
21
  sizeBytes: number;
22
22
  }
23
- /**
24
- * Prepare a video from any source (data URL / HTTP URL / local file) as
25
- * base64 + MIME. OpenRouter requires the client to send video as either a
26
- * URL or a data URL; we always base64 it so the tool works regardless of
27
- * provider quirks.
28
- */
23
+ /** Prepare video from data URL, HTTP URL, or sandboxed local file. */
29
24
  export declare function prepareVideoData(source: string): Promise<VideoData>;
@@ -1,11 +1,4 @@
1
- /**
2
- * Video format detection and fetch utilities. Mirrors the structure of
3
- * `audio-utils.ts`: all network/security logic comes from `fetch-utils.ts`,
4
- * this module owns format detection, base64 encoding, and MIME mapping.
5
- *
6
- * OpenRouter's video-understanding docs (accessed 2026-04-20) list four
7
- * supported container formats: mp4, mpeg, mov, webm.
8
- */
1
+ /** Video format detection and fetch utilities. */
9
2
  import path from 'node:path';
10
3
  import { promises as fs } from 'node:fs';
11
4
  import { readEnvInt, fetchHttpResource, parseBase64DataUrl } from './fetch-utils.js';
@@ -107,14 +100,8 @@ export function detectVideoFormat(buffer) {
107
100
  }
108
101
  return undefined;
109
102
  }
110
- /**
111
- * Prepare a video from any source (data URL / HTTP URL / local file) as
112
- * base64 + MIME. OpenRouter requires the client to send video as either a
113
- * URL or a data URL; we always base64 it so the tool works regardless of
114
- * provider quirks.
115
- */
103
+ /** Prepare video from data URL, HTTP URL, or sandboxed local file. */
116
104
  export async function prepareVideoData(source) {
117
- // --- data URL ---
118
105
  if (source.startsWith('data:')) {
119
106
  const parsed = parseBase64DataUrl(source);
120
107
  if (!parsed)
@@ -136,7 +123,6 @@ export async function prepareVideoData(source) {
136
123
  sizeBytes: approxBytes,
137
124
  };
138
125
  }
139
- // --- HTTP(S) URL ---
140
126
  if (source.startsWith('http://') || source.startsWith('https://')) {
141
127
  const { buffer, contentType } = await fetchHttpResource(source, {
142
128
  timeoutMs: getFetchTimeoutMs(),
@@ -155,7 +141,6 @@ export async function prepareVideoData(source) {
155
141
  sizeBytes: buffer.length,
156
142
  };
157
143
  }
158
- // --- local file ---
159
144
  const safe = await resolveSafeInputPath(source);
160
145
  const buffer = await fs.readFile(safe);
161
146
  const format = detectVideoFormat(buffer) ?? getVideoFormat(safe);