Files
memind/mindspace-agent-runner.test.mjs
2026-07-12 08:12:15 +08:00

161 lines
5.6 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { agentRunnerInternals, createMindSpaceAgentRunner } from './mindspace-agent-runner.mjs';
test('buildAgentJobPrompt includes asset excerpts and strict JSON instructions', () => {
const prompt = agentRunnerInternals.buildAgentJobPrompt(
{ instruction: '根据资料生成周报' },
[
{
displayName: 'weekly.md',
mimeType: 'text/markdown',
excerpt: '# Weekly\nDone.',
},
],
);
assert.match(prompt, /合法 JSON 对象/);
assert.match(prompt, /weekly\.md/);
assert.match(prompt, /# Weekly/);
});
test('extractJsonObject and normalizeStructuredResult parse structured agent output', () => {
const payload = agentRunnerInternals.extractJsonObject(`
这里是结果
\`\`\`json
{"title":"项目周报","summary":"本周进展","content":"# 周报\\n\\n完成了任务","content_format":"markdown"}
\`\`\`
`);
const result = agentRunnerInternals.normalizeStructuredResult(payload);
assert.equal(result.title, '项目周报');
assert.equal(result.contentFormat, 'markdown');
});
test('extractJsonObject repairs multiline content and trailing commas', () => {
const multiline = agentRunnerInternals.extractJsonObject(`{
"title":"项目周报",
"summary":"本周进展",
"content":"# 周报
完成了任务",
"content_format":"markdown"
}`);
assert.equal(multiline.title, '项目周报');
assert.match(multiline.content, /完成了任务/);
const trailingComma = agentRunnerInternals.extractJsonObject(
'{"title":"页面","summary":"摘要","content":"正文","content_format":"markdown",}',
);
assert.equal(trailingComma.title, '页面');
});
test('extractJsonObject keeps braces inside content strings', () => {
const payload = agentRunnerInternals.extractJsonObject(
'{"title":"页面","summary":"摘要","content":"function demo() { return 1; }","content_format":"markdown"}',
);
assert.match(payload.content, /return 1/);
});
test('readAssetContext reads and truncates text assets', async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-agent-runner-'));
const file = path.join(dir, 'note.md');
await fs.writeFile(file, 'A'.repeat(64), 'utf8');
const context = await agentRunnerInternals.readAssetContext(
{ assetId: 'a1', displayName: 'note.md', mimeType: 'text/markdown', path: file },
16,
);
assert.equal(context.excerpt.length, 16);
assert.equal(context.truncated, true);
});
test('runner claims job, executes reply, bills usage, and completes job', async () => {
const calls = [];
const runner = createMindSpaceAgentRunner({
apiTarget: 'http://example.test',
apiSecret: 'secret',
userAuth: {
async canUseChat() {
return { ok: true };
},
async resolveWorkingDir() {
return '/tmp/user-space';
},
async getAgentSessionPolicy() {
return { enableContextMemory: false, unrestricted: true };
},
async getUserPublishLayout() {
return null;
},
async registerAgentSession(userId, sessionId) {
calls.push(['registerAgentSession', userId, sessionId]);
},
async billSessionUsage(userId, sessionId, tokenState, requestId) {
calls.push(['billSessionUsage', userId, sessionId, tokenState, requestId]);
return { ok: true, costCents: 1 };
},
},
agentJobService: {
async claimJob(jobId) {
calls.push(['claimJob', jobId]);
return {
jobId,
userId: 'user-1',
jobToken: 'job-token',
instruction: '生成周报',
allowedAssets: [{ assetId: 'asset-1', displayName: 'weekly.md', mimeType: 'text/markdown' }],
};
},
async getAssetForJob(jobId, token, assetId) {
calls.push(['getAssetForJob', jobId, token, assetId]);
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-agent-job-'));
const file = path.join(dir, 'weekly.md');
await fs.writeFile(file, '# Weekly\nAll good.', 'utf8');
return {
assetId,
displayName: 'weekly.md',
mimeType: 'text/markdown',
path: file,
};
},
async setSessionId(jobId, token, sessionId) {
calls.push(['setSessionId', jobId, token, sessionId]);
},
async completeJob(jobId, token, payload) {
calls.push(['completeJob', jobId, token, payload]);
return { id: jobId, status: payload.status ?? 'completed', resultPageId: 'page-1' };
},
},
executeSessionReply: async (_apiFetch, sessionId, requestId, prompt) => {
calls.push(['executeSessionReply', sessionId, requestId, prompt]);
return {
text: '{"title":"项目周报","summary":"本周完成情况","content":"# 周报\\n\\n完成了全部任务","content_format":"markdown"}',
tokenState: {
inputTokens: 10,
outputTokens: 20,
totalTokens: 30,
accumulatedInputTokens: 10,
accumulatedOutputTokens: 20,
accumulatedTotalTokens: 30,
},
};
},
apiFetchImpl: async (_pathname, _init) => ({
ok: true,
async text() {
return JSON.stringify({ id: 'session-1' });
},
}),
});
const result = await runner.runJob('job-1');
assert.equal(result.status, 'completed');
assert.equal(calls.some((item) => item[0] === 'setSessionId' && item[3] === 'session-1'), true);
assert.equal(calls.some((item) => item[0] === 'billSessionUsage'), true);
const completeCall = calls.find((item) => item[0] === 'completeJob');
assert.equal(completeCall[3].title, '项目周报');
});