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
|
- `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)
|
- 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
|
- 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`
|
- 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)
|
- 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**
|
- API keys must be created in the **Singapore region**
|
||||||
- The coding plan is for personal use only in interactive coding tools
|
- 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)
|
- 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 |
|
| `command` | `string[]` | Yes (ACP only) | Command to spawn the agent process |
|
||||||
| `env` | `Record<string, string>` | No | Environment variables to set for 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) |
|
| `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`) |
|
| `enabled` | `boolean` | No | Set to `false` to hide the provider (default: `true`) |
|
||||||
| `order` | `number` | No | Sort order in the provider list |
|
| `order` | `number` | No | Sort order in the provider list |
|
||||||
|
|
||||||
@@ -460,6 +463,29 @@ Each entry in the `models` array:
|
|||||||
| `description` | `string` | No | Short description |
|
| `description` | `string` | No | Short description |
|
||||||
| `isDefault` | `boolean` | No | Mark as the default thinking option |
|
| `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
|
### Valid `extends` values
|
||||||
|
|
||||||
Built-in providers: `claude`, `codex`, `copilot`, `opencode`, `pi`
|
Built-in providers: `claude`, `codex`, `copilot`, `opencode`, `pi`
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const ProviderRuntimeSettingsSchema = z
|
|||||||
.object({
|
.object({
|
||||||
command: ProviderCommandSchema.optional(),
|
command: ProviderCommandSchema.optional(),
|
||||||
env: z.record(z.string()).optional(),
|
env: z.record(z.string()).optional(),
|
||||||
|
disallowedTools: z.array(z.string()).optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ export const ProviderOverrideSchema = z
|
|||||||
command: z.array(z.string().min(1)).min(1).optional(),
|
command: z.array(z.string().min(1)).min(1).optional(),
|
||||||
env: z.record(z.string()).optional(),
|
env: z.record(z.string()).optional(),
|
||||||
models: z.array(ProviderProfileModelSchema).optional(),
|
models: z.array(ProviderProfileModelSchema).optional(),
|
||||||
|
disallowedTools: z.array(z.string()).optional(),
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
order: z.number().optional(),
|
order: z.number().optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -473,6 +473,52 @@ describe("buildProviderRegistry", () => {
|
|||||||
expect(mockState.isCommandAvailable).toHaveBeenCalledWith("claude");
|
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", () => {
|
test("extension inherits base override — override claude command, zai extends claude gets overridden command", () => {
|
||||||
buildProviderRegistry(logger, {
|
buildProviderRegistry(logger, {
|
||||||
providerOverrides: {
|
providerOverrides: {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ function getProviderClientFactory(provider: string): ProviderClientFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toRuntimeSettings(override?: ProviderOverride): ProviderRuntimeSettings | undefined {
|
function toRuntimeSettings(override?: ProviderOverride): ProviderRuntimeSettings | undefined {
|
||||||
if (!override?.command && !override?.env) {
|
if (!override?.command && !override?.env && !override?.disallowedTools) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +102,7 @@ function toRuntimeSettings(override?: ProviderOverride): ProviderRuntimeSettings
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
env: override.env,
|
env: override.env,
|
||||||
|
disallowedTools: override.disallowedTools,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +123,10 @@ function mergeRuntimeSettings(
|
|||||||
...(override?.env ?? {}),
|
...(override?.env ?? {}),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
disallowedTools:
|
||||||
|
base?.disallowedTools || override?.disallowedTools
|
||||||
|
? [...(base?.disallowedTools ?? []), ...(override?.disallowedTools ?? [])]
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2145,6 +2145,12 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
if (this.claudeSessionId) {
|
if (this.claudeSessionId) {
|
||||||
base.resume = this.claudeSessionId;
|
base.resume = this.claudeSessionId;
|
||||||
}
|
}
|
||||||
|
if (this.runtimeSettings?.disallowedTools?.length) {
|
||||||
|
base.disallowedTools = [
|
||||||
|
...(base.disallowedTools ?? []),
|
||||||
|
...this.runtimeSettings.disallowedTools,
|
||||||
|
];
|
||||||
|
}
|
||||||
return this.applyRuntimeSettings(base);
|
return this.applyRuntimeSettings(base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user