Files
deepseek-harness/packages/llm/llm-pi-ai/tests/context.spec.ts
2026-07-23 19:38:37 +08:00

208 lines
6.2 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { AttachmentId } from '@deepseek-ai/dsh-attachment'
import type { AttachmentStore, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
import { CallId } from '@deepseek-ai/dsh-llm'
import type { GenerateOptions } from '@deepseek-ai/dsh-llm'
import { toPiContext } from '../src/context.ts'
import { toPiAssistant } from '../src/replay.ts'
const ref: ImageAttachmentRef = {
attachmentId: AttachmentId(`sha256:${'a'.repeat(64)}`),
mediaType: 'image/png',
bytes: 1,
width: 1,
height: 1,
}
const attachments = {
readImage: vi.fn(() => Promise.resolve({ ref, data: Uint8Array.of(1) })),
} as unknown as AttachmentStore
function request(messages: GenerateOptions['messages']): GenerateOptions {
return {
provider: 'openai',
model: 'gpt-4.1',
system: 'system prompt',
tools: [{ name: 'lookup', description: 'look up', parameters: { type: 'object' } }],
messages,
}
}
describe('pi-ai request context conversion', () => {
it('omits absent and empty request-level optional fields', () => {
const base = { provider: 'openai', model: 'gpt-4.1', messages: [] }
expect(toPiContext(base)).toEqual({ messages: [] })
expect(toPiContext({ ...base, tools: [] })).toEqual({ messages: [] })
})
it('converts complete text-only history and rejects nested images without storage', () => {
const callId = CallId('call-1')
expect(toPiContext(request([
{ role: 'system', content: [{ type: 'text', text: 'history system' }] },
{
role: 'assistant',
content: [{ type: 'tool-call', id: callId, name: 'lookup', arguments: '{}' }],
},
{
role: 'user',
content: [
{ type: 'text', text: 'after tool' },
{
type: 'tool-result',
toolCallId: callId,
content: [{ type: 'text', text: '' }],
},
],
},
]))).toMatchObject({
systemPrompt: 'system prompt',
tools: [{ name: 'lookup' }],
messages: [
{ role: 'user', content: 'history system' },
{ role: 'assistant' },
{ role: 'user', content: 'after tool' },
{
role: 'toolResult',
toolCallId: 'call-1',
toolName: 'lookup',
content: [{ type: 'text', text: '(no output)' }],
isError: false,
},
],
})
expect(() => toPiContext(request([{
role: 'user',
content: [{
type: 'tool-result',
toolCallId: callId,
content: [{ type: 'image', attachment: ref }],
}],
}]))).toThrow(/durable attachment service/)
})
it('resolves user and tool-result images while preserving explicit fallbacks', async () => {
const callId = CallId('missing-call')
const knownCallId = CallId('known-call')
const context = await toPiContext(request([
{ role: 'user', content: [{ type: 'text', text: '' }] },
{
role: 'assistant',
content: [
{ type: 'text', text: 'calling' },
{ type: 'tool-call', id: knownCallId, name: 'lookup', arguments: '{}' },
],
},
{
role: 'user',
content: [
{ type: 'image', attachment: ref },
{ type: 'text', text: 'caption' },
{ type: 'reasoning', text: 'ignored' },
],
},
{
role: 'user',
content: [{
type: 'tool-result',
toolCallId: knownCallId,
content: [{ type: 'text', text: '' }],
}],
},
{
role: 'user',
content: [{
type: 'tool-result',
toolCallId: callId,
isError: true,
content: [
{ type: 'tool-result', toolCallId: callId, content: [] },
{ type: 'image', attachment: ref },
],
}],
},
]), attachments)
expect(context.messages).toEqual([
{ role: 'user', content: '', timestamp: 0 },
expect.objectContaining({ role: 'assistant' }),
{
role: 'user',
content: [
{ type: 'image', data: 'AQ==', mimeType: 'image/png' },
{ type: 'text', text: 'caption' },
],
timestamp: 0,
},
{
role: 'toolResult',
toolCallId: 'known-call',
toolName: 'lookup',
content: [{ type: 'text', text: '(no output)' }],
isError: false,
timestamp: 0,
},
{
role: 'toolResult',
toolCallId: 'missing-call',
toolName: 'unknown',
content: [{ type: 'image', data: 'AQ==', mimeType: 'image/png' }],
isError: true,
timestamp: 0,
},
])
})
it('keeps empty text-only users while separating result-only messages', () => {
const callId = CallId('unknown-call')
expect(toPiContext(request([
{ role: 'user', content: [] },
{
role: 'assistant',
content: [
{ type: 'text', text: 'answer' },
{ type: 'tool-call', id: CallId('other-call'), name: 'lookup', arguments: '{}' },
],
},
{
role: 'user',
content: [{
type: 'tool-result',
toolCallId: callId,
content: [{ type: 'text', text: 'result' }],
}],
},
]))).toMatchObject({
messages: [
{ role: 'user', content: '' },
{ role: 'assistant' },
{ role: 'toolResult', toolName: 'unknown' },
],
})
})
it('handles in-history system and assistant messages explicitly on the image path', async () => {
await expect(toPiContext(request([{
role: 'system',
content: [{ type: 'image', attachment: ref }],
}]), attachments)).rejects.toMatchObject({ code: 'UNSUPPORTED_CONTENT' })
await expect(toPiContext(request([
{ role: 'system', content: [{ type: 'text', text: 'history system' }] },
{ role: 'assistant', content: [{ type: 'text', text: 'answer' }] },
{ role: 'user', content: [{ type: 'text', text: 'plain' }] },
]), attachments)).resolves.toMatchObject({
messages: [
{ role: 'user', content: 'history system' },
{ role: 'assistant' },
{ role: 'user', content: 'plain' },
],
})
expect(() => toPiAssistant({
role: 'assistant',
content: [{ type: 'image', attachment: ref }],
})).toThrow(/assistant image output/)
})
})