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:
Mohamed Boudra
2026-04-16 09:42:54 +07:00
parent 0b3c29b2ec
commit 483312e1dc
5 changed files with 86 additions and 1 deletions
@@ -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);
}