feat: add save_image tool and list image MCP tools

Expose save_image across direct model, MCP gateway, DeepSeek Harness and remote Agents (written on the remote host via chunked reads), and show the image MCP tool list in settings.
This commit is contained in:
mesalogo
2026-10-02 10:08:42 +08:00
parent b02ce31886
commit dd29f309f1
20 changed files with 479 additions and 33 deletions
+24 -3
View File
@@ -1,14 +1,19 @@
import { z } from 'zod'
import { agentIdentifierSchema, positiveAgentSequenceSchema } from './agent-protocol/contracts'
import { imageOperationSchema, imageToolDescriptionLimit, imageToolInputSchema } from './image-generation-contracts'
import { imageOperationSchema, imageSaveMaximumBytes, imageSaveMimeTypeSchema, imageToolDescriptionLimit, imageToolInputSchema } from './image-generation-contracts'
export const remoteImageToolSchema = z.object({
channelId: agentIdentifierSchema,
channelEpoch: positiveAgentSequenceSchema,
description: z.string().min(1).max(imageToolDescriptionLimit).optional(),
storyGraph: z.boolean().optional()
storyGraph: z.boolean().optional(),
/** When present, the Agent exposes save_image with this description and writes files on its own host. */
saveDescription: z.string().min(1).max(imageToolDescriptionLimit).optional()
}).strict()
/** Raw bytes per save_image chunk; base64 plus JSON framing stays below remoteImageToolMaximumBytes. */
export const remoteImageSaveChunkBytes = 128 * 1024
export const remoteImageToolCallSchema = z.union([z.object({
callId: z.string().uuid(),
input: imageToolInputSchema
@@ -16,16 +21,32 @@ export const remoteImageToolCallSchema = z.union([z.object({
callId: z.string().uuid(),
name: z.enum(['story_graph_list', 'story_graph_search', 'story_graph_get_context', 'story_graph_read_source']),
input: z.record(z.string(), z.unknown())
}).strict(), z.object({
callId: z.string().uuid(),
name: z.literal('save_image_read'),
input: z.object({
artifactId: z.string().uuid(),
mimeType: imageSaveMimeTypeSchema,
offset: z.number().int().min(0).max(imageSaveMaximumBytes)
}).strict()
}).strict()])
export const remoteImageSaveChunkSchema = z.object({
mimeType: imageSaveMimeTypeSchema,
totalBytes: z.number().int().min(1).max(imageSaveMaximumBytes),
offset: z.number().int().min(0).max(imageSaveMaximumBytes),
data: z.string().max(Math.ceil(remoteImageSaveChunkBytes / 3) * 4)
}).strict()
export const remoteImageToolReplySchema = z.object({
callId: z.string().uuid(),
result: imageOperationSchema.optional(),
storyGraphResult: z.record(z.string(), z.unknown()).optional(),
imageChunk: remoteImageSaveChunkSchema.optional(),
error: z.string().max(4_000).optional()
}).strict()
// Bounded graph evidence or image-operation metadata. Image bytes never cross this channel.
// Bounded graph evidence, image-operation metadata, or one chunk of an image the model explicitly asked to save.
export const remoteImageToolMaximumBytes = 256 * 1024
export function encodeRemoteImageToolMessage(value: unknown): Uint8Array {