fix: preserve image attachments in live and restored conversations

This commit is contained in:
2026-09-12 23:51:57 +08:00
parent 1d661f7a56
commit 03fc50f7a9
9 changed files with 347 additions and 10 deletions

View File

@@ -368,6 +368,123 @@ describe('Pi Agent Server real process', () => {
}
}, 10_000);
it('sends DeepSeek images in fresh and restored real sessions', async () => {
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-agent-server-image-delivery-'));
roots.push(root);
const projectPath = path.join(root, 'project');
const configDir = path.join(root, 'config');
const sessionDir = path.join(root, 'sessions');
const extensionDir = path.join(root, 'extensions');
await Promise.all([
mkdir(projectPath, { recursive: true }),
mkdir(configDir, { recursive: true }),
mkdir(sessionDir, { recursive: true }),
mkdir(extensionDir, { recursive: true }),
]);
const provider = await startHeldProvider();
await Promise.all([
writeFile(path.join(configDir, 'settings.json'), JSON.stringify({ httpIdleTimeoutMs: 250 })),
writeFile(path.join(configDir, 'models.json'), JSON.stringify({
providers: {
'makelore-test': {
baseUrl: provider.baseUrl,
api: 'openai-completions',
apiKey: '$MAKELORE_TEST_KEY',
models: [{
id: 'deepseek-flash',
name: 'Test model',
reasoning: true,
compat: { supportsDeveloperRole: false, supportsReasoningEffort: false,
thinkingFormat: 'deepseek', requiresReasoningContentOnAssistantMessages: true },
input: ['text', 'image'],
contextWindow: 32_000,
maxTokens: 4_096,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
}],
},
},
})),
]);
const promptPath = path.join(root, 'system.md');
const languagePromptPath = path.join(root, 'language.md');
await Promise.all([
writeFile(promptPath, 'Describe the supplied image.'),
writeFile(languagePromptPath, MAKELORE_DEFAULT_LANGUAGE_PROMPT),
]);
const extensionHost = new PiManagedExtensionHost();
const registration = await extensionHost.registerWorker({
conversationId: 'image-delivery',
generation: 1,
projectId: 'project-a',
projectPath,
extensionsDir: extensionDir,
});
const runtimeRoot = path.resolve('node_modules/@earendil-works/pi-coding-agent');
const server = new PiAgentServerProcess({
executablePath: process.execPath,
serverPath: path.resolve('resources/pi-agent-server.mjs'),
runtimeRoot,
configDir,
});
const workerOptions: PiWorkerProcessOptions = {
executablePath: process.execPath,
cliPath: path.join(runtimeRoot, 'dist', 'cli.js'),
cwd: projectPath,
configDir,
sessionDir,
conversationId: 'image-delivery',
workerGeneration: 1,
tools: ['bash'],
additionalArgs: [
'--provider', 'makelore-test',
'--model', 'deepseek-flash',
'--thinking', 'off',
'--system-prompt', promptPath,
'--append-system-prompt', languagePromptPath,
'--extension', registration.extensionPath,
'--session-id', 'session-image-delivery',
],
env: { MAKELORE_TEST_KEY: 'secret-image-delivery', ...registration.env },
sensitiveValues: ['secret-image-delivery', ...registration.sensitiveValues],
};
let worker = server.createWorker(workerOptions);
try {
for (let pass = 0; pass < 2; pass += 1) {
await worker.start();
await extensionHost.bindRun('image-delivery', 1, 'run-image-delivery');
const settled = new Promise<void>((resolve) => {
const unsubscribe = worker.subscribe((event) => {
if (event.type !== 'agent_settled') return;
unsubscribe();
resolve();
});
});
const image = { type: 'image', data: 'aW1hZ2U=', mimeType: 'image/png' };
await worker.request(pass === 0
? { type: 'prompt', message: 'Describe this image', images: [image] }
: { type: 'prompt', message: 'Describe the earlier image again' });
await expect.poll(() => provider.requests.length).toBe(pass + 1);
const request = JSON.parse(provider.requests[pass].body);
expect(request.messages).toContainEqual({
role: 'user', content: [
{ type: 'text', text: 'Describe this image' },
{ type: 'image_url', image_url: { url: 'data:image/png;base64,aW1hZ2U=' } },
],
});
provider.release();
await settled;
await worker.stop('test_injection');
if (pass === 0) worker = server.createWorker(workerOptions);
}
} finally {
await worker.stop('test_injection').catch(() => undefined);
await server.stop().catch(() => undefined);
await registration.dispose().catch(() => undefined);
await extensionHost.close().catch(() => undefined);
await provider.close().catch(() => undefined);
}
}, 10_000);
it('boots from the packaged sibling resource layout', async () => {
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-agent-server-layout-'));
roots.push(root);