@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.
- package/README.md +78 -53
- package/dist/errors.d.ts +3 -12
- package/dist/errors.js +2 -6
- package/dist/index.js +2 -10
- package/dist/logger.d.ts +1 -18
- package/dist/logger.js +0 -7
- package/dist/model-cache.d.ts +1 -20
- package/dist/model-cache.js +1 -20
- package/dist/openrouter-api.d.ts +4 -18
- package/dist/openrouter-api.js +4 -20
- package/dist/openrouter-openai-client.d.ts +9 -0
- package/dist/openrouter-openai-client.js +15 -0
- package/dist/tool-definitions.d.ts +1 -0
- package/dist/tool-definitions.js +719 -0
- package/dist/tool-handlers/analyze-audio.d.ts +0 -5
- package/dist/tool-handlers/analyze-image.d.ts +0 -6
- package/dist/tool-handlers/analyze-image.js +1 -8
- package/dist/tool-handlers/analyze-video.d.ts +0 -5
- package/dist/tool-handlers/analyze-video.js +0 -7
- package/dist/tool-handlers/async-chat.d.ts +17 -13
- package/dist/tool-handlers/async-chat.js +49 -65
- package/dist/tool-handlers/audio-utils.d.ts +1 -4
- package/dist/tool-handlers/audio-utils.js +4 -14
- package/dist/tool-handlers/cache.d.ts +2 -18
- package/dist/tool-handlers/cache.js +1 -19
- package/dist/tool-handlers/chat-completion.d.ts +2 -30
- package/dist/tool-handlers/chat-completion.js +13 -33
- package/dist/tool-handlers/chat-request.d.ts +25 -0
- package/dist/tool-handlers/chat-request.js +38 -0
- package/dist/tool-handlers/completion-utils.d.ts +1 -18
- package/dist/tool-handlers/completion-utils.js +0 -2
- package/dist/tool-handlers/fetch-utils.d.ts +2 -18
- package/dist/tool-handlers/fetch-utils.js +3 -51
- package/dist/tool-handlers/generate-audio.d.ts +3 -37
- package/dist/tool-handlers/generate-audio.js +23 -47
- package/dist/tool-handlers/generate-image-dedicated.d.ts +1 -11
- package/dist/tool-handlers/generate-image-dedicated.js +65 -87
- package/dist/tool-handlers/generate-image-input.d.ts +0 -1
- package/dist/tool-handlers/generate-image-input.js +2 -18
- package/dist/tool-handlers/generate-image.d.ts +2 -45
- package/dist/tool-handlers/generate-image.js +16 -37
- package/dist/tool-handlers/generate-video.d.ts +1 -7
- package/dist/tool-handlers/generate-video.js +34 -142
- package/dist/tool-handlers/health-check.d.ts +1 -9
- package/dist/tool-handlers/health-check.js +1 -12
- package/dist/tool-handlers/image-source.d.ts +14 -0
- package/dist/tool-handlers/image-source.js +23 -0
- package/dist/tool-handlers/image-utils.d.ts +5 -0
- package/dist/tool-handlers/image-utils.js +23 -0
- package/dist/tool-handlers/openai-withresponse.d.ts +1 -10
- package/dist/tool-handlers/openai-withresponse.js +0 -7
- package/dist/tool-handlers/openrouter-errors.d.ts +2 -18
- package/dist/tool-handlers/openrouter-errors.js +2 -34
- package/dist/tool-handlers/path-safety.d.ts +15 -14
- package/dist/tool-handlers/path-safety.js +61 -55
- package/dist/tool-handlers/path-utils.d.ts +2 -0
- package/dist/tool-handlers/path-utils.js +7 -0
- package/dist/tool-handlers/provider-routing.d.ts +0 -9
- package/dist/tool-handlers/provider-routing.js +1 -14
- package/dist/tool-handlers/rerank.js +0 -2
- package/dist/tool-handlers/search-models.d.ts +0 -6
- package/dist/tool-handlers/speech-to-text.js +3 -21
- package/dist/tool-handlers/structured-output.d.ts +1 -4
- package/dist/tool-handlers/structured-output.js +2 -16
- package/dist/tool-handlers/text-to-speech.d.ts +1 -11
- package/dist/tool-handlers/text-to-speech.js +20 -41
- package/dist/tool-handlers/tool-result-payload.d.ts +47 -0
- package/dist/tool-handlers/tool-result-payload.js +96 -0
- package/dist/tool-handlers/video-utils.d.ts +1 -6
- package/dist/tool-handlers/video-utils.js +2 -17
- package/dist/tool-handlers.js +6 -739
- package/dist/tool-icons.d.ts +0 -6
- package/dist/tool-icons.js +1 -8
- package/dist/version.d.ts +1 -15
- package/dist/version.js +1 -15
- 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);
|