Skip to content

Commit 7e694fb

Browse files
authored
refactor(acp): 收敛 ACP bridge 的 SDK 消息类型边界
这个 PR 不改变 ACP 的运行时协议行为,只收敛 bridge.ts 内部的 SDKMessage 类型边界。 当前 nextSdkMessageOrAbort() 把 IteratorResult<SDKMessage, void> 暴露给主循环,导致 nextResult.value 变成 SDKMessage | void,后续字段访问依赖大量 cast。 本 PR: • 将 done/value 判断封装在 nextSdkMessageOrAbort() 内部 • 主循环只接收 SDKMessage | undefined • 在 bridge.ts 内部增加局部 BridgeSDKMessage union • 不修改全局 SDKMessage,避免影响其他模块 目标是提升 ACP bridge 的类型安全和后续可维护性,不改变现有 ACP 行为。
2 parents fac16da + ad42465 commit 7e694fb

2 files changed

Lines changed: 267 additions & 68 deletions

File tree

src/services/acp/__tests__/bridge.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
toolUpdateFromToolResult,
55
toolUpdateFromEditToolResponse,
66
forwardSessionUpdates,
7+
nextSdkMessageOrAbort,
78
} from '../bridge.js'
89
import { promptToQueryInput } from '../promptConversion.js'
910
import { markdownEscape, toDisplayPath } from '../utils.js'
@@ -30,6 +31,10 @@ async function* makeStream(
3031
for (const m of msgs) yield m
3132
}
3233

34+
async function* makeWaitingStream(): AsyncGenerator<SDKMessage, void, unknown> {
35+
await new Promise<never>(() => {})
36+
}
37+
3338
// ── toolInfoFromToolUse ────────────────────────────────────────────
3439

3540
describe('toolInfoFromToolUse', () => {
@@ -692,6 +697,47 @@ describe('toDisplayPath', () => {
692697

693698
// ── forwardSessionUpdates ─────────────────────────────────────────
694699

700+
describe('nextSdkMessageOrAbort', () => {
701+
test('returns undefined when aborted while waiting for next message', async () => {
702+
const ac = new AbortController()
703+
const pending = nextSdkMessageOrAbort(makeWaitingStream(), ac.signal)
704+
ac.abort()
705+
706+
const result = await Promise.race([
707+
pending,
708+
new Promise<'timeout'>(resolve => setTimeout(resolve, 100, 'timeout')),
709+
])
710+
711+
expect(result).toBeUndefined()
712+
})
713+
714+
test('returns undefined when stream is done', async () => {
715+
const result = await nextSdkMessageOrAbort(
716+
makeStream([]),
717+
new AbortController().signal,
718+
)
719+
720+
expect(result).toBeUndefined()
721+
})
722+
723+
test('returns a valid SDKMessage', async () => {
724+
const msg = {
725+
type: 'assistant',
726+
message: {
727+
role: 'assistant',
728+
content: [{ type: 'text', text: 'hello' }],
729+
},
730+
} as unknown as SDKMessage
731+
732+
const result = await nextSdkMessageOrAbort(
733+
makeStream([msg]),
734+
new AbortController().signal,
735+
)
736+
737+
expect(result).toBe(msg)
738+
})
739+
})
740+
695741
describe('forwardSessionUpdates', () => {
696742
test('returns end_turn when stream is empty', async () => {
697743
const conn = makeConn()
@@ -1077,6 +1123,28 @@ describe('forwardSessionUpdates', () => {
10771123
).toBe(0)
10781124
})
10791125

1126+
test('ignores unknown message types without crashing', async () => {
1127+
const conn = makeConn()
1128+
const debug = console.debug
1129+
const debugMock = mock(() => {})
1130+
console.debug = debugMock as typeof console.debug
1131+
1132+
try {
1133+
const result = await forwardSessionUpdates(
1134+
's1',
1135+
makeStream([{ type: 'future_message' } as unknown as SDKMessage]),
1136+
conn,
1137+
new AbortController().signal,
1138+
{},
1139+
)
1140+
1141+
expect(result.stopReason).toBe('end_turn')
1142+
expect(debugMock).toHaveBeenCalled()
1143+
} finally {
1144+
console.debug = debug
1145+
}
1146+
})
1147+
10801148
test('re-throws unexpected errors from stream', async () => {
10811149
const conn = makeConn()
10821150
async function* errorStream(): AsyncGenerator<

0 commit comments

Comments
 (0)