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;
}