feat: 支持 MCP 图片内联渲染

This commit is contained in:
shiyue
2026-08-06 13:37:42 +08:00
parent 3f00cfe7a4
commit 3ddf21af85
6 changed files with 237 additions and 3 deletions

View File

@@ -12,7 +12,7 @@ const { createCodexAppRuntime } = require('./lib/codex-app-runtime');
const { createCodexRolloutStore } = require('./lib/codex-rollouts');
const { createSessionSearchIndex } = require('./lib/session-search-index');
const { createUsageStatisticsIndex, UsageStatisticsError } = require('./lib/usage-statistics');
const { TOOLS: CCWEB_MCP_TOOLS } = require('./lib/ccweb-mcp-server');
const { TOOLS: CCWEB_MCP_TOOLS, prepareImagePayload } = require('./lib/ccweb-mcp-server');
const CCWEB_MCP_SERVER_INFO = { name: 'ccweb', version: '1.0.0' };
if (process.argv.includes('--ccweb-mcp-server')) {
@@ -4902,6 +4902,43 @@ function createCcwebPromptUser(args = {}, sourceSessionId = '') {
};
}
function createCcwebDisplayImage(args = {}, sourceSessionId = '') {
const sourceId = sanitizeId(sourceSessionId || '');
const payload = prepareImagePayload(args);
if (!payload.ok) return payload;
const now = new Date();
let attachment = null;
if (payload.image.data) {
const buffer = Buffer.from(payload.image.data, 'base64');
const id = crypto.randomUUID();
const filename = safeFilename(payload.image.title || payload.image.alt || `image${extFromMime(payload.image.mimeType)}`);
const dataPath = attachmentDataPath(id, extFromMime(payload.image.mimeType));
const meta = {
id,
kind: 'image',
filename,
mime: payload.image.mimeType,
size: buffer.length,
createdAt: now.toISOString(),
expiresAt: new Date(now.getTime() + ATTACHMENT_TTL_MS).toISOString(),
path: dataPath,
};
try {
fs.writeFileSync(dataPath, buffer);
saveAttachmentMeta(meta);
} catch (err) {
try { if (fs.existsSync(dataPath)) fs.unlinkSync(dataPath); } catch {}
return mcpToolError('image_store_failed', `保存图片失败: ${err.message}`);
}
attachment = normalizeMessageAttachments([meta])[0] || null;
}
const responseImage = payload.image.data
? { type: 'image', mimeType: payload.image.mimeType, alt: payload.image.alt, title: payload.image.title, size: attachment?.size || 0 }
: payload.image;
return { ok: true, image: responseImage, attachment, status: 'ready', sourceConversationId: sourceId || null };
}
function findCcwebPromptMessage(session, promptId) {
const normalizedPromptId = String(promptId || '').trim();
if (!normalizedPromptId || !Array.isArray(session?.messages)) return null;
@@ -5430,6 +5467,8 @@ function completeCrossConversationReply(requestId, entry = {}, targetSession = n
function callInternalMcpTool(tool, args, sourceSessionId, sourceHopCount) {
switch (tool) {
case 'ccweb_display_image':
return createCcwebDisplayImage(args, sourceSessionId);
case 'ccweb_list_conversations':
return listConversationSummaries(args, sanitizeId(sourceSessionId || ''));
case 'ccweb_set_title':