mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-17 13:30:53 -07:00
135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import type { PolicyRule, PolicyDecision } from '@google/gemini-cli-core';
|
|
|
|
/**
|
|
* Represents a single item in the policies dialog list.
|
|
*/
|
|
export interface PolicyListItem {
|
|
/** Unique key for React rendering */
|
|
key: string;
|
|
/** The original policy rule */
|
|
rule: PolicyRule;
|
|
/** Uppercased decision string */
|
|
decision: string;
|
|
/** Resolved display name (e.g. "Shell") or fallback to internal name */
|
|
toolDisplayName: string;
|
|
/** Formatted constraint string for parenthetical display, or undefined */
|
|
constraint: string | undefined;
|
|
/** Formatted priority string */
|
|
priority: string;
|
|
/** rule.source ?? '' */
|
|
source: string;
|
|
/** Concatenated searchable fields */
|
|
searchText: string;
|
|
}
|
|
|
|
/**
|
|
* Un-escapes a regex string that was escaped by `escapeRegex()` from
|
|
* packages/core/src/policy/utils.ts.
|
|
*
|
|
* escapeRegex escapes: [-[\]{}()*+?.,\\^$|#\s"]
|
|
* by prefixing each with a backslash.
|
|
*/
|
|
function unescapeRegex(escaped: string): string {
|
|
return escaped.replace(/\\(.)/g, '$1');
|
|
}
|
|
|
|
/**
|
|
* Formats an argsPattern regex into a human-readable constraint string.
|
|
*
|
|
* The policy engine compiles TOML rule fields (commandPrefix, commandRegex,
|
|
* argsPattern) into RegExp objects via buildArgsPatterns() in
|
|
* packages/core/src/policy/utils.ts. This function reverses that compilation
|
|
* back into readable text for display.
|
|
*
|
|
* Pattern formats (checked in order):
|
|
*
|
|
* 1. commandPrefix — `"command":"<escaped>(?:[\s"]|\\")` → `<prefix>*`
|
|
* 2. commandRegex — starts with `"command":"` → raw regex after the prefix
|
|
* 3. file_path — contains `"file_path":"<path>"` → `path: <path>`
|
|
* 4. Fallback — truncated raw regex source
|
|
*
|
|
* Returns undefined if there is no constraint (tool-only or wildcard rules).
|
|
*/
|
|
export function formatArgsPattern(
|
|
argsPattern: RegExp | undefined,
|
|
): string | undefined {
|
|
if (!argsPattern) {
|
|
return undefined;
|
|
}
|
|
|
|
const source = argsPattern.source;
|
|
|
|
// 1. commandPrefix: "command":"<escaped-prefix>(?:[\s"]|\\")"
|
|
// The lookahead ensures the prefix is word-bounded in JSON.
|
|
const prefixMatch = source.match(/^"command":"(.+?)\(\?:\[\\s"\]\|\\\\"?\)$/);
|
|
if (prefixMatch) {
|
|
return unescapeRegex(prefixMatch[1]) + '*';
|
|
}
|
|
|
|
// 2. commandRegex: starts with "command":"
|
|
if (source.startsWith('"command":"')) {
|
|
const regex = source.slice('"command":"'.length);
|
|
return regex;
|
|
}
|
|
|
|
// 3. file_path pattern
|
|
if (source.includes('"file_path"')) {
|
|
const pathMatch = source.match(/"file_path":"(.+?)"/);
|
|
if (pathMatch) {
|
|
return `path: ${pathMatch[1]}`;
|
|
}
|
|
return 'path: ...';
|
|
}
|
|
|
|
// 4. Fallback: truncated raw regex
|
|
const maxLen = 40;
|
|
if (source.length > maxLen) {
|
|
return source.substring(0, maxLen) + '...';
|
|
}
|
|
return source;
|
|
}
|
|
|
|
/**
|
|
* Builds a list of PolicyListItems from rules, filtered by decision and sorted
|
|
* by priority descending.
|
|
*/
|
|
export function buildPolicyListItems(
|
|
rules: readonly PolicyRule[],
|
|
toolDisplayNames: Map<string, string>,
|
|
decision: PolicyDecision,
|
|
): PolicyListItem[] {
|
|
return rules
|
|
.map((rule, index) => ({ rule, index }))
|
|
.filter(({ rule }) => rule.decision === decision)
|
|
.sort((a, b) => (b.rule.priority ?? 0) - (a.rule.priority ?? 0))
|
|
.map(({ rule, index }) => {
|
|
const toolDisplayName = rule.toolName
|
|
? (toolDisplayNames.get(rule.toolName) ?? rule.toolName)
|
|
: 'all tools';
|
|
const constraint = formatArgsPattern(rule.argsPattern);
|
|
const priority = String(rule.priority ?? 0);
|
|
const source = rule.source ?? '';
|
|
|
|
const searchText = [toolDisplayName, rule.toolName, constraint, source]
|
|
.filter(Boolean)
|
|
.join(' ');
|
|
|
|
return {
|
|
key: `policy-${index}`,
|
|
rule,
|
|
decision: rule.decision.toUpperCase(),
|
|
toolDisplayName,
|
|
constraint,
|
|
priority,
|
|
source,
|
|
searchText,
|
|
};
|
|
});
|
|
}
|