fix: 主线 B3/B4 结构化工具调用与完成前验证
- 打通 Worker → WorkerManager → Provider 的 tools 传递链路,ProviderManager/adapter 返回结构化 tool_calls 给 WorkerRuntime - OpenAI-compatible/Anthropic adapter 发送工具 schema,并解析 provider 返回的 tool_calls/tool_use;OpenAI 工具名使用 fs.write ↔ fs__write 双向映射 - 修复独立复审发现的 OpenAI 协议隐患:assistant tool_use blocks 必须转换为 assistant.tool_calls,后续 role=tool 消息的 tool_call_id 必须匹配前一轮 tool_calls[].id;不再把 tool_use JSON 字符串化为普通文本 - ExecutorRole 优先消费原生 tool_calls,回灌 canonical tool_result block;移除 fs.write(...)/shell.run(...) 函数调用正则解析,只保留严格 JSON tool_call fallback 与 filename code block 兼容 - DONE 前执行 verification-before-completion:任务要求 build/compile/run/test/编译/ 运行/测试时必须实际 shell.run 验证,失败不 checkpoint、不返回 completed - fs.write 覆盖已有文件也强制 read-before-write,补齐 Claude Code 文件状态纪律 - 新增 packages/workers/test/executor-role.test.ts 行为测试:原生 tool_calls 执行、 verification 失败不得 completed 真实验收: - TSC=0 - bun test packages/workers/test/executor-role.test.ts: 2 pass / 0 fail - OpenAI converter 探针确认 assistant.tool_calls 与 role=tool 的 tool_call_id 匹配 - 真实 GLM Worker C++ 编译运行任务通过,worker verification 记录实际命令: c++ hello.cpp -o /tmp/aircoding-verify && /tmp/aircoding-verify Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ interface AnthropicApiRequest {
|
||||
top_p?: number
|
||||
system?: string
|
||||
stream?: boolean
|
||||
tools?: Array<{ name: string; description: string; input_schema: Record<string, unknown> }>
|
||||
}
|
||||
|
||||
export class AnthropicAdapter implements ProviderAdapter {
|
||||
@@ -101,6 +102,7 @@ export class AnthropicAdapter implements ProviderAdapter {
|
||||
temperature: input.temperature,
|
||||
system: input.system as string | undefined,
|
||||
stream: false,
|
||||
tools: this.convert_tools(input.tools),
|
||||
})
|
||||
|
||||
// Yield each content block as an event
|
||||
@@ -110,6 +112,8 @@ export class AnthropicAdapter implements ProviderAdapter {
|
||||
yield { type: 'content_delta', payload: { type: 'text_delta', text: block.text } }
|
||||
} else if (block.type === 'thinking' && block.thinking) {
|
||||
yield { type: 'content_delta', payload: { type: 'thinking_delta', thinking: block.thinking } }
|
||||
} else if (block.type === 'tool_use' && block.name) {
|
||||
yield { type: 'tool_use', payload: { id: block.id, name: block.name, input: (block.input as Record<string, unknown>) || {} } }
|
||||
}
|
||||
}
|
||||
if (response.usage) {
|
||||
@@ -126,19 +130,36 @@ export class AnthropicAdapter implements ProviderAdapter {
|
||||
* Backward-compat: single-shot complete that returns string content.
|
||||
* Used by MainAgent.classify_via_llm.
|
||||
*/
|
||||
async complete_text(messages: unknown[], options: { model?: string; max_tokens?: number } = {}): Promise<{ content: string; usage?: { input_tokens: number; output_tokens: number } }> {
|
||||
async complete_text(messages: unknown[], options: { model?: string; max_tokens?: number; tools?: unknown[] } = {}): Promise<{ content: string; usage?: { input_tokens: number; output_tokens: number }; tool_calls?: Array<{ id?: string; name: string; arguments: Record<string, unknown> }> }> {
|
||||
const response = await this.make_request({
|
||||
model: options.model || 'claude-haiku-4-5-20251001',
|
||||
messages: this.convert_raw_messages(messages),
|
||||
max_tokens: options.max_tokens || 1024,
|
||||
tools: this.convert_tools(options.tools),
|
||||
stream: false,
|
||||
})
|
||||
const tool_calls = response.content
|
||||
.filter(b => b.type === 'tool_use' && b.name)
|
||||
.map(b => ({ id: b.id, name: b.name!, arguments: (b.input as Record<string, unknown>) || {} }))
|
||||
return {
|
||||
content: this.extract_content(response),
|
||||
usage: response.usage ? { input_tokens: response.usage.input_tokens, output_tokens: response.usage.output_tokens } : undefined,
|
||||
tool_calls: tool_calls.length ? tool_calls : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
private convert_tools(tools?: unknown[]): Array<{ name: string; description: string; input_schema: Record<string, unknown> }> | undefined {
|
||||
if (!tools?.length) return undefined
|
||||
return tools.map(t => {
|
||||
const tool = t as { name: string; description?: string; input_schema?: Record<string, unknown> }
|
||||
return {
|
||||
name: tool.name,
|
||||
description: tool.description || tool.name,
|
||||
input_schema: tool.input_schema || { type: 'object', properties: {} },
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private convert_to_anthropic_messages(messages: Array<{ role: string; content: unknown }>): Array<{ role: string; content: Array<Record<string, unknown>> }> {
|
||||
return messages.map(m => {
|
||||
const blocks: Array<Record<string, unknown>> = []
|
||||
|
||||
Reference in New Issue
Block a user