fix(subagent-claude-code): tighten provider evidence

This commit is contained in:
pku-xht
2026-08-05 07:32:27 +08:00
parent 80adf8c7f4
commit 6d8095825b
13 changed files with 78 additions and 14 deletions

View File

@@ -99,7 +99,7 @@ export async function startMessagesFixture(
request.on('data', (chunk: Buffer) => { chunks.push(chunk) })
request.on('end', () => {
const path = request.url ?? ''
if (!path.startsWith('/v1/messages')) {
if (path !== '/v1/messages' && !path.startsWith('/v1/messages?')) {
response.writeHead(404, { 'content-type': 'application/json' })
response.end(JSON.stringify({
type: 'error',

View File

@@ -10,6 +10,11 @@ import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { promisify } from 'node:util'
import type {
Query,
SDKMessage,
SDKSystemMessage,
} from '@anthropic-ai/claude-agent-sdk'
import { Context } from 'cordis'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { Agent } from '@deepseek-ai/dsh-agent'
@@ -23,6 +28,39 @@ import {
type MessagesFixture,
} from './messages-fixture.ts'
const observedSdkMessages = vi.hoisted((): SDKMessage[] => [])
vi.mock('@anthropic-ai/claude-agent-sdk', async (importOriginal) => {
const actual = await importOriginal<
typeof import('@anthropic-ai/claude-agent-sdk')
>()
return {
...actual,
query(options: Parameters<typeof actual.query>[0]): Query {
const query = actual.query(options)
// Observe the real SDK stream without replacing its protocol or CLI.
return new Proxy(query, {
get(target, property) {
if (property === Symbol.asyncIterator) {
return async function* (): AsyncGenerator<SDKMessage, void> {
for await (const message of target) {
observedSdkMessages.push(message)
yield message
}
}
}
const value: unknown = Reflect.get(target, property, target)
if (typeof value === 'function') {
const method = value as (...args: unknown[]) => unknown
return method.bind(target)
}
return value
},
})
},
}
})
const execFileAsync = promisify(execFile)
const sdkRoot = dirname(fileURLToPath(
import.meta.resolve('@anthropic-ai/claude-agent-sdk'),
@@ -54,6 +92,7 @@ afterEach(async () => {
for (const root of roots.splice(0)) {
rmSync(root, { recursive: true, force: true })
}
observedSdkMessages.length = 0
})
interface RealHarness {
@@ -168,10 +207,16 @@ describe('real Claude Agent SDK 0.3.220 and Claude Code 2.1.220', {
})
await run.dispose()
const initMessage = observedSdkMessages.find(
(message): message is SDKSystemMessage =>
message.type === 'system' && message.subtype === 'init',
)
expect(initMessage?.claude_code_version).toBe('2.1.220')
expect(fixture.requests).toHaveLength(1)
const recorded = fixture.requests[0]!
expect(recorded.method).toBe('POST')
expect(recorded.path).toMatch(/^\/v1\/messages(?:\\?|$)/)
expect(recorded.path).toMatch(/^\/v1\/messages(?:\?.*)?$/)
expect(recorded.headers['x-api-key']).toBe(fakeKey)
expect(recorded.body.model).toBe(settingsModel)
expect(Array.isArray(recorded.body.messages)).toBe(true)

View File

@@ -341,6 +341,17 @@ describe('task admission and package contracts', () => {
disposeGraceMs: 29,
})
await expect(ctx.subagents.start('claude-code', {
...request(),
parent: {
id: 'parent-without-cwd',
session: { header: {} },
} as unknown as Agent,
})).rejects.toThrow(
'subagent-claude-code: no working directory for the child — delegate from a parent session that has one',
)
expect(queryMock).not.toHaveBeenCalled()
const run = await ctx.subagents.start('claude-code', request())
child.settle({ exitCode: 9, signal: null })
child.stdout.end()