File size: 3,253 Bytes
68d7816 | 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 | import picomatch from 'picomatch';
import { isMcpToolName, type ToolSource } from '#/tool/toolContract';
export interface ToolActivationPolicy {
readonly tools?: readonly string[];
readonly disallowedTools?: readonly string[];
}
export function isToolActive(
policy: ToolActivationPolicy,
name: string,
source: ToolSource = 'builtin',
): boolean {
if (policy.tools !== undefined) {
const allowed =
source !== 'mcp'
? policy.tools.includes(name)
: policy.tools
.filter((pattern) => isMcpToolName(pattern))
.some((pattern) => picomatch.isMatch(name, pattern));
if (!allowed) return false;
}
if (policy.disallowedTools === undefined) return true;
if (source !== 'mcp') return !policy.disallowedTools.includes(name);
return !policy.disallowedTools
.filter((pattern) => isMcpToolName(pattern))
.some((pattern) => picomatch.isMatch(name, pattern));
}
export interface GlobalToolsPolicy {
readonly enabled?: readonly string[];
readonly disabled?: readonly string[];
}
export interface ToolPolicyLayers {
readonly workspaceDisabledTools?: readonly string[];
readonly profile: ToolActivationPolicy;
readonly global?: GlobalToolsPolicy;
readonly sessionDisabledTools?: readonly string[];
}
export function isToolActiveComposed(
layers: ToolPolicyLayers,
name: string,
source: ToolSource = 'builtin',
): boolean {
return (
isToolActive({ disallowedTools: layers.workspaceDisabledTools }, name, source) &&
isToolActive(layers.profile, name, source) &&
isToolActive(
{
tools: layers.global?.enabled?.length ? layers.global.enabled : undefined,
disallowedTools: layers.global?.disabled,
},
name,
source,
) &&
isToolActive({ disallowedTools: layers.sessionDisabledTools }, name, source)
);
}
export function resolveActiveToolNames(
policy: ToolActivationPolicy,
): readonly string[] | undefined {
if (policy.tools === undefined) return undefined;
return policy.tools.filter((name) =>
isToolActive(policy, name, isMcpToolName(name) ? 'mcp' : 'builtin'),
);
}
export type InactiveToolPatternKind = 'wildcard-not-mcp' | 'incomplete-mcp-name' | 'unknown-tool';
export interface InactiveToolPattern {
readonly pattern: string;
readonly kind: InactiveToolPatternKind;
}
const GLOB_MAGIC = /[*?[\]{}]/;
export function literalToolNames(patterns: readonly string[]): string[] {
return patterns.filter((pattern) => !isMcpToolName(pattern) && !GLOB_MAGIC.test(pattern));
}
export function findInactiveToolPatterns(
patterns: readonly string[],
isKnownToolName?: (name: string) => boolean,
): InactiveToolPattern[] {
const issues: InactiveToolPattern[] = [];
for (const pattern of patterns) {
if (isMcpToolName(pattern)) {
if (!GLOB_MAGIC.test(pattern) && !pattern.slice('mcp__'.length).includes('__')) {
issues.push({ pattern, kind: 'incomplete-mcp-name' });
}
continue;
}
if (GLOB_MAGIC.test(pattern)) {
issues.push({ pattern, kind: 'wildcard-not-mcp' });
continue;
}
if (isKnownToolName !== undefined && !isKnownToolName(pattern)) {
issues.push({ pattern, kind: 'unknown-tool' });
}
}
return issues;
}
|