1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
# Recommendations — Fixing Claude Opus Tool Calling
## 1. Add Anthropic Schema Normalization
**Problem:** `zodToJsonSchema()` generates Draft 7 JSON Schema with `$schema`, `additionalProperties`, and potentially `default` fields that Anthropic's API doesn't support.
**Fix:** Add a normalization step between `zodToJsonSchema()` and `jsonSchema()` in `packages/core/src/tools/registry.ts` that strips unsupported fields:
```typescript
function normalizeForAnthropic(schema: Record<string, unknown>): Record<string, unknown> {
// Remove fields Anthropic doesn't support
delete schema.$schema;
delete schema.additionalProperties;
delete schema.default;
// Strip from nested properties too
if (schema.properties && typeof schema.properties === 'object') {
for (const key of Object.keys(schema.properties as Record<string, unknown>)) {
const prop = (schema.properties as Record<string, unknown>)[key] as Record<string, unknown>;
delete prop.$schema;
delete prop.additionalProperties;
delete prop.default;
}
}
return schema;
}
```
**File to modify:** `packages/core/src/tools/registry.ts` — the `toAISDKTool()` function.
## 2. Verify @ai-sdk/anthropic Adapter Version
**Check:** `node_modules/@ai-sdk/anthropic/dist/index.mjs` — verify the adapter properly converts `input_schema` to Anthropic's `input_schema` format on the wire.
The AI SDK v6 adapter should handle this, but verify by looking at how it serializes tools. The key serialization happens in the adapter's `convertToolsToAnthropic()` or similar function.
## 3. Add `tool_choice: "any"` for Opus
**Problem:** Opus may decide not to call tools even when it should.
**Fix:** For Claude Opus sessions, consider setting `tool_choice: { type: "any" }` (or `"auto"`) to encourage tool use. Currently dispatch doesn't set any explicit `tool_choice` — the AI SDK default may be suboptimal.
In `packages/core/src/agent/agent.ts`, the `streamText` options don't include `toolChoice`. Consider adding it conditionally for anthropic provider:
```typescript
const streamOptions = {
model,
messages: coreMessages,
tools,
...(isClaudeOAuth ? { toolChoice: "auto" } : {}),
};
```
## 4. Check Parameter Descriptions
**Problem:** All tools have `.describe()` on their parameters, but verify the AI SDK's Anthropic adapter is forwarding these descriptions to the Anthropic `input_schema.properties.*.description` field.
## 5. System Prompt Tool Instructions
The current system prompt in `agent-manager.ts` lists tools generically:
```
"You have access to the following tools:\n\n{tool_list}\n\nWhen asked to work with files, use these tools."
```
For Claude Opus, add more explicit instructions about WHEN to use each tool and that it SHOULD use tools rather than just talking about solutions.
## 6. Debugging: Log the Actual API Request
Add logging to see what's actually sent to Anthropic for the `tools` parameter. The most reliable way is to add a `fetch` wrapper or check the `@ai-sdk/anthropic` adapter's serialization.
Quick check:
```bash
node -e "
import { zodToJsonSchema } from 'zod-to-json-schema';
import { z } from 'zod';
const schema = z.object({
path: z.string().describe('Path to the file'),
offset: z.number().int().optional(),
});
console.log(JSON.stringify(zodToJsonSchema(schema), null, 2));
"
```
This will show exactly what JSON Schema is produced and whether it has `$schema`, `additionalProperties`, etc.
## 7. Test with Raw Anthropic API
Bypass the AI SDK entirely and test with a direct API call to isolate whether the issue is in the AI SDK adapter:
```bash
curl -X POST https://api.anthropic.com/v1/messages \
-H "anthropic-version: 2023-06-01" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-20250514",
"max_tokens": 1024,
"tools": [
{
"name": "read_file",
"description": "Read the contents of a file",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to file"}
},
"required": ["path"]
}
}
],
"messages": [
{"role": "user", "content": "Read /etc/hostname"}
]
}'
```
If this works but dispatch doesn't, the issue is in the AI SDK adapter or the schema conversion.
|