Files
makelore/tests/unit/opencode-message.test.ts
2026-07-29 17:22:35 +08:00

686 lines
19 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { applyStreamingPartDeltaToMessage, normalizeOpencodeSessionMessages } from '@/lib/opencode-message';
describe('opencode message normalization', () => {
it('keeps reasoning deltas as thinking content when the full part has not arrived yet', () => {
const message = applyStreamingPartDeltaToMessage(null, {
sessionID: 'ses_1',
messageID: 'msg_assistant',
partID: 'part_reasoning',
field: 'text',
delta: 'Reviewing the project flow.',
part: {
id: 'part_reasoning',
type: 'reasoning',
},
});
expect(message).toEqual(expect.objectContaining({
id: 'msg_assistant',
role: 'assistant',
content: [
{
type: 'thinking',
id: 'part_reasoning',
thinking: 'Reviewing the project flow.',
},
],
}));
});
it('surfaces a completed assistant message with no visible content as an error', () => {
const messages = normalizeOpencodeSessionMessages([
{
info: {
id: 'msg_empty_assistant',
role: 'assistant',
sessionID: 'ses_1',
time: { created: 1747040000000, completed: 1747040001000 },
},
parts: [
{ id: 'step_start', type: 'step-start' },
{ id: 'step_finish', type: 'step-finish' },
],
},
]);
expect(messages).toEqual([
expect.objectContaining({
id: 'msg_empty_assistant',
role: 'assistant',
isError: true,
errorMessage: '模型没有返回任何内容。请重试,或切换到支持图片输入的模型。',
content: [{
type: 'text',
text: '模型没有返回任何内容。请重试,或切换到支持图片输入的模型。',
}],
}),
]);
});
it('retains bounded runtime tool evidence without exposing raw tool state', () => {
const messages = normalizeOpencodeSessionMessages([
{
info: {
id: 'msg_tool_evidence',
role: 'assistant',
time: { created: 1747040000000, completed: 1747040001000 },
},
parts: [
{
id: 'tool_build',
callID: 'call_build',
type: 'tool',
tool: 'bash',
state: {
status: 'completed',
input: { command: 'pnpm build' },
output: 'build completed',
metadata: { exit: 0 },
time: { start: 1747040000000, end: 1747040000500 },
},
},
],
},
]);
expect(messages[0]?._toolEvidence).toEqual([
expect.objectContaining({
id: 'tool_build',
toolCallId: 'call_build',
name: 'bash',
status: 'completed',
inputSummary: expect.stringContaining('pnpm build'),
exitCode: 0,
}),
]);
});
it('preserves bounded user runtime context and only the true synthetic bit', () => {
const [message] = normalizeOpencodeSessionMessages([{
info: {
id: 'msg_user',
role: 'user',
time: { created: 1_700_000_000_000 },
agent: 'game-development',
model: {
providerID: 'niancode-user-models',
modelID: 'qwen3.7-plus',
variant: 'high',
},
system: 'SECRET SYSTEM PROMPT',
},
parts: [
{ id: 'part_visible', type: 'text', text: 'Visible request', synthetic: false },
{ id: 'part_hidden', type: 'text', text: 'Injected context', synthetic: true },
],
}]);
expect(message?._runtimeContext).toEqual({
agent: 'game-development',
model: 'niancode-user-models/qwen3.7-plus',
variant: 'high',
});
expect(message?.content).toEqual([
{ id: 'part_visible', type: 'text', text: 'Visible request' },
{ id: 'part_hidden', type: 'text', text: 'Injected context', synthetic: true },
]);
expect(JSON.stringify(message)).not.toContain('SECRET SYSTEM PROMPT');
});
it('drops oversized runtime identifiers instead of retaining arbitrary metadata', () => {
const [message] = normalizeOpencodeSessionMessages([{
info: {
id: 'msg_user',
role: 'user',
time: { created: 1_700_000_000_000 },
agent: 'a'.repeat(200),
model: {
providerID: 'provider',
modelID: 'model',
variant: 'v'.repeat(200),
},
},
parts: [{ type: 'text', text: 'Hello' }],
}]);
expect(message?._runtimeContext).toEqual({ model: 'provider/model' });
});
it('renders only a persisted command invocation and its whitelisted context', () => {
const [message] = normalizeOpencodeSessionMessages([{
info: {
id: 'msg_command',
role: 'user',
time: { created: 1_700_000_000_000 },
agent: 'game-development',
model: {
providerID: 'niancode-user-models',
modelID: 'qwen3.7-plus',
},
},
parts: [
{
id: 'prt_invocation',
type: 'command-invocation',
command: 'Review',
arguments: ' raw arguments ',
contextPartIDs: ['prt_context', 'prt_image'],
},
{
id: 'prt_template',
type: 'text',
text: 'PRIVATE EXPANDED TEMPLATE',
synthetic: true,
},
{
id: 'prt_context',
type: 'text',
text: 'Bounded selected path context',
synthetic: true,
},
{
id: 'prt_image',
type: 'file',
mime: 'image/webp',
filename: 'screen.webp',
url: 'data:image/webp;base64,QUFB',
},
{
id: 'prt_plugin',
type: 'text',
text: 'PRIVATE PLUGIN TEXT',
synthetic: true,
},
],
}]);
expect(message?.content?.[0]).toEqual({
id: 'prt_invocation',
type: 'text',
text: '/Review raw arguments ',
});
expect(JSON.stringify(message)).toContain('Bounded selected path context');
expect(message?._attachedFiles ?? []).toHaveLength(1);
expect(JSON.stringify(message)).not.toContain('PRIVATE EXPANDED TEMPLATE');
expect(JSON.stringify(message)).not.toContain('PRIVATE PLUGIN TEXT');
expect(JSON.stringify(message)).not.toContain('contextPartIDs');
});
it('fails closed when a command invocation marker is malformed or duplicated', () => {
const messages = normalizeOpencodeSessionMessages([
{
info: {
id: 'msg_malformed',
role: 'user',
time: { created: 1_700_000_000_000 },
agent: 'game-development',
model: { providerID: 'provider', modelID: 'model' },
},
parts: [
{
id: 'prt_bad',
type: 'command-invocation',
command: '',
arguments: '',
contextPartIDs: ['not-a-part-id'],
},
{ id: 'prt_template', type: 'text', text: 'PRIVATE MALFORMED TEMPLATE', synthetic: true },
],
},
{
info: {
id: 'msg_duplicated',
role: 'user',
time: { created: 1_700_000_000_001 },
},
parts: [
{
id: 'prt_first',
type: 'command-invocation',
command: 'Review',
arguments: '',
contextPartIDs: ['prt_context'],
},
{
id: 'prt_second',
type: 'command-invocation',
command: 'Explain',
arguments: '',
contextPartIDs: [],
},
{
id: 'prt_context',
type: 'text',
text: 'PRIVATE DUPLICATED TEMPLATE',
synthetic: true,
},
],
},
{
info: {
id: 'msg_control',
role: 'user',
time: { created: 1_700_000_000_002 },
},
parts: [
{
id: 'prt_control',
type: 'command-invocation',
command: 'Review\u0000hidden',
arguments: '',
contextPartIDs: ['prt_control_context'],
},
{
id: 'prt_control_context',
type: 'text',
text: 'PRIVATE CONTROL TEMPLATE',
synthetic: true,
},
],
},
{
info: {
id: 'msg_invalid_marker_id',
role: 'user',
time: { created: 1_700_000_000_003 },
},
parts: [
{
id: 'arbitrary-renderer-id',
type: 'command-invocation',
command: 'Review',
arguments: '',
contextPartIDs: ['prt_invalid_id_context'],
},
{
id: 'prt_invalid_id_context',
type: 'text',
text: 'PRIVATE INVALID ID TEMPLATE',
synthetic: true,
},
],
},
]);
expect(messages).toHaveLength(4);
for (const message of messages) {
expect(message.content).toEqual([{
type: 'text',
text: '[Project command metadata unavailable]',
}]);
expect(message._attachedFiles ?? []).toEqual([]);
expect(JSON.stringify(message)).not.toContain('PRIVATE');
}
});
it('fails closed for command markers on every non-user role', () => {
const messages = normalizeOpencodeSessionMessages(
['assistant', 'system', 'unknown-runtime-role'].map((role, index) => ({
info: {
id: `msg_non_user_${index}`,
role,
time: { created: 1_700_000_001_000 + index },
},
parts: [
{
id: `prt_marker_${index}`,
type: 'command-invocation',
command: 'Review',
arguments: '--private',
contextPartIDs: [`prt_private_${index}`, `prt_file_${index}`, `prt_tool_${index}`],
},
{
id: `prt_private_${index}`,
type: 'text',
text: `PRIVATE_ROLE_${index}`,
synthetic: true,
},
{
id: `prt_file_${index}`,
type: 'file',
mime: 'image/png',
filename: 'private.png',
url: 'data:image/png;base64,QUFB',
},
{
id: `prt_tool_${index}`,
type: 'tool',
tool: 'bash',
state: { input: { command: 'print-private-data' } },
},
],
})),
);
expect(messages).toHaveLength(3);
for (const message of messages) {
expect(message.content).toEqual([{
type: 'text',
text: '[Project command metadata unavailable]',
}]);
expect(message._attachedFiles ?? []).toEqual([]);
expect(message._toolEvidence ?? []).toEqual([]);
expect(JSON.stringify(message)).not.toContain('PRIVATE_ROLE');
expect(JSON.stringify(message)).not.toContain('print-private-data');
expect(JSON.stringify(message)).not.toContain('--private');
}
});
it('never renders tool or reasoning parts referenced by a valid user command marker', () => {
const [message] = normalizeOpencodeSessionMessages([{
info: {
id: 'msg_user_unsafe_context',
role: 'user',
time: { created: 1_700_000_001_500 },
},
parts: [
{
id: 'prt_user_unsafe_marker',
type: 'command-invocation',
command: 'Review',
arguments: 'safe arguments',
contextPartIDs: [
'prt_user_safe_text',
'prt_user_private_reasoning',
'prt_user_private_tool',
],
},
{
id: 'prt_user_safe_text',
type: 'text',
text: 'Visible selected context',
synthetic: true,
},
{
id: 'prt_user_private_reasoning',
type: 'reasoning',
text: 'PRIVATE_REASONING',
},
{
id: 'prt_user_private_tool',
callID: 'call_user_private_tool',
type: 'tool',
tool: 'bash',
state: {
status: 'completed',
input: { command: 'PRIVATE_TOOL_INPUT' },
},
},
],
}]);
expect(message?.content).toEqual([
{
id: 'prt_user_unsafe_marker',
type: 'text',
text: '/Review safe arguments',
},
{
id: 'prt_user_safe_text',
type: 'text',
text: 'Visible selected context',
synthetic: true,
},
]);
expect(message?._toolEvidence ?? []).toEqual([]);
expect(JSON.stringify(message)).not.toContain('PRIVATE_REASONING');
expect(JSON.stringify(message)).not.toContain('PRIVATE_TOOL_INPUT');
});
it('fails closed for malformed structured-like and legacy command marker shapes', () => {
const messages = normalizeOpencodeSessionMessages([
{
id: 'msg_missing_info',
role: 'user',
parts: [
{
id: 'prt_missing_info',
type: 'command-invocation',
command: 'Review',
arguments: '',
contextPartIDs: ['prt_missing_info_private'],
},
{
id: 'prt_missing_info_private',
type: 'text',
text: 'PRIVATE_MISSING_INFO',
synthetic: true,
},
],
},
{
info: {
id: 'msg_non_array_parts',
role: 'user',
time: { created: 1_700_000_002_000 },
},
parts: {
marker: {
id: 'prt_non_array',
type: 'command-invocation',
command: 'Review',
arguments: '',
contextPartIDs: ['prt_non_array_private'],
},
privatePart: {
id: 'prt_non_array_private',
type: 'text',
text: 'PRIVATE_NON_ARRAY_PARTS',
synthetic: true,
},
},
},
{
id: 'msg_legacy_array',
role: 'user',
content: [
{
id: 'prt_legacy_marker',
type: 'command-invocation',
command: 'Review',
arguments: '',
contextPartIDs: ['prt_legacy_private'],
},
{
id: 'prt_legacy_private',
type: 'text',
text: 'PRIVATE_LEGACY_ARRAY',
synthetic: true,
},
],
},
{
id: 'msg_array_info',
info: [],
parts: [
{
id: 'prt_array_info_marker',
type: 'command-invocation',
command: 'Review',
arguments: '',
contextPartIDs: ['prt_array_info_private'],
},
{
id: 'prt_array_info_private',
type: 'text',
text: 'PRIVATE_ARRAY_INFO',
synthetic: true,
},
],
},
{
id: 'msg_ordinary_legacy',
role: 'assistant',
content: 'Ordinary legacy content',
},
]);
expect(messages).toHaveLength(5);
for (const message of messages.slice(0, 4)) {
expect(message.content).toEqual([{
type: 'text',
text: '[Project command metadata unavailable]',
}]);
expect(message._attachedFiles ?? []).toEqual([]);
expect(message._toolEvidence ?? []).toEqual([]);
expect(JSON.stringify(message)).not.toContain('PRIVATE');
expect(JSON.stringify(message)).not.toContain('command-invocation');
}
expect(messages[4]?.content).toEqual([{
type: 'text',
text: 'Ordinary legacy content',
}]);
});
it('preserves legacy normalization for structured-like records without a command marker', () => {
const messages = normalizeOpencodeSessionMessages([
{
id: 'msg_legacy_info',
role: 'assistant',
text: 'Legacy info content',
info: { source: 'legacy-adapter' },
},
{
id: 'msg_legacy_parts',
role: 'assistant',
text: 'Legacy parts content',
parts: { source: 'legacy-adapter' },
},
]);
expect(messages).toEqual([
{
id: 'msg_legacy_info',
role: 'assistant',
timestamp: undefined,
content: [{ type: 'text', text: 'Legacy info content' }],
},
{
id: 'msg_legacy_parts',
role: 'assistant',
timestamp: undefined,
content: [{ type: 'text', text: 'Legacy parts content' }],
},
]);
});
it('requires string arguments and enforces command selector bounds', () => {
const invalidArguments: unknown[] = [undefined, null, 42, { private: true }];
const messages = normalizeOpencodeSessionMessages([
...invalidArguments.map((argumentsValue, index) => ({
info: {
id: `msg_bad_arguments_${index}`,
role: 'user',
time: { created: 1_700_000_003_000 + index },
},
parts: [{
id: `prt_bad_arguments_${index}`,
type: 'command-invocation',
command: 'Review',
...(argumentsValue === undefined ? {} : { arguments: argumentsValue }),
contextPartIDs: [],
}],
})),
{
info: { id: 'msg_whitespace_command', role: 'user' },
parts: [{
id: 'prt_whitespace_command',
type: 'command-invocation',
command: ' Review',
arguments: '',
contextPartIDs: [],
}],
},
{
info: { id: 'msg_leading_slash_command', role: 'user' },
parts: [{
id: 'prt_leading_slash_command',
type: 'command-invocation',
command: '/Review',
arguments: '',
contextPartIDs: [],
}],
},
{
info: { id: 'msg_oversized_arguments', role: 'user' },
parts: [{
id: 'prt_oversized_arguments',
type: 'command-invocation',
command: 'Review',
arguments: 'a'.repeat(8_193),
contextPartIDs: [],
}],
},
{
info: { id: 'msg_too_many_context_ids', role: 'user' },
parts: [{
id: 'prt_too_many_context_ids',
type: 'command-invocation',
command: 'Review',
arguments: '',
contextPartIDs: Array.from({ length: 65 }, (_, index) => `prt_context_${index}`),
}],
},
]);
expect(messages).toHaveLength(8);
for (const message of messages) {
expect(message.content).toEqual([{
type: 'text',
text: '[Project command metadata unavailable]',
}]);
expect(message._attachedFiles ?? []).toEqual([]);
}
});
it('does not mistake a large ordinary tool payload for command metadata', () => {
const [message] = normalizeOpencodeSessionMessages([{
info: {
id: 'msg_large_tool_payload',
role: 'assistant',
time: { created: 1_700_000_005_000 },
},
parts: [
{
id: 'prt_visible_result',
type: 'text',
text: 'Visible assistant result',
},
{
id: 'prt_large_tool',
callID: 'call_large_tool',
type: 'tool',
tool: 'inspect',
state: {
status: 'completed',
input: Array.from({ length: 4_100 }, (_, index) => ({ index })),
output: 'Inspection complete',
},
},
],
}]);
expect(message?.content).toEqual([
{
id: 'prt_visible_result',
type: 'text',
text: 'Visible assistant result',
},
expect.objectContaining({
id: 'call_large_tool',
type: 'tool_use',
name: 'inspect',
}),
]);
expect(message?._toolEvidence).toEqual([
expect.objectContaining({
id: 'prt_large_tool',
toolCallId: 'call_large_tool',
name: 'inspect',
status: 'completed',
}),
]);
});
});