kimi-code / packages /acp-server /test /convert.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f0634fb verified
Raw History Blame Contribute Delete
6.08 kB
import { mkdtemp, readdir, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import type { McpServer } from '@agentclientprotocol/sdk';
import type { ContentPart } from '@moonshot-ai/agent-core-v2';
import { afterEach, describe, expect, it } from 'vitest';
import {
acpBlocksToContentParts,
acpMcpServersToConfigRecord,
compressPromptImageParts,
} from '../src/convert';
import { solidPng, solidPngBase64 } from './_helpers/png';
describe('acpMcpServersToConfigRecord', () => {
it('returns undefined for an absent or empty list', () => {
expect(acpMcpServersToConfigRecord(undefined)).toBeUndefined();
expect(acpMcpServersToConfigRecord([])).toBeUndefined();
});
it('maps stdio servers (no type field) to local stdio configs', () => {
const servers: McpServer[] = [
{
name: 'fs',
command: '/usr/local/bin/mcp-fs',
args: ['--root', '/tmp'],
env: [
{ name: 'API_KEY', value: 'secret' },
{ name: 'DEBUG', value: '1' },
],
},
];
expect(acpMcpServersToConfigRecord(servers)).toEqual({
fs: {
transport: 'stdio',
command: '/usr/local/bin/mcp-fs',
args: ['--root', '/tmp'],
env: { API_KEY: 'secret', DEBUG: '1' },
runtime_id: 'local',
},
});
});
it('maps http and sse servers with header pairs as a record', () => {
const servers: McpServer[] = [
{
type: 'http',
name: 'web',
url: 'https://example.com/mcp',
headers: [{ name: 'Authorization', value: 'Bearer x' }],
},
{ type: 'sse', name: 'events', url: 'https://example.com/sse', headers: [] },
];
expect(acpMcpServersToConfigRecord(servers)).toEqual({
web: {
transport: 'http',
url: 'https://example.com/mcp',
headers: { Authorization: 'Bearer x' },
},
events: { transport: 'sse', url: 'https://example.com/sse', headers: undefined },
});
});
it('drops the unstable acp transport and returns undefined when nothing survives', () => {
const servers = [{ type: 'acp', name: 'nested', serverId: 'srv-1' } as unknown as McpServer];
expect(acpMcpServersToConfigRecord(servers)).toBeUndefined();
});
});
describe('acpBlocksToContentParts', () => {
it('projects file links and embedded text resources with provenance', () => {
const parts = acpBlocksToContentParts([
{ type: 'resource_link', uri: 'file:///tmp/example.ts#L2-L4', name: 'example.ts' },
{ type: 'resource_link', uri: 'https://example.test/doc', name: 'remote doc' },
{
type: 'resource',
resource: { uri: 'memory://note/1', text: 'remember this' },
},
] as never);
expect(parts).toEqual([
{ type: 'text', text: '/tmp/example.ts:2-4' },
{
type: 'text',
text: '<resource_link uri="https://example.test/doc" name="remote doc" />',
},
{ type: 'text', text: '<resource uri="memory://note/1">remember this</resource>' },
]);
});
});
describe('compressPromptImageParts', () => {
const trash: string[] = [];
afterEach(async () => {
await Promise.all(trash.splice(0).map((dir) => rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })));
});
async function tempOriginalsDir(): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), 'acp-originals-'));
trash.push(dir);
return dir;
}
function imagePart(url: string): ContentPart {
return { type: 'image_url', imageUrl: { url } };
}
it('leaves format judgment to the engine: a format it cannot re-encode passes through', async () => {
// Which formats are acceptable depends on the provider the agent is
// bound to, which this edge does not know; the engine's prompt gate
// decides. So neither a HEIC payload nor a MIME alias is rewritten here.
const heic = `data:image/heic;base64,${Buffer.alloc(32).toString('base64')}`;
const alias = `data:IMAGE/PNG;base64,${solidPngBase64(8, 8)}`;
const out = await compressPromptImageParts([
{ type: 'text', text: 'look' },
imagePart(heic),
imagePart(alias),
]);
expect(out).toEqual([{ type: 'text', text: 'look' }, imagePart(heic), imagePart(alias)]);
});
it('passes an under-limit image through unchanged and persists nothing', async () => {
const originalsDir = await tempOriginalsDir();
const url = `data:image/png;base64,${solidPngBase64(32, 32)}`;
const out = await compressPromptImageParts([imagePart(url)], {
originalsDir,
maxImageEdgePx: 64,
});
expect(out).toHaveLength(1);
expect(out[0]).toEqual(imagePart(url));
// No compression happened, so no original was persisted.
expect(await readdir(originalsDir)).toEqual([]);
});
it('compresses an over-edge image, prefixes a caption, and persists the original', async () => {
const originalsDir = await tempOriginalsDir();
const original = solidPng(128, 128);
const url = `data:image/png;base64,${original.toString('base64')}`;
const out = await compressPromptImageParts([imagePart(url)], {
originalsDir,
maxImageEdgePx: 64,
});
// caption text part immediately precedes the re-encoded image part.
expect(out).toHaveLength(2);
const caption = out[0] as { text: string };
const image = out[1] as { imageUrl: { url: string } };
expect(caption.text).toContain('Image compressed to fit model limits');
expect(caption.text).toContain('128x128');
expect(caption.text).toContain(originalsDir);
expect(image.imageUrl.url).not.toBe(url);
expect(image.imageUrl.url.startsWith('data:image/')).toBe(true);
// The original bytes landed in the session media-originals dir so the
// model can read fine detail back from the captioned path.
const files = await readdir(originalsDir);
expect(files).toHaveLength(1);
expect(caption.text).toContain(files[0]!);
expect(await readFile(join(originalsDir, files[0]!))).toEqual(original);
});
});