70 lines
3.4 KiB
TypeScript
70 lines
3.4 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk'
|
|
import { createUserMessage, CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
|
import { SessionId } from '@deepseek-ai/dsh-session'
|
|
import { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
|
|
import { makeBridgeHarness, textResponse, type BridgeHarness } from './harness.ts'
|
|
|
|
function toolCallResponse(): StreamChunk[] {
|
|
return [
|
|
{ type: 'block-start', index: 0, blockType: 'tool-call' },
|
|
{ type: 'tool-call-delta', index: 0, id: CallId('call-1'), name: 'echo', argumentsDelta: '{}' },
|
|
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('call-1'), name: 'echo', arguments: '{}' } },
|
|
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
|
]
|
|
}
|
|
|
|
describe('ACP automation output boundary', () => {
|
|
let harness: BridgeHarness | undefined
|
|
|
|
afterEach(async () => {
|
|
await harness?.dispose()
|
|
harness = undefined
|
|
})
|
|
|
|
it('does not emit tool, terminal, plan, title, or reasoning presentation updates', async () => {
|
|
harness = await makeBridgeHarness({ script: [toolCallResponse(), textResponse('done')] })
|
|
harness.ctx.tools.register(defineContentToolFixture({
|
|
name: 'echo',
|
|
description: 'Return a deterministic result.',
|
|
parameters: {},
|
|
execute: () => Promise.resolve([{ type: 'text', text: 'tool result' }]),
|
|
}))
|
|
await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
|
|
const { sessionId } = await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] })
|
|
await harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'go' }] })
|
|
|
|
await vi.waitFor(() => { expect(harness!.updates).toHaveLength(1) })
|
|
expect(harness.updates).toEqual([{
|
|
sessionUpdate: 'agent_message_chunk',
|
|
content: { type: 'text', text: 'done' },
|
|
}])
|
|
})
|
|
|
|
it('ignores events from agents the bridge does not own', async () => {
|
|
harness = await makeBridgeHarness({ script: [textResponse('foreign')] })
|
|
await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
|
|
await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] })
|
|
const { agent } = await harness.ctx.agents.create({
|
|
sessionId: SessionId('foreign'),
|
|
agentOptions: { provider: 'mock', model: 'mock' },
|
|
})
|
|
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } }))
|
|
await agent.whenIdle()
|
|
expect(harness.updates).toHaveLength(0)
|
|
})
|
|
|
|
// `session/update` is a JSON-RPC notification, so a client-side handler
|
|
// failure never reaches the bridge; this pins that the prompt still settles
|
|
// normally with such a client. The bridge's own write-failure guard is
|
|
// transport-level and documented untestable at `notify`.
|
|
it('settles the prompt normally when the client rejects update notifications', async () => {
|
|
harness = await makeBridgeHarness({ script: [textResponse('answer')] })
|
|
await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
|
|
const { sessionId } = await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] })
|
|
harness.onSessionUpdateError = () => {}
|
|
await expect(harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'go' }] }))
|
|
.resolves.toEqual({ stopReason: 'end_turn' })
|
|
})
|
|
})
|