Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/common/promptSyntax/hookCompatibility.ts
5243 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { URI } from '../../../../../base/common/uri.js';
7
import { basename, dirname } from '../../../../../base/common/path.js';
8
import { HookType, IHookCommand, toHookType, resolveHookCommand } from './hookSchema.js';
9
import { parseClaudeHooks } from './hookClaudeCompat.js';
10
import { resolveCopilotCliHookType } from './hookCopilotCliCompat.js';
11
12
/**
13
* Represents a hook source with its original and normalized properties.
14
* Used to display hooks from different formats in a unified view.
15
*/
16
export interface IResolvedHookEntry {
17
/** The normalized hook type (our canonical HookType enum) */
18
readonly hookType: HookType;
19
/** The original hook type ID as it appears in the source file */
20
readonly originalHookTypeId: string;
21
/** The source format this hook came from */
22
readonly sourceFormat: HookSourceFormat;
23
/** The resolved hook command */
24
readonly command: IHookCommand;
25
/** The index of this hook in its array (for editing) */
26
readonly index: number;
27
}
28
29
/**
30
* Supported hook file formats.
31
*/
32
export enum HookSourceFormat {
33
/** GitHub Copilot hooks .json format */
34
Copilot = 'copilot',
35
/** Claude settings.json / settings.local.json format */
36
Claude = 'claude',
37
}
38
39
/**
40
* Determines the hook source format based on the file URI.
41
*/
42
export function getHookSourceFormat(fileUri: URI): HookSourceFormat {
43
const filename = basename(fileUri.path).toLowerCase();
44
const dir = dirname(fileUri.path);
45
46
// Claude format: .claude/settings.json or .claude/settings.local.json
47
if ((filename === 'settings.json' || filename === 'settings.local.json') && dir.endsWith('.claude')) {
48
return HookSourceFormat.Claude;
49
}
50
51
// Default to Copilot format
52
return HookSourceFormat.Copilot;
53
}
54
55
/**
56
* Checks if a file is read-only based on its source format.
57
* Claude settings files should be read-only from our perspective since they have a different format.
58
*/
59
export function isReadOnlyHookSource(format: HookSourceFormat): boolean {
60
return format === HookSourceFormat.Claude;
61
}
62
63
/**
64
* Parses hooks from a Copilot hooks .json file (our native format).
65
*/
66
export function parseCopilotHooks(
67
json: unknown,
68
workspaceRootUri: URI | undefined,
69
userHome: string
70
): Map<HookType, { hooks: IHookCommand[]; originalId: string }> {
71
const result = new Map<HookType, { hooks: IHookCommand[]; originalId: string }>();
72
73
if (!json || typeof json !== 'object') {
74
return result;
75
}
76
77
const root = json as Record<string, unknown>;
78
79
const hooks = root.hooks;
80
if (!hooks || typeof hooks !== 'object') {
81
return result;
82
}
83
84
const hooksObj = hooks as Record<string, unknown>;
85
86
for (const originalId of Object.keys(hooksObj)) {
87
const hookType = resolveCopilotCliHookType(originalId) ?? toHookType(originalId);
88
if (!hookType) {
89
continue;
90
}
91
92
const hookArray = hooksObj[originalId];
93
if (!Array.isArray(hookArray)) {
94
continue;
95
}
96
97
const commands: IHookCommand[] = [];
98
99
for (const item of hookArray) {
100
const resolved = resolveHookCommand(item as Record<string, unknown>, workspaceRootUri, userHome);
101
if (resolved) {
102
commands.push(resolved);
103
}
104
}
105
106
if (commands.length > 0) {
107
result.set(hookType, { hooks: commands, originalId });
108
}
109
}
110
111
return result;
112
}
113
114
/**
115
* Parses hooks from any supported format, auto-detecting the format from the file URI.
116
*/
117
export function parseHooksFromFile(
118
fileUri: URI,
119
json: unknown,
120
workspaceRootUri: URI | undefined,
121
userHome: string
122
): { format: HookSourceFormat; hooks: Map<HookType, { hooks: IHookCommand[]; originalId: string }> } {
123
const format = getHookSourceFormat(fileUri);
124
125
let hooks: Map<HookType, { hooks: IHookCommand[]; originalId: string }>;
126
127
switch (format) {
128
case HookSourceFormat.Claude:
129
hooks = parseClaudeHooks(json, workspaceRootUri, userHome);
130
break;
131
case HookSourceFormat.Copilot:
132
default:
133
hooks = parseCopilotHooks(json, workspaceRootUri, userHome);
134
break;
135
}
136
137
return { format, hooks };
138
}
139
140
/**
141
* Gets a human-readable label for a hook source format.
142
*/
143
export function getHookSourceFormatLabel(format: HookSourceFormat): string {
144
switch (format) {
145
case HookSourceFormat.Claude:
146
return 'Claude';
147
case HookSourceFormat.Copilot:
148
return 'GitHub Copilot';
149
}
150
}
151
152