test(cli): cover pi-ai Kimi prompt and tool updates through the headless profile

This commit is contained in:
Yixiang Tu committed 2026-10-08 14:38:55 +08:00
1 parent 86fea5561e
commit 5209845a7d
2 files changed
+196

No files matched your search

@@ -0,0 +1,36 @@
# pi-ai Kimi route with dynamic prompt and tool updates over the shipped headless profile.
- id: headless-startup
disabled: true
- id: headless-runner
disabled: true
- id: llm-deepseek
disabled: true
- id: llm-pi-ai
config:
providers:
moonshotai:
apiKeyEnv: MOONSHOT_API_KEY
baseURL: !!js process.env.DSH_SNAPSHOT_BASE_URL
streamIdleTimeoutMs: 1000
- id: agent-loop
config:
agents:
- id: main
provider: moonshotai
model: kimi-k3
cwd: !!js process.cwd()
- id: agent-instructions
disabled: true
# The fixture counts every model request, so title generation stays off.
- id: session-title-llm
disabled: true
- insert:
- id: dynamic-tool-prompt-updates
name: '../../../../../../../packages/test-support/session-snapshot/tests/fixtures/dynamic-tool-prompt-updates.ts'
@@ -42,6 +42,7 @@ const tsconfigPath = fileURLToPath(new URL('../../../../../../tsconfig.json', im
const reasoningConfigPath = fileURLToPath(new URL('./fixtures/cli.patch.yml', import.meta.url))
const deepseekDefaultsConfigPath = fileURLToPath(new URL('./fixtures/deepseek-defaults.patch.yml', import.meta.url))
const piAiDefaultsConfigPath = fileURLToPath(new URL('./fixtures/pi-ai-defaults.patch.yml', import.meta.url))
const piAiConversationUpdatesConfigPath = fileURLToPath(new URL('./fixtures/pi-ai-conversation-updates.patch.yml', import.meta.url))
const headlessOverlayPath = fileURLToPath(new URL('./fixtures/headless-profile.patch.yml', import.meta.url))
const headlessSessionExpected = join(goldensDir, 'headless-profile', 'session.expected.jsonl')
const headlessReasoningExpected = join(goldensDir, 'headless-profile', 'reasoning.stderr.expected.txt')
@@ -148,6 +149,58 @@ async function deepseekDefaultsServer(
}
}
/** Script Chat Completions responses in request order: `read`, then `snapshot_ping`, then text. */
async function piAiConversationServer(): Promise<DeepSeekDefaultsServer> {
const requests: JsonObject[] = []
const agentReplies = [
{ tool_calls: [{ index: 0, id: 'call_read', type: 'function', function: { name: 'read', arguments: '{"file_path":"task.txt"}' } }] },
{ tool_calls: [{ index: 0, id: 'call_ping', type: 'function', function: { name: 'snapshot_ping', arguments: '{}' } }] },
{ content: 'DONE' },
]
const server = createServer((request: IncomingMessage, response: ServerResponse) => {
let body = ''
request.setEncoding('utf8')
request.on('data', (chunk: string) => { body += chunk })
request.on('end', () => {
requests.push(JSON.parse(body) as JsonObject)
const delta = agentReplies[requests.length - 1] ?? { content: 'UNEXPECTED' }
const finish = 'tool_calls' in delta ? 'tool_calls' : 'stop'
response.writeHead(200, { 'content-type': 'text/event-stream' })
response.end([
`data: ${JSON.stringify({ choices: [{ delta }] })}`,
`data: ${JSON.stringify({ choices: [{ delta: {}, finish_reason: finish }], usage: { prompt_tokens: 3, completion_tokens: 1 } })}`,
'data: [DONE]',
'',
].join('\n\n'))
})
})
await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve))
const address = server.address()
if (address === null || typeof address === 'string') throw new Error('pi-ai conversation snapshot server has no port')
return {
url: `http://127.0.0.1:${address.port}`,
requests,
paths: [],
close: () => new Promise(resolve => server.close(() => { resolve() })),
}
}
/** Project one Chat Completions request to its fixture-owned declarations and message roles, tools, and prompt markers. */
function piAiTranscript(request: JsonObject): JsonObject {
const names = (tools: unknown): string[] => Array.isArray(tools)
? tools.map(tool => ((tool as { function: { name: string } }).function.name))
: []
return {
// Profile tools change independently of this scenario; only the fixture's tool is recorded.
tools: names(request.tools).filter(name => name === 'snapshot_ping'),
messages: (request.messages as JsonObject[]).map(message => ({
role: message.role,
...message.tools === undefined ? {} : { tools: names(message.tools) },
...typeof message.content === 'string' && message.content.includes('Dynamic tool guidance') ? { guidance: true } : {},
})),
}
}
function parseJsonl(content: string): JsonObject[] {
return content.split('\n')
.filter(line => line.trim().length > 0)
@@ -745,6 +798,113 @@ describe('headless stream-json snapshots', () => {
}
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('sends pi-ai Kimi prompt and tool updates in conversation history', async () => {
const server = await piAiConversationServer()
try {
const result = await runLoaderSmoke({
label: 'pi-ai Kimi conversation updates headless stream-json snapshot',
tempDirPrefix: 'headless-snapshot-pi-ai-conversation-updates-',
binScript,
libBinScript: binScript,
configPath: piAiConversationUpdatesConfigPath,
binArgs: [
piAiConversationUpdatesConfigPath,
'read task.txt and follow it',
],
tsconfigPath,
prepare: cwd => writeFile(join(cwd, 'task.txt'), 'Call snapshot_ping once.\n'),
env: {
MOONSHOT_API_KEY: 'snapshot-key',
DSH_SNAPSHOT_BASE_URL: server.url,
NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
},
})
expect(result.stderr).toBe('')
expect(parseJsonl(result.stdout).at(-1)).toMatchObject({ type: 'result', output: 'DONE' })
expect(server.requests.map(piAiTranscript)).toMatchInlineSnapshot(`
[
{
"messages": [
{
"role": "system",
},
{
"role": "user",
},
{
"role": "user",
},
],
"tools": [],
},
{
"messages": [
{
"role": "system",
},
{
"role": "user",
},
{
"role": "user",
},
{
"role": "assistant",
},
{
"role": "tool",
},
{
"guidance": true,
"role": "system",
},
{
"role": "system",
"tools": [
"snapshot_ping",
],
},
],
"tools": [],
},
{
"messages": [
{
"role": "system",
},
{
"role": "user",
},
{
"role": "user",
},
{
"role": "assistant",
},
{
"role": "tool",
},
{
"guidance": true,
"role": "system",
},
{
"role": "assistant",
},
{
"role": "tool",
},
],
"tools": [],
},
]
`)
} finally {
await server.close()
}
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('runs a keyless Agent Team with peer mail, dependent tasks, waiting, and Lead aggregation', async () => {
let projection: unknown
const result = await runLoaderSmoke({