@0xmaxma/claude-gateway 1.5.8 → 1.6.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 +22 -0
- package/dist/agent/runner.d.ts +30 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +223 -16
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/apps-router.d.ts.map +1 -1
- package/dist/api/apps-router.js +7 -2
- package/dist/api/apps-router.js.map +1 -1
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +27 -0
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/line-webhook-router.d.ts.map +1 -1
- package/dist/api/line-webhook-router.js +68 -0
- package/dist/api/line-webhook-router.js.map +1 -1
- package/dist/api/router.d.ts +2 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +59 -3
- package/dist/api/router.js.map +1 -1
- package/dist/api/share-router.d.ts +11 -0
- package/dist/api/share-router.d.ts.map +1 -0
- package/dist/api/share-router.js +402 -0
- package/dist/api/share-router.js.map +1 -0
- package/dist/apps/installer.d.ts +55 -0
- package/dist/apps/installer.d.ts.map +1 -1
- package/dist/apps/installer.js +130 -0
- package/dist/apps/installer.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +9 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/public-url.d.ts +11 -0
- package/dist/config/public-url.d.ts.map +1 -0
- package/dist/config/public-url.js +41 -0
- package/dist/config/public-url.js.map +1 -0
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +1 -0
- package/dist/config/watcher.js.map +1 -1
- package/dist/history/db.d.ts +14 -0
- package/dist/history/db.d.ts.map +1 -1
- package/dist/history/db.js +48 -4
- package/dist/history/db.js.map +1 -1
- package/dist/history/types.d.ts +5 -0
- package/dist/history/types.d.ts.map +1 -1
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +17 -1
- package/dist/session/process.js.map +1 -1
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +9 -1
- package/dist/session/store.js.map +1 -1
- package/dist/share/session-image-catalog.d.ts +22 -0
- package/dist/share/session-image-catalog.d.ts.map +1 -0
- package/dist/share/session-image-catalog.js +151 -0
- package/dist/share/session-image-catalog.js.map +1 -0
- package/dist/share/share-store.d.ts +137 -0
- package/dist/share/share-store.d.ts.map +1 -0
- package/dist/share/share-store.js +340 -0
- package/dist/share/share-store.js.map +1 -0
- package/dist/types.d.ts +17 -7
- package/dist/types.d.ts.map +1 -1
- package/mcp/instructions.ts +1 -0
- package/mcp/server.ts +2 -0
- package/mcp/tools/image/module.ts +240 -26
- package/mcp/tools/line/module.ts +186 -0
- package/mcp/tools/share-image/module.ts +120 -0
- package/mcp/tools/shared/share-client.ts +197 -0
- package/package.json +3 -1
package/dist/types.d.ts
CHANGED
|
@@ -146,13 +146,15 @@ export interface GatewayConfig {
|
|
|
146
146
|
*/
|
|
147
147
|
bind?: string;
|
|
148
148
|
/**
|
|
149
|
-
* Absolute, externally-reachable
|
|
150
|
-
* "https://gateway.example.com"
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
149
|
+
* Absolute, externally-reachable base URL of this gateway, including any
|
|
150
|
+
* reverse-proxy path prefix (e.g. "https://gateway.example.com" or
|
|
151
|
+
* "https://vm.example.com/gateway"). Two consumers: phone-openable links
|
|
152
|
+
* such as the `/cli` webview terminal viewer, and the image-share bridge —
|
|
153
|
+
* its presence is the sole enable switch and source for minting public
|
|
154
|
+
* shared-image URLs. The process cannot infer its own public URL (it binds
|
|
155
|
+
* localhost by default and sits behind a reverse proxy), so the operator
|
|
156
|
+
* sets it explicitly. Unset = features that need a public link are
|
|
157
|
+
* disabled. A trailing slash is trimmed when building links.
|
|
156
158
|
*/
|
|
157
159
|
publicUrl?: string;
|
|
158
160
|
models?: ModelConfig[];
|
|
@@ -256,6 +258,14 @@ export type ImageParams = {
|
|
|
256
258
|
size?: string;
|
|
257
259
|
aspect_ratio?: string;
|
|
258
260
|
image_ref?: string;
|
|
261
|
+
/**
|
|
262
|
+
* Reference images explicitly selected in the composer, in the order the user
|
|
263
|
+
* picked them. Each entry is a `ref` value from GET /api/v1/image-catalog —
|
|
264
|
+
* either an `artifact:<id>` ref or a media-relative path (the same forms the
|
|
265
|
+
* generate_image `image`/`images` arguments accept). Per-turn only: unlike the
|
|
266
|
+
* rest of ImageParams these are NOT persisted as durable session image config.
|
|
267
|
+
*/
|
|
268
|
+
image_refs?: string[];
|
|
259
269
|
n?: number;
|
|
260
270
|
};
|
|
261
271
|
export type StreamEvent = {
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/mcp/instructions.ts
CHANGED
|
@@ -15,6 +15,7 @@ const IMAGE_INSTRUCTION = [
|
|
|
15
15
|
'• RETRY ON FAILURE ONLY: a generate can fail with no_supply / no_credential (pool_eligible does not guarantee a working key). If — AND ONLY IF — a generate returns no_supply, no_credential, or an error, try the NEXT usable model (byok_available ones first), up to every usable model. This retry rule NEVER applies to a successful generate or to sending.',
|
|
16
16
|
'• ONE image, sent ONCE, then STOP: as soon as a generate SUCCEEDS, deliver that single file with your reply tool exactly ONE time (files: ["/abs/path.png"]) and briefly mention the model. Then you are DONE — do NOT generate again, do NOT resend the same image, do NOT call the reply tool again, and do NOT send extra follow-up messages. Sending the image (or any message) repeatedly is a bug.',
|
|
17
17
|
'• Only if there is NO usable model at all (none byok_available AND none pool_eligible), OR every usable model failed to generate, tell the user PLAINLY that image generation isn\'t set up with a working model yet and they need to connect an image-provider key (BYOK). Do NOT invent app-install / MCP-setup steps, and do NOT pretend an image was created.',
|
|
18
|
+
'• REFERRING TO AN EARLIER IMAGE: if the user points at an image from earlier in the chat ("image number 2", "the first image", "the one you just made"), call generate_image with action="list_refs" FIRST and use the "ref" of the item they mean — never count images from your own memory of the conversation. Not remembering any images is NOT evidence there are none: your context may have been reset or compacted while the chat history still has them, so NEVER reply "no images attached / please attach" to such a request before list_refs has returned an empty catalog. If it is ambiguous, or the index they gave does not exist in the catalog, ask them which image instead of guessing.',
|
|
18
19
|
'• NEVER tell the user to install an app or set up an MCP server for images.',
|
|
19
20
|
].join('\n');
|
|
20
21
|
|
package/mcp/server.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { SkillsModule } from './tools/skills/module';
|
|
|
20
20
|
import { AgentModule } from './tools/agent/module';
|
|
21
21
|
import { BrowserModule } from './tools/browser/module';
|
|
22
22
|
import { ImageModule } from './tools/image/module';
|
|
23
|
+
import { ShareImageModule } from './tools/share-image/module';
|
|
23
24
|
import { AppsModule } from './tools/apps/module';
|
|
24
25
|
import { ApiModule } from './tools/api/module';
|
|
25
26
|
import { buildChannelInstructions } from './instructions';
|
|
@@ -42,6 +43,7 @@ const modules: AnyModule[] = [
|
|
|
42
43
|
new AgentModule(),
|
|
43
44
|
new BrowserModule(),
|
|
44
45
|
new ImageModule(),
|
|
46
|
+
new ShareImageModule(),
|
|
45
47
|
new AppsModule(),
|
|
46
48
|
new ApiModule(),
|
|
47
49
|
];
|
|
@@ -4,6 +4,16 @@ import * as path from 'node:path';
|
|
|
4
4
|
import * as dns from 'node:dns';
|
|
5
5
|
import * as net from 'node:net';
|
|
6
6
|
import type { ToolModule, McpToolDefinition, McpToolResult, ToolVisibility } from '../../types';
|
|
7
|
+
import {
|
|
8
|
+
ShareClientError,
|
|
9
|
+
createShares,
|
|
10
|
+
listSessionImages,
|
|
11
|
+
registerArtifacts,
|
|
12
|
+
revokeSharesBestEffort,
|
|
13
|
+
shareBridgeEnabled,
|
|
14
|
+
type ShareRef,
|
|
15
|
+
type ShareItem,
|
|
16
|
+
} from '../shared/share-client';
|
|
7
17
|
|
|
8
18
|
/**
|
|
9
19
|
* Image-generation tool module (#184, Track B).
|
|
@@ -61,12 +71,9 @@ export class ImageModule implements ToolModule {
|
|
|
61
71
|
toolVisibility: ToolVisibility = 'all-configured';
|
|
62
72
|
|
|
63
73
|
isEnabled(): boolean {
|
|
64
|
-
// Enabled when
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
// disables the tool cleanly, instead of advertising it and then failing every call with a
|
|
68
|
-
// 401 on an empty Bearer.
|
|
69
|
-
if (!this.baseUrl() || !this.authToken() || process.env.IMAGE_DISABLED === 'true') return false;
|
|
74
|
+
// Enabled when the image endpoint is configured in ~/.claude/settings.json (see
|
|
75
|
+
// baseUrl()) and not explicitly turned off.
|
|
76
|
+
if (!this.baseUrl() || process.env.IMAGE_DISABLED === 'true') return false;
|
|
70
77
|
// The Bearer proxy_secret rides every call — refuse a cleartext http URL to a
|
|
71
78
|
// PUBLIC host (that would leak the secret). http to a local/internal host is a
|
|
72
79
|
// trusted hop (e.g. host.docker.internal in dev) and stays allowed.
|
|
@@ -100,9 +107,11 @@ export class ImageModule implements ToolModule {
|
|
|
100
107
|
return this.handleStatus(args);
|
|
101
108
|
case 'list':
|
|
102
109
|
return this.handleList();
|
|
110
|
+
case 'list_refs':
|
|
111
|
+
return this.handleListRefs();
|
|
103
112
|
default:
|
|
104
113
|
return {
|
|
105
|
-
content: [{ type: 'text', text: `generate_image: unknown action "${action}" (expected generate | status | list)` }],
|
|
114
|
+
content: [{ type: 'text', text: `generate_image: unknown action "${action}" (expected generate | status | list | list_refs)` }],
|
|
106
115
|
isError: true,
|
|
107
116
|
};
|
|
108
117
|
}
|
|
@@ -187,6 +196,53 @@ export class ImageModule implements ToolModule {
|
|
|
187
196
|
return { content: [{ type: 'text', text: body || '[]' }] };
|
|
188
197
|
}
|
|
189
198
|
|
|
199
|
+
/**
|
|
200
|
+
* action="list_refs" (#72) — return the deterministic, gateway-computed catalog
|
|
201
|
+
* of every image in this session so the agent never has to count images from its
|
|
202
|
+
* own transcript (which breaks after compaction/resume). Read-only.
|
|
203
|
+
*
|
|
204
|
+
* Gated on the share bridge exactly like reference minting: with the bridge off
|
|
205
|
+
* there is no catalog endpoint to read, so the action stays inert (legacy mode
|
|
206
|
+
* gains no new behavior).
|
|
207
|
+
*/
|
|
208
|
+
private async handleListRefs(): Promise<McpToolResult> {
|
|
209
|
+
if (!shareBridgeEnabled()) {
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: 'text', text: 'generate_image: list_refs is unavailable (image share bridge is not configured).' }],
|
|
212
|
+
isError: true,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
let items;
|
|
216
|
+
try {
|
|
217
|
+
items = await listSessionImages();
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (err instanceof ShareClientError) {
|
|
220
|
+
return { content: [{ type: 'text', text: `generate_image: ${err.code}: ${err.message}` }], isError: true };
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: 'text', text: `generate_image: image share service unavailable: ${(err as Error).message}` }],
|
|
224
|
+
isError: true,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
content: [{
|
|
229
|
+
type: 'text',
|
|
230
|
+
text: JSON.stringify({
|
|
231
|
+
images: items,
|
|
232
|
+
note: 'Ground-truth catalog of every image in this session, numbered in order of first appearance ("image 1" = index 1). '
|
|
233
|
+
+ 'To use one as a reference, pass its "ref" value in the "image"/"images" argument of action="generate". '
|
|
234
|
+
+ 'Do NOT count images from conversation memory. '
|
|
235
|
+
+ 'Resolution precedence: (1) when the user names an index ("Image 3", "the third image") or attached an image this turn, '
|
|
236
|
+
+ 'trust that EXACTLY — never let "desc" override an explicit reference; '
|
|
237
|
+
+ '(2) when the user refers by content ("the dog picture"), match against each item\'s "desc" '
|
|
238
|
+
+ '(the generation prompt, or the user text that came with an upload); '
|
|
239
|
+
+ '(3) if the reference is ambiguous or the index does not exist, ask the user instead of guessing. '
|
|
240
|
+
+ 'Items with available:false can no longer be used.',
|
|
241
|
+
}),
|
|
242
|
+
}],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
190
246
|
private async handleGenerate(args: Record<string, unknown>): Promise<McpToolResult> {
|
|
191
247
|
const prompt = typeof args.prompt === 'string' ? args.prompt.trim() : '';
|
|
192
248
|
const model = typeof args.model === 'string' ? args.model.trim() : '';
|
|
@@ -202,13 +258,39 @@ export class ImageModule implements ToolModule {
|
|
|
202
258
|
for (const k of ['quality', 'size', 'aspect_ratio', 'style'] as const) {
|
|
203
259
|
if (typeof args[k] === 'string' && (args[k] as string).length) reqBody[k] = args[k];
|
|
204
260
|
}
|
|
205
|
-
if (typeof args.n === 'number' && args.n > 0)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
261
|
+
if (typeof args.n === 'number' && args.n > 0) {
|
|
262
|
+
// Clamp the requested image count — an unbounded n is forwarded straight to
|
|
263
|
+
// the provider (cost / abuse). MAX_DELIVER_IMAGES is the most we can deliver
|
|
264
|
+
// back anyway, so generating more is wasted spend.
|
|
265
|
+
reqBody.n = Math.min(Math.floor(args.n), MAX_DELIVER_IMAGES);
|
|
209
266
|
}
|
|
210
267
|
|
|
211
|
-
//
|
|
268
|
+
// Reference images (#70): when the share bridge is enabled, local paths and
|
|
269
|
+
// artifact:<id> refs are auto-converted to short-lived public share URLs via
|
|
270
|
+
// the authenticated gateway API (plan §7). With the bridge off, behavior is
|
|
271
|
+
// EXACTLY the legacy pass-through (regression guarantee).
|
|
272
|
+
const rawImage = typeof args.image === 'string' && args.image.length ? args.image : undefined;
|
|
273
|
+
const rawImages =
|
|
274
|
+
Array.isArray(args.images) && args.images.every((x) => typeof x === 'string') && args.images.length
|
|
275
|
+
? (args.images as string[])
|
|
276
|
+
: undefined;
|
|
277
|
+
let mintedShareIds: string[] = [];
|
|
278
|
+
if (shareBridgeEnabled() && (rawImage || rawImages)) {
|
|
279
|
+
if (rawImage && rawImages) {
|
|
280
|
+
return { content: [{ type: 'text', text: 'generate_image: pass either "image" or "images", not both.' }], isError: true };
|
|
281
|
+
}
|
|
282
|
+
const normalized = await this.normalizeRefs(rawImage ? [rawImage] : rawImages!);
|
|
283
|
+
if ('error' in normalized) return normalized.error;
|
|
284
|
+
mintedShareIds = normalized.mintedShareIds;
|
|
285
|
+
if (rawImage) reqBody.image = normalized.urls[0];
|
|
286
|
+
else reqBody.images = normalized.urls;
|
|
287
|
+
} else {
|
|
288
|
+
if (rawImage) reqBody.image = rawImage;
|
|
289
|
+
if (rawImages) reqBody.images = rawImages;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Submit (E1). On an immediate submit failure, best-effort revoke any share
|
|
293
|
+
// URLs minted for this call (§18) — the TTL still bounds exposure otherwise.
|
|
212
294
|
let res: Response;
|
|
213
295
|
try {
|
|
214
296
|
res = await fetch(`${this.baseUrl()}/v1/images/generations`, {
|
|
@@ -218,19 +300,25 @@ export class ImageModule implements ToolModule {
|
|
|
218
300
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
219
301
|
});
|
|
220
302
|
} catch (err) {
|
|
303
|
+
await revokeSharesBestEffort(mintedShareIds);
|
|
221
304
|
return this.unavailable(err);
|
|
222
305
|
}
|
|
223
306
|
const submitText = await res.text().catch(() => '');
|
|
224
|
-
if (!res.ok)
|
|
307
|
+
if (!res.ok) {
|
|
308
|
+
await revokeSharesBestEffort(mintedShareIds);
|
|
309
|
+
return this.mapHttpError(res.status, submitText);
|
|
310
|
+
}
|
|
225
311
|
|
|
226
312
|
let submit: JobResponse;
|
|
227
313
|
try {
|
|
228
314
|
submit = JSON.parse(submitText) as JobResponse;
|
|
229
315
|
} catch {
|
|
316
|
+
await revokeSharesBestEffort(mintedShareIds);
|
|
230
317
|
return { content: [{ type: 'text', text: 'generate_image: invalid JSON from image service on submit' }], isError: true };
|
|
231
318
|
}
|
|
232
319
|
const taskId = submit.task_id;
|
|
233
320
|
if (!taskId) {
|
|
321
|
+
await revokeSharesBestEffort(mintedShareIds);
|
|
234
322
|
return { content: [{ type: 'text', text: 'generate_image: image service did not return a task_id' }], isError: true };
|
|
235
323
|
}
|
|
236
324
|
|
|
@@ -246,7 +334,7 @@ export class ImageModule implements ToolModule {
|
|
|
246
334
|
}
|
|
247
335
|
if (polled.httpError) return this.mapHttpError(polled.httpError.status, polled.httpError.body);
|
|
248
336
|
last = polled.job!;
|
|
249
|
-
if (last.status === 'done') return await this.deliver(last, taskId);
|
|
337
|
+
if (last.status === 'done') return await this.deliver(last, taskId, model, prompt);
|
|
250
338
|
if (last.status === 'failed') return this.mapJobError(last);
|
|
251
339
|
}
|
|
252
340
|
|
|
@@ -287,6 +375,82 @@ export class ImageModule implements ToolModule {
|
|
|
287
375
|
|
|
288
376
|
// ── helpers ─────────────────────────────────────────────────────────────
|
|
289
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Normalize reference inputs (#70, plan §7), preserving order:
|
|
380
|
+
* https URL → validate syntax, pass through
|
|
381
|
+
* http URL → reject (phase 1 requires HTTPS)
|
|
382
|
+
* artifact:<id> → resolve+mint via gateway share API
|
|
383
|
+
* local media path → validate+mint via gateway share API
|
|
384
|
+
* Rejects duplicates and over-count BEFORE any minting/billing.
|
|
385
|
+
*/
|
|
386
|
+
private async normalizeRefs(
|
|
387
|
+
refs: string[],
|
|
388
|
+
): Promise<{ urls: string[]; mintedShareIds: string[] } | { error: McpToolResult }> {
|
|
389
|
+
const fail = (text: string): { error: McpToolResult } => ({
|
|
390
|
+
error: { content: [{ type: 'text', text }], isError: true },
|
|
391
|
+
});
|
|
392
|
+
const maxRefs = (() => {
|
|
393
|
+
const n = Number(process.env.IMAGE_SHARE_MAX_REFS);
|
|
394
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 5;
|
|
395
|
+
})();
|
|
396
|
+
if (refs.length > maxRefs) {
|
|
397
|
+
return fail(`generate_image: too many reference images (max ${maxRefs}).`);
|
|
398
|
+
}
|
|
399
|
+
if (new Set(refs).size !== refs.length) {
|
|
400
|
+
return fail('generate_image: duplicate reference images are not allowed.');
|
|
401
|
+
}
|
|
402
|
+
type Entry = { kind: 'url'; url: string } | { kind: 'local'; ref: ShareRef };
|
|
403
|
+
const entries: Entry[] = [];
|
|
404
|
+
for (const raw of refs) {
|
|
405
|
+
const ref = raw.trim();
|
|
406
|
+
if (/^https:\/\//i.test(ref)) {
|
|
407
|
+
try {
|
|
408
|
+
new URL(ref);
|
|
409
|
+
} catch {
|
|
410
|
+
return fail(`generate_image: reference URL is malformed.`);
|
|
411
|
+
}
|
|
412
|
+
entries.push({ kind: 'url', url: ref });
|
|
413
|
+
} else if (/^http:\/\//i.test(ref)) {
|
|
414
|
+
return fail('generate_image: http:// reference URLs are not allowed — use https.');
|
|
415
|
+
} else if (ref.startsWith('artifact:')) {
|
|
416
|
+
const id = ref.slice('artifact:'.length).trim();
|
|
417
|
+
if (!id) return fail('generate_image: empty artifact reference.');
|
|
418
|
+
entries.push({ kind: 'local', ref: { artifact_id: id } });
|
|
419
|
+
} else {
|
|
420
|
+
entries.push({ kind: 'local', ref: { path: ref } });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const localRefs = entries.filter((e): e is Extract<Entry, { kind: 'local' }> => e.kind === 'local');
|
|
424
|
+
let minted: ShareItem[] = [];
|
|
425
|
+
if (localRefs.length) {
|
|
426
|
+
try {
|
|
427
|
+
minted = await createShares(localRefs.map((e) => e.ref), { purpose: 'codex_ref' });
|
|
428
|
+
} catch (err) {
|
|
429
|
+
if (err instanceof ShareClientError) {
|
|
430
|
+
if (err.code === 'image_ref_not_found') {
|
|
431
|
+
return fail('generate_image: image_ref_not_found: the referenced image/artifact does not exist in this session.');
|
|
432
|
+
}
|
|
433
|
+
return fail(`generate_image: ${err.code}: ${err.message}`);
|
|
434
|
+
}
|
|
435
|
+
return fail(`generate_image: image share service unavailable: ${(err as Error).message}`);
|
|
436
|
+
}
|
|
437
|
+
if (minted.length !== localRefs.length) {
|
|
438
|
+
await revokeSharesBestEffort(minted.map((m) => m.share_id));
|
|
439
|
+
return fail('generate_image: image share service returned a mismatched share count.');
|
|
440
|
+
}
|
|
441
|
+
// Provider fetches these refs over HTTPS, so each needs an absolute URL —
|
|
442
|
+
// which the share API only fills when gateway.publicUrl is configured.
|
|
443
|
+
if (minted.some((m) => !m.url)) {
|
|
444
|
+
await revokeSharesBestEffort(minted.map((m) => m.share_id));
|
|
445
|
+
return fail('generate_image: image reference sharing requires gateway.publicUrl to be configured.');
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// Merge back preserving the caller's order.
|
|
449
|
+
let next = 0;
|
|
450
|
+
const urls = entries.map((e) => (e.kind === 'url' ? e.url : minted[next++]!.url!));
|
|
451
|
+
return { urls, mintedShareIds: minted.map((m) => m.share_id) };
|
|
452
|
+
}
|
|
453
|
+
|
|
290
454
|
private pollTimeoutMs(): number {
|
|
291
455
|
const raw = Number(process.env.IMAGE_POLL_TIMEOUT_MS);
|
|
292
456
|
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_POLL_TIMEOUT_MS;
|
|
@@ -317,7 +481,10 @@ export class ImageModule implements ToolModule {
|
|
|
317
481
|
* item is either base64 bytes (sync providers like openai/gemini) OR an https URL
|
|
318
482
|
* (async providers like nanobanana/bfl/fal, which return a hosted file) — download
|
|
319
483
|
* URLs, decode base64. */
|
|
320
|
-
|
|
484
|
+
// `prompt` is only known on the generate path (same-turn poll); the
|
|
485
|
+
// action="status" path re-enters in a later turn without it, so those
|
|
486
|
+
// artifacts register promptless.
|
|
487
|
+
private async deliver(job: JobResponse, taskId: string, model?: string, prompt?: string): Promise<McpToolResult> {
|
|
321
488
|
const allImages = Array.isArray(job.images) ? job.images.filter((s) => typeof s === 'string' && s.length) : [];
|
|
322
489
|
// Cap how many we process — downloads are sequential (~30s each), so an
|
|
323
490
|
// over-long list would hang the tool call far past the poll budget.
|
|
@@ -338,9 +505,18 @@ export class ImageModule implements ToolModule {
|
|
|
338
505
|
let buf: Buffer;
|
|
339
506
|
if (/^https?:\/\//i.test(item)) {
|
|
340
507
|
// Async providers (nanobanana / bfl / fal / runway) return a hosted image
|
|
341
|
-
// URL — download the bytes. SSRF guard
|
|
342
|
-
//
|
|
343
|
-
// redirect:'error' so a
|
|
508
|
+
// URL — download the bytes. SSRF guard: https-only + reject any host that
|
|
509
|
+
// resolves to a private/loopback/link-local/metadata address, and
|
|
510
|
+
// redirect:'error' so a 3xx hop can't bounce to an internal target.
|
|
511
|
+
//
|
|
512
|
+
// Residual risk (accepted): this is a best-effort screen, NOT a full
|
|
513
|
+
// rebinding defence. assertSafeImageUrl resolves DNS once; fetch()
|
|
514
|
+
// resolves again (Node/undici does not pin the vetted IP), so a
|
|
515
|
+
// low-TTL record could answer public here and 127.0.0.1 at fetch time.
|
|
516
|
+
// redirect:'error' does NOT close this — it only blocks HTTP redirects,
|
|
517
|
+
// not DNS rebinding. The backstop is downstream: the response body must
|
|
518
|
+
// pass detectImageExt() below, so an internal non-image endpoint yields
|
|
519
|
+
// unrecognized bytes and is rejected before anything is written to disk.
|
|
344
520
|
await assertSafeImageUrl(item);
|
|
345
521
|
const res = await fetch(item, { signal: AbortSignal.timeout(30_000), redirect: 'error' });
|
|
346
522
|
if (!res.ok) throw new Error(`download image failed: HTTP ${res.status}`);
|
|
@@ -369,6 +545,28 @@ export class ImageModule implements ToolModule {
|
|
|
369
545
|
} catch (err) {
|
|
370
546
|
return { content: [{ type: 'text', text: `generate_image: failed to save image: ${(err as Error).message}` }], isError: true };
|
|
371
547
|
}
|
|
548
|
+
|
|
549
|
+
// Register the written files as private artifacts (#70, plan §8) so a later
|
|
550
|
+
// turn can reference them as artifact:<id> for editing. Best-effort: when
|
|
551
|
+
// the bridge is off or registration fails, the response simply carries no
|
|
552
|
+
// artifacts — file delivery is unaffected.
|
|
553
|
+
let artifacts: Array<Record<string, unknown>> | undefined;
|
|
554
|
+
if (shareBridgeEnabled() && files.length) {
|
|
555
|
+
const slash = (model ?? '').indexOf('/');
|
|
556
|
+
const provider = slash > 0 ? (model as string).slice(0, slash) : 'unknown';
|
|
557
|
+
const modelName = slash > 0 ? (model as string).slice(slash + 1) : (model || 'unknown');
|
|
558
|
+
const registered = await registerArtifacts(files, { provider, model: modelName, taskId, prompt });
|
|
559
|
+
if (registered) {
|
|
560
|
+
artifacts = registered.map((item, index) => ({
|
|
561
|
+
artifact_id: item.artifact_id,
|
|
562
|
+
artifact_ref: item.artifact_ref,
|
|
563
|
+
index,
|
|
564
|
+
path: files[index],
|
|
565
|
+
provider,
|
|
566
|
+
model: modelName,
|
|
567
|
+
}));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
372
570
|
return {
|
|
373
571
|
content: [{
|
|
374
572
|
type: 'text',
|
|
@@ -378,8 +576,10 @@ export class ImageModule implements ToolModule {
|
|
|
378
576
|
byok: job.byok ?? false,
|
|
379
577
|
cost: job.cost ?? 0,
|
|
380
578
|
files,
|
|
579
|
+
...(artifacts ? { artifacts } : {}),
|
|
381
580
|
...(droppedImages > 0 ? { dropped_images: droppedImages } : {}),
|
|
382
|
-
note: 'Image saved. Deliver it to the user with your channel
|
|
581
|
+
note: 'Image saved. Deliver it to the user with your channel delivery tool — api_reply/reply (files: [...]), or line_image on LINE. Do NOT open/Read the file to inspect it first; attach it and answer briefly.'
|
|
582
|
+
+ (artifacts ? ' To edit this image later, reference it via its artifact_ref (e.g. image: "artifact:...").' : '')
|
|
383
583
|
+ (droppedImages > 0 ? ` (${droppedImages} extra image(s) beyond the cap were not saved)` : ''),
|
|
384
584
|
}),
|
|
385
585
|
}],
|
|
@@ -450,8 +650,10 @@ function sanitize(s: string): string {
|
|
|
450
650
|
// SSRF guard for provider image URLs. Require https, then resolve the host and
|
|
451
651
|
// reject if ANY resolved address is private / loopback / link-local / metadata —
|
|
452
652
|
// so a compromised provider response can't make the gateway fetch internal or
|
|
453
|
-
// cloud-metadata endpoints. Best-effort screen
|
|
454
|
-
// and the fetch is
|
|
653
|
+
// cloud-metadata endpoints. Best-effort screen only: a DNS rebind between this
|
|
654
|
+
// lookup and the fetch is NOT prevented here (Node does not let us pin the vetted
|
|
655
|
+
// IP without a custom undici dispatcher). The real backstop is the caller's
|
|
656
|
+
// post-download detectImageExt() check — non-image bytes are rejected before use.
|
|
455
657
|
async function assertSafeImageUrl(raw: string): Promise<void> {
|
|
456
658
|
let u: URL;
|
|
457
659
|
try {
|
|
@@ -592,7 +794,10 @@ const imageToolDefs: McpToolDefinition[] = [
|
|
|
592
794
|
'Use this WHENEVER the user asks to create, draw, make, or edit an image — it is built in, no app install needed. ' +
|
|
593
795
|
'Generate images from a text prompt (optionally guided by a reference image) via the configured image generation service. ' +
|
|
594
796
|
'action="generate" submits the request and returns the saved image file path(s) once ready — ' +
|
|
595
|
-
'then deliver them with your channel reply tool (files: [...]). ' +
|
|
797
|
+
'then deliver them with your channel reply tool (files: [...]). AFTER A SUCCESSFUL GENERATE: do NOT ' +
|
|
798
|
+
'open/Read the produced file to inspect it and do NOT re-analyze it — the generation already succeeded; ' +
|
|
799
|
+
'attach it with your reply tool and answer in one or two short sentences. Every extra step risks pushing ' +
|
|
800
|
+
'the request past its timeout. ' +
|
|
596
801
|
'action="status" polls a previously returned task_id. ' +
|
|
597
802
|
'action="list" returns every available image model with its supported_qualities, supported_sizes, cost, and ' +
|
|
598
803
|
'the capability flags supports_image_ref (image-to-image / edit) and supports_style_ref. Call it FIRST when ' +
|
|
@@ -603,14 +808,23 @@ const imageToolDefs: McpToolDefinition[] = [
|
|
|
603
808
|
'img2img-capable model is available — or the model you picked has supports_image_ref=false — do NOT pass ' +
|
|
604
809
|
'"image": instead look at the reference image yourself, describe what matters in the "prompt", and generate ' +
|
|
605
810
|
'text-to-image. Never send "image" to a model that does not support it. ' +
|
|
811
|
+
'EARLIER IMAGES IN THE CHAT: when the user points at an image from earlier in this conversation ' +
|
|
812
|
+
'("image number 2", "the first image", "the picture you just made", "that logo from before"), call ' +
|
|
813
|
+
'action="list_refs" FIRST. It returns the ground-truth catalog of this session\'s images numbered by ' +
|
|
814
|
+
'first appearance; pass the chosen item\'s "ref" into "image"/"images". NEVER work out which earlier ' +
|
|
815
|
+
'image the user means by counting from your own memory of the conversation — the numbering there is ' +
|
|
816
|
+
'not reliable, and not remembering any images is NOT evidence there are none (your context may have ' +
|
|
817
|
+
'been reset while the chat history still has them): never answer "no images attached" to such a ' +
|
|
818
|
+
'request before list_refs has returned an empty catalog. If the catalog makes the request ambiguous ' +
|
|
819
|
+
'(or the requested index does not exist), ask the user which image they mean instead of guessing. ' +
|
|
606
820
|
'When the user selected options in the composer (an <image-params .../> tag in the turn), honor those values.',
|
|
607
821
|
inputSchema: {
|
|
608
822
|
type: 'object',
|
|
609
823
|
properties: {
|
|
610
824
|
action: {
|
|
611
825
|
type: 'string',
|
|
612
|
-
enum: ['generate', 'status', 'list'],
|
|
613
|
-
description: 'generate (default) | status | list',
|
|
826
|
+
enum: ['generate', 'status', 'list', 'list_refs'],
|
|
827
|
+
description: 'generate (default) | status | list | list_refs (numbered catalog of this session\'s images, for resolving "the second image" style references)',
|
|
614
828
|
},
|
|
615
829
|
model: {
|
|
616
830
|
type: 'string',
|
|
@@ -621,8 +835,8 @@ const imageToolDefs: McpToolDefinition[] = [
|
|
|
621
835
|
size: { type: 'string', description: 'Optional size, e.g. "1024x1024".' },
|
|
622
836
|
aspect_ratio: { type: 'string', description: 'Optional aspect ratio, e.g. "1:1" (converted to size if the provider needs it).' },
|
|
623
837
|
n: { type: 'integer', description: 'Optional number of images (default 1).' },
|
|
624
|
-
image: { type: 'string', description: 'Optional reference
|
|
625
|
-
images: { type: 'array', items: { type: 'string' }, description: 'Optional multiple reference
|
|
838
|
+
image: { type: 'string', description: 'Optional reference image for image-to-image/edit: a media path (e.g. "media/xxx.png"), an "artifact:<id>" ref from a previous generate_image result, or an https URL. Local/artifact refs are converted to short-lived URLs automatically. ONLY pass this to a model whose supports_image_ref is true (check action="list"); for any other model, describe the reference image in the prompt instead of sending it here. Do not pass both "image" and "images".' },
|
|
839
|
+
images: { type: 'array', items: { type: 'string' }, description: 'Optional multiple reference images (max 5; media paths, artifact:<id> refs, or https URLs — same supports_image_ref rule as "image"). Order is preserved.' },
|
|
626
840
|
style: { type: 'string', description: 'Optional native style parameter (e.g. "vivid") — only for models whose supports_style_ref is true.' },
|
|
627
841
|
task_id: { type: 'string', description: 'Job id to poll (required for action="status").' },
|
|
628
842
|
},
|