Sync generated provider output

This commit is contained in:
github-actions[bot]
2026-06-20 09:24:47 +00:00
parent 41ff946121
commit 221064858e
39 changed files with 1781 additions and 78 deletions
+107 -1
View File
@@ -959,13 +959,114 @@ export function resolveTargetFiles(event, projectCwd) {
export function resolveHarness(env = {}, event = null) {
const explicit = env?.IMPECCABLE_HOOK_HARNESS;
if (explicit === 'cursor') return 'cursor';
if (explicit === 'github') return 'github';
if (explicit === 'claude' || explicit === 'codex') return 'claude';
// GitHub Copilot's postToolUse event uses camelCase `toolName`/`toolArgs` and
// has no `tool_name`/`tool_input`. That shape is the discriminator.
if (event && typeof event === 'object'
&& (typeof event.toolName === 'string' || event.toolArgs !== undefined)
&& event.tool_name === undefined && event.tool_input === undefined) {
return 'github';
}
if (typeof event?.conversation_id === 'string' && event.conversation_id) return 'cursor';
return 'claude';
}
// GitHub Copilot's postToolUse payload is
// { sessionId, timestamp, cwd, toolName, toolArgs, toolResult }
// mapped onto the internal `{ tool_name, tool_input, cwd, session_id }` shape.
// `toolArgs` shape depends on the tool: the `edit`/`create`/`view` tools send a
// JSON *string* (double-encoded) carrying the file under `path`, e.g.
// "{\"path\":\"/abs/app.tsx\",\"old_str\":\"...\",\"new_str\":\"...\"}",
// while `apply_patch` sends a raw OpenAI-format patch string (handled below in
// normalizeGitHubEvent). The detector reads the file from disk after the tool
// ran, so only the path (not the proposed content) is needed here.
export function parseGitHubToolArgs(toolArgs) {
if (toolArgs && typeof toolArgs === 'object' && !Array.isArray(toolArgs)) return toolArgs;
if (typeof toolArgs === 'string' && toolArgs.trim()) {
try {
const parsed = JSON.parse(toolArgs);
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
} catch {
return {};
}
}
return {};
}
// Copilot's `apply_patch` tool (used by interactive sessions and the cloud
// agent) sends a raw OpenAI-format patch string in toolArgs, not JSON:
// *** Begin Patch
// *** Add File: /abs/app.css
// +body { ... }
// *** End Patch
// The `view`/`edit`/`create` tools (seen in `copilot -p` runs) instead send a
// JSON string with the path under `path`. Both must map onto the internal shape.
const APPLY_PATCH_MARKER = /\*\*\* (?:Begin Patch|Add File:|Update File:|Delete File:)/;
function looksLikeApplyPatch(rawArgs) {
if (typeof rawArgs !== 'string' || !APPLY_PATCH_MARKER.test(rawArgs)) return false;
// Guard against an edit/create payload whose edited *content* happens to
// contain patch markers: that payload is a JSON object string, whereas a real
// apply_patch payload is a raw patch string that does not parse as JSON. Only
// treat non-JSON-object strings as apply_patch so edit events still get their
// `path` extracted.
try {
const parsed = JSON.parse(rawArgs);
if (parsed && typeof parsed === 'object') return false;
} catch { /* not JSON → genuine raw patch */ }
return true;
}
function applyPatchText(rawArgs) {
if (typeof rawArgs === 'string') {
if (APPLY_PATCH_MARKER.test(rawArgs)) return rawArgs;
// Defensive: a future Copilot build might JSON-wrap the patch.
const parsed = parseGitHubToolArgs(rawArgs);
return parsed.patch || parsed.input || parsed.command || '';
}
if (rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)) {
return rawArgs.patch || rawArgs.input || rawArgs.command || '';
}
return '';
}
function normalizeGitHubEvent(event, projectCwd) {
const cwd = event.cwd || envProjectDir(projectCwd) || projectCwd;
const sessionId = event.sessionId || event.session_id || 'unknown';
const toolName = event.toolName || event.tool_name || null;
const toolInput = event.tool_input && typeof event.tool_input === 'object' ? { ...event.tool_input } : {};
const rawArgs = event.toolArgs;
let normalizedToolName = toolName;
if (toolName === 'apply_patch' || looksLikeApplyPatch(rawArgs)) {
// resolveTargetFiles() reads the touched paths from tool_input.command when
// tool_name is 'apply_patch', so normalize the name even if a future build
// sends the patch under a different tool label.
const patch = applyPatchText(rawArgs);
if (patch) {
toolInput.command = patch;
normalizedToolName = 'apply_patch';
}
} else {
const args = parseGitHubToolArgs(rawArgs);
const filePath = args.path || args.file_path || args.filePath || args.target_file;
if (typeof filePath === 'string' && filePath) toolInput.file_path = filePath;
}
return {
...event,
cwd,
session_id: sessionId,
tool_name: normalizedToolName,
tool_input: toolInput,
};
}
export function normalizeHookEvent(event, projectCwd, harness = 'claude') {
if (!event || typeof event !== 'object' || harness !== 'cursor') return event;
if (!event || typeof event !== 'object') return event;
if (harness === 'github') return normalizeGitHubEvent(event, projectCwd);
if (harness !== 'cursor') return event;
const cwd = event.cwd
|| (Array.isArray(event.workspace_roots) && event.workspace_roots[0])
@@ -1520,6 +1621,11 @@ export function payload(text, eventName = 'PostToolUse', harness = 'claude') {
if (harness === 'cursor') {
return JSON.stringify({ additional_context: text });
}
// GitHub Copilot's postToolUse hook injects context via a top-level
// `additionalContext` string (alongside an optional `modifiedResult`).
if (harness === 'github') {
return JSON.stringify({ additionalContext: text });
}
return JSON.stringify({
hookSpecificOutput: { hookEventName: eventName, additionalContext: text },
});