feat: add disallowedTools to provider config
Allow providers to specify tools that should be disabled via a disallowedTools array in config.json. This is useful for providers that extend "claude" but point to third-party API endpoints that don't support Anthropic-only server-side tools like WebSearch. Closes #390
This commit is contained in:
@@ -107,6 +107,7 @@ Required fields for custom providers:
|
||||
- `ANTHROPIC_AUTH_TOKEN` is used instead of `ANTHROPIC_API_KEY` — this is the z.ai API key
|
||||
- The `API_TIMEOUT_MS` env var extends the request timeout (z.ai can be slower than direct Anthropic)
|
||||
- If you get auth errors, run `/logout` inside Claude Code before switching to the z.ai provider
|
||||
- Web search (`WebSearch` tool) is an Anthropic-only server-side feature — third-party endpoints don't support it. Add `"disallowedTools": ["WebSearch"]` to avoid errors.
|
||||
- Automated setup is also available: `npx @z_ai/coding-helper`
|
||||
- Official docs: [docs.z.ai/devpack/tool/claude](https://docs.z.ai/devpack/tool/claude)
|
||||
|
||||
@@ -173,6 +174,7 @@ For pay-as-you-go, use `ANTHROPIC_API_KEY` with a standard Model Studio key (`sk
|
||||
|
||||
- API keys must be created in the **Singapore region**
|
||||
- The coding plan is for personal use only in interactive coding tools
|
||||
- Web search (`WebSearch` tool) is an Anthropic-only server-side feature — third-party endpoints don't support it. Add `"disallowedTools": ["WebSearch"]` to avoid errors.
|
||||
- Official docs: [alibabacloud.com/help/en/model-studio/claude-code-coding-plan](https://www.alibabacloud.com/help/en/model-studio/claude-code-coding-plan)
|
||||
|
||||
---
|
||||
@@ -436,6 +438,7 @@ Every entry under `agents.providers` accepts these fields:
|
||||
| `command` | `string[]` | Yes (ACP only) | Command to spawn the agent process |
|
||||
| `env` | `Record<string, string>` | No | Environment variables to set for the agent process |
|
||||
| `models` | `ProviderProfileModel[]` | No | Static model list (overrides runtime discovery) |
|
||||
| `disallowedTools` | `string[]` | No | Tool names to disable for this provider (e.g. `["WebSearch"]`) |
|
||||
| `enabled` | `boolean` | No | Set to `false` to hide the provider (default: `true`) |
|
||||
| `order` | `number` | No | Sort order in the provider list |
|
||||
|
||||
@@ -460,6 +463,29 @@ Each entry in the `models` array:
|
||||
| `description` | `string` | No | Short description |
|
||||
| `isDefault` | `boolean` | No | Mark as the default thinking option |
|
||||
|
||||
### Gotcha: `extends: "claude"` with third-party endpoints
|
||||
|
||||
When a custom provider extends `"claude"` but points `ANTHROPIC_BASE_URL` at a non-Anthropic API (Z.AI, Alibaba/Qwen, proxies), the Claude Agent SDK may try to use Anthropic-only server-side tools like `WebSearch`. Third-party APIs don't support these tools, causing errors.
|
||||
|
||||
Use `disallowedTools` to disable unsupported tools:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"my-proxy": {
|
||||
"extends": "claude",
|
||||
"label": "My Proxy",
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://my-proxy.example.com/v1"
|
||||
},
|
||||
"disallowedTools": ["WebSearch"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Valid `extends` values
|
||||
|
||||
Built-in providers: `claude`, `codex`, `copilot`, `opencode`, `pi`
|
||||
|
||||
@@ -34,6 +34,7 @@ export const ProviderRuntimeSettingsSchema = z
|
||||
.object({
|
||||
command: ProviderCommandSchema.optional(),
|
||||
env: z.record(z.string()).optional(),
|
||||
disallowedTools: z.array(z.string()).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -64,6 +65,7 @@ export const ProviderOverrideSchema = z
|
||||
command: z.array(z.string().min(1)).min(1).optional(),
|
||||
env: z.record(z.string()).optional(),
|
||||
models: z.array(ProviderProfileModelSchema).optional(),
|
||||
disallowedTools: z.array(z.string()).optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
order: z.number().optional(),
|
||||
})
|
||||
|
||||
@@ -473,6 +473,52 @@ describe("buildProviderRegistry", () => {
|
||||
expect(mockState.isCommandAvailable).toHaveBeenCalledWith("claude");
|
||||
});
|
||||
|
||||
test("disallowedTools flows through to runtime settings", () => {
|
||||
buildProviderRegistry(logger, {
|
||||
providerOverrides: {
|
||||
claude: {
|
||||
disallowedTools: ["WebSearch", "WebFetch"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockState.constructorArgs.claude[0]).toEqual({
|
||||
runtimeSettings: {
|
||||
command: undefined,
|
||||
env: undefined,
|
||||
disallowedTools: ["WebSearch", "WebFetch"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("derived provider inherits and merges disallowedTools from base", () => {
|
||||
buildProviderRegistry(logger, {
|
||||
providerOverrides: {
|
||||
claude: {
|
||||
disallowedTools: ["WebSearch"],
|
||||
},
|
||||
zai: {
|
||||
extends: "claude",
|
||||
label: "ZAI",
|
||||
disallowedTools: ["ComputerUse"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const zaiArgs = mockState.constructorArgs.claude.find(
|
||||
(entry) =>
|
||||
Array.isArray((entry.runtimeSettings as { disallowedTools?: string[] })?.disallowedTools) &&
|
||||
(entry.runtimeSettings as { disallowedTools: string[] }).disallowedTools.includes(
|
||||
"ComputerUse",
|
||||
),
|
||||
);
|
||||
expect(zaiArgs).toBeDefined();
|
||||
expect((zaiArgs!.runtimeSettings as { disallowedTools: string[] }).disallowedTools).toEqual([
|
||||
"WebSearch",
|
||||
"ComputerUse",
|
||||
]);
|
||||
});
|
||||
|
||||
test("extension inherits base override — override claude command, zai extends claude gets overridden command", () => {
|
||||
buildProviderRegistry(logger, {
|
||||
providerOverrides: {
|
||||
|
||||
@@ -90,7 +90,7 @@ function getProviderClientFactory(provider: string): ProviderClientFactory {
|
||||
}
|
||||
|
||||
function toRuntimeSettings(override?: ProviderOverride): ProviderRuntimeSettings | undefined {
|
||||
if (!override?.command && !override?.env) {
|
||||
if (!override?.command && !override?.env && !override?.disallowedTools) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ function toRuntimeSettings(override?: ProviderOverride): ProviderRuntimeSettings
|
||||
}
|
||||
: undefined,
|
||||
env: override.env,
|
||||
disallowedTools: override.disallowedTools,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -122,6 +123,10 @@ function mergeRuntimeSettings(
|
||||
...(override?.env ?? {}),
|
||||
}
|
||||
: undefined,
|
||||
disallowedTools:
|
||||
base?.disallowedTools || override?.disallowedTools
|
||||
? [...(base?.disallowedTools ?? []), ...(override?.disallowedTools ?? [])]
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2145,6 +2145,12 @@ class ClaudeAgentSession implements AgentSession {
|
||||
if (this.claudeSessionId) {
|
||||
base.resume = this.claudeSessionId;
|
||||
}
|
||||
if (this.runtimeSettings?.disallowedTools?.length) {
|
||||
base.disallowedTools = [
|
||||
...(base.disallowedTools ?? []),
|
||||
...this.runtimeSettings.disallowedTools,
|
||||
];
|
||||
}
|
||||
return this.applyRuntimeSettings(base);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user