{"record":{"id":"d5c584e59bb35c9a","repo":"zed-industries/zed","slug":"anthropic-does-not-support-custom-tool-calls","errorCode":null,"errorMessage":"Anthropic does not support custom tool calls","messagePattern":"Anthropic does not support custom tool calls","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/anthropic/src/completion.rs","lineNumber":171,"sourceCode":"                Ok(None)\n            }\n        }\n        MessageContent::Image(image) => Ok(Some(RequestContent::Image {\n            source: ImageSource {\n                source_type: \"base64\".to_string(),\n                media_type: \"image/png\".to_string(),\n                data: image.source.to_string(),\n            },\n            cache_control: None,\n        })),\n        MessageContent::ToolUse(tool_use) => match tool_use.input {\n            LanguageModelToolUseInput::Json(input) => Ok(Some(RequestContent::ToolUse {\n                id: tool_use.id.to_string(),\n                name: tool_use.name.to_string(),\n                input,\n                cache_control: None,\n            })),\n            LanguageModelToolUseInput::Text(_) => Err(anyhow::anyhow!(\n                \"Anthropic does not support custom tool calls\"\n            )),\n        },\n        MessageContent::ToolResult(tool_result) => {\n            let content = match tool_result.content.as_slice() {\n                [LanguageModelToolResultContent::Text(text)] => {\n                    ToolResultContent::Plain(text.to_string())\n                }\n                _ => {\n                    let parts = tool_result\n                        .content\n                        .into_iter()\n                        .map(|part| match part {\n                            LanguageModelToolResultContent::Text(text) => ToolResultPart::Text {\n                                text: text.to_string(),\n                            },\n                            LanguageModelToolResultContent::Image(image) => ToolResultPart::Image {\n                                source: ImageSource {","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/anthropic/src/completion.rs#L153-L189","documentation":"When converting a conversation into Anthropic Messages API requests, tool_use content must carry structured JSON input (Anthropic requires an 'input' object). LanguageModelToolUseInput::Text — free-form text produced by custom/untyped tools — has no faithful Anthropic encoding, so the conversion refuses it rather than sending a malformed request.","triggerScenarios":"Replaying a thread that contains a tool call with text input into the Anthropic backend; switching models/providers mid-thread; custom tools that attach unstructured text as tool input.","commonSituations":"Switching the agent from another provider to Anthropic on an existing thread; custom tools without JSON schemas; imported transcripts or fixtures containing Text tool uses.","solutions":["Use tools whose inputs are JSON (structured input schema) when Anthropic is the backend.","Start a new thread after switching to Anthropic instead of replaying old tool calls.","As a developer: convert Text input before serialization (e.g. wrap it as a JSON object) or skip incompatible turns with a warning."],"exampleFix":"// before\nLanguageModelToolUseInput::Text(_) => Err(anyhow::anyhow!(\n    \"Anthropic does not support custom tool calls\"\n)),\n// after: wrap free-form text as JSON input\nLanguageModelToolUseInput::Text(text) => Ok(Some(RequestContent::ToolUse {\n    id: tool_use.id.to_string(),\n    name: tool_use.name.to_string(),\n    input: serde_json::json!({ \"text\": text.to_string() }),\n    cache_control: None,\n})),","handlingStrategy":"type-guard","validationCode":"for message in &request.messages {\n    for content in &message.content {\n        if let MessageContent::ToolUse(tool_use) = content {\n            if matches!(tool_use.input, LanguageModelToolUseInput::Text(_)) {\n                // convert to JSON or reject before handing the request to Anthropic\n            }\n        }\n    }\n}","typeGuard":"fn is_anthropic_compatible(request: &LanguageModelRequest) -> bool {\n    request\n        .messages\n        .iter()\n        .flat_map(|message| message.content.iter())\n        .all(|content| match content {\n            MessageContent::ToolUse(tool_use) => {\n                matches!(tool_use.input, LanguageModelToolUseInput::Json(_))\n            }\n            _ => true,\n        })\n}","tryCatchPattern":null,"preventionTips":["Define tool input schemas as JSON objects rather than free-form text.","Gate provider switching on compatibility, or start a fresh thread when replaying is impossible.","Convert Text tool inputs at the boundary where custom tools are invoked."],"tags":["anthropic","tool-calls","provider-mismatch","api","serialization"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}