test(cli): cover pi-ai Kimi prompt and tool updates through the headless profile
This commit is contained in:
1 parent
86fea5561e
commit
5209845a7d
2 files changed
+196
No files matched your search
+36
@@ -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({
|
||||
|
||||
Reference in new issue
Block a user