refactor: centralize client tool availability (#13451)

This commit is contained in:
Bee
2026-08-20 21:34:38 -07:00
committed by GitHub
parent 05da55857a
commit 355ce11ae2
8 changed files with 68 additions and 4 deletions
+1
View File
@@ -13,6 +13,7 @@ export function getToolCatalog(
): ToolCatalogEntry[] {
const modelToolSettings = resolveModelToolSettings();
return getCoreBuiltinToolCatalog({
clientType: "cli",
disabledToolIds: resolveDisabledToolNames(),
enabledModelToolIds: new Set(
Object.entries(modelToolSettings)
@@ -75,9 +75,12 @@ export {
getCoreBuiltinToolCatalog,
getCoreDefaultEnabledToolIds,
getCoreHeadlessToolNames,
isCoreBuiltinToolAvailable,
isSkillsToolAvailable,
resolveCoreSelectedToolIds,
resolveToolClientType,
type ToolCatalogEntry,
type ToolClientType,
} from "./runtime";
// Schemas
export {
@@ -28,6 +28,16 @@ describe("builtin tool catalog", () => {
(entry) => entry.id === "tasks",
),
).toBe(false);
expect(
getCoreBuiltinToolCatalog({ mode: "act", clientType: "cli" }).some(
(entry) => entry.id === "tasks",
),
).toBe(false);
expect(
getCoreBuiltinToolCatalog({ mode: "act", clientType: "vscode" }).some(
(entry) => entry.id === "tasks",
),
).toBe(false);
});
it("marks teams enabled by default in act mode", () => {
@@ -14,10 +14,22 @@ export interface ToolCatalogEntry {
description: string;
defaultEnabled: boolean;
headlessToolNames: string[];
unavailableClientTypes?: readonly ToolClientType[];
}
export type ToolClientType = "cli" | "vscode";
export function resolveToolClientType(
source?: string,
): ToolClientType | undefined {
if (source === "vscode") return "vscode";
if (source === "cli" || source?.startsWith("cline-cli")) return "cli";
return undefined;
}
export interface BuiltinToolAvailabilityContext {
mode?: CoreAgentMode;
clientType?: ToolClientType;
providerId?: string;
modelId?: string;
enableSpawnAgent?: boolean;
@@ -82,6 +94,7 @@ const BASE_TOOL_CATALOG: readonly RuntimeToolCatalogEntry[] = [
description:
"Create and manage durable Todo items or explicitly requested one-time and recurring agent schedules.",
headlessToolNames: ["tasks"],
unavailableClientTypes: ["cli", "vscode"],
},
{
id: "spawn_agent",
@@ -173,6 +186,19 @@ function resolvePresetFlags(context: BuiltinToolAvailabilityContext): {
};
}
export function isCoreBuiltinToolAvailable(
toolName: string,
clientType?: ToolClientType,
): boolean {
if (!clientType) return true;
const entry = BASE_TOOL_CATALOG.find(
(candidate) =>
candidate.id === toolName ||
candidate.headlessToolNames.includes(toolName),
);
return !entry?.unavailableClientTypes?.includes(clientType);
}
function isEntryEnabledByDefault(
entryId: string,
context: BuiltinToolAvailabilityContext,
@@ -228,6 +254,7 @@ export function getCoreBuiltinToolCatalog(
): ToolCatalogEntry[] {
return BASE_TOOL_CATALOG.filter(
(entry) =>
isCoreBuiltinToolAvailable(entry.id, context.clientType) &&
(entry.id !== "tasks" || resolveContextMode(context.mode) !== "yolo") &&
(entry.id !== "web_search" ||
supportsModelTool(
@@ -8,7 +8,7 @@ import {
const tool = (name: string) => ({ name });
describe("selectSessionTools", () => {
it("excludes tasks in yolo mode only", () => {
it("excludes tasks in yolo mode and CLI/VS Code sessions", () => {
const tools = [tool("read_files"), tool("tasks")];
expect(selectSessionTools(tools, "act").map(({ name }) => name)).toEqual([
@@ -26,6 +26,15 @@ describe("selectSessionTools", () => {
expect(selectSessionTools(tools, "yolo").map(({ name }) => name)).toEqual([
"read_files",
]);
expect(
selectSessionTools(tools, "act", "cli").map(({ name }) => name),
).toEqual(["read_files"]);
expect(
selectSessionTools(tools, "act", "cline-cli-zen").map(({ name }) => name),
).toEqual(["read_files"]);
expect(
selectSessionTools(tools, "act", "vscode").map(({ name }) => name),
).toEqual(["read_files"]);
});
});
@@ -9,6 +9,10 @@ import {
parseRuntimeConfigExtensions,
ReasoningEffortSchema,
} from "@cline/shared";
import {
isCoreBuiltinToolAvailable,
resolveToolClientType,
} from "../../../extensions/tools/runtime";
import { normalizeConnectionUpdate } from "../../../runtime/config/connection-update";
import type {
RuntimeSessionConfig,
@@ -44,10 +48,14 @@ const CAPABILITY_OWNER_METADATA_KEY = "hubCapabilityOwnerClientId";
export function selectSessionTools<T extends { name: string }>(
tools: readonly T[],
mode: string,
source?: string,
): T[] {
return mode === "yolo"
? tools.filter((tool) => tool.name !== TASKS_TOOL_NAME)
: [...tools];
const clientType = resolveToolClientType(source);
return tools.filter(
(tool) =>
(mode !== "yolo" || tool.name !== TASKS_TOOL_NAME) &&
isCoreBuiltinToolAvailable(tool.name, clientType),
);
}
function readConnectionString(value: unknown): string | undefined {
@@ -321,6 +329,7 @@ export async function handleSessionCreate(
...(clientContributionRuntime.localRuntime.extraTools ?? []),
],
sessionMode,
typeof metadata.source === "string" ? metadata.source : undefined,
),
},
capabilities: {
@@ -602,6 +611,7 @@ export async function handleSessionRestore(
...(clientContributionRuntime.localRuntime.extraTools ?? []),
],
sessionMode,
typeof metadata.source === "string" ? metadata.source : undefined,
),
},
capabilities: {
+3
View File
@@ -931,18 +931,21 @@ export {
getCoreBuiltinToolCatalog,
getCoreDefaultEnabledToolIds,
getCoreHeadlessToolNames,
isCoreBuiltinToolAvailable,
isSkillsToolAvailable,
MAX_COMMAND_OUTPUT_CHARS,
PATCH_MARKERS,
PatchActionType,
type PatchFileChange,
resolveCoreSelectedToolIds,
resolveToolClientType,
type ShellExecutor,
type ShellExecutorOptions,
type StructuredCommandInput,
StructuredCommandInputSchema,
TEAM_TOOL_NAMES,
type ToolCatalogEntry,
type ToolClientType,
type ToolExecutors,
type ToolPolicyPresetName,
type ToolPresetName,
+1
View File
@@ -75,6 +75,7 @@ export {
export type {
BuiltinToolAvailabilityContext,
ToolCatalogEntry,
ToolClientType,
} from "./extensions/tools";
export {
getCoreAcpToolNames,