feat: add Gemini CLI, OpenCode, and Windsurf IDE integrations
Gemini CLI: platform adapter mapping 6 of 11 hooks, settings.json deep-merge installer, GEMINI.md context injection. OpenCode: plugin with tool.execute.after interceptor, bus events for session lifecycle, claude_mem_search custom tool, AGENTS.md context. Windsurf: platform adapter for tool_info envelope format, hooks.json installer for 5 post-action hooks, .windsurf/rules context injection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import type { PlatformAdapter, NormalizedHookInput, HookResult } from '../types.js';
|
||||
|
||||
/**
|
||||
* Gemini CLI Platform Adapter
|
||||
*
|
||||
* Normalizes Gemini CLI's hook JSON to NormalizedHookInput.
|
||||
* Gemini CLI has 11 lifecycle hooks; we map 6 of them:
|
||||
* SessionStart → session-init
|
||||
* BeforeAgent → user-message (captures prompt)
|
||||
* AfterAgent → observation (full response)
|
||||
* AfterTool → observation (tool result)
|
||||
* PreCompress → summarize
|
||||
* SessionEnd → session-complete
|
||||
*
|
||||
* Base fields (all events): session_id, transcript_path, cwd, hook_event_name, timestamp
|
||||
*
|
||||
* Output format: { continue, stopReason, suppressOutput, systemMessage, decision, reason }
|
||||
* Advisory hooks (SessionStart, SessionEnd, PreCompress) ignore `continue` and `decision`.
|
||||
*/
|
||||
export const geminiCliAdapter: PlatformAdapter = {
|
||||
normalizeInput(raw) {
|
||||
const r = (raw ?? {}) as any;
|
||||
|
||||
// Use GEMINI_CWD, GEMINI_PROJECT_DIR, or the JSON cwd field
|
||||
const cwd = r.cwd
|
||||
?? process.env.GEMINI_CWD
|
||||
?? process.env.GEMINI_PROJECT_DIR
|
||||
?? process.env.CLAUDE_PROJECT_DIR
|
||||
?? process.cwd();
|
||||
|
||||
const sessionId = r.session_id
|
||||
?? process.env.GEMINI_SESSION_ID
|
||||
?? undefined;
|
||||
|
||||
// Map event-specific fields into normalized shape
|
||||
// AfterTool provides tool_name, tool_input, tool_response
|
||||
// BeforeAgent/AfterAgent provide prompt (and prompt_response for AfterAgent)
|
||||
const hookEventName: string | undefined = r.hook_event_name;
|
||||
|
||||
// For AfterAgent, treat the full response as an observation by packing it
|
||||
// into toolResponse so the observation handler can process it
|
||||
let toolName: string | undefined = r.tool_name;
|
||||
let toolInput: unknown = r.tool_input;
|
||||
let toolResponse: unknown = r.tool_response;
|
||||
|
||||
if (hookEventName === 'AfterAgent' && r.prompt_response) {
|
||||
toolName = toolName ?? 'GeminiAgent';
|
||||
toolInput = toolInput ?? { prompt: r.prompt };
|
||||
toolResponse = toolResponse ?? { response: r.prompt_response };
|
||||
}
|
||||
|
||||
return {
|
||||
sessionId,
|
||||
cwd,
|
||||
prompt: r.prompt,
|
||||
toolName,
|
||||
toolInput,
|
||||
toolResponse,
|
||||
transcriptPath: r.transcript_path,
|
||||
};
|
||||
},
|
||||
|
||||
formatOutput(result) {
|
||||
// Gemini CLI expects: { continue, stopReason, suppressOutput, systemMessage, decision, reason }
|
||||
const output: Record<string, unknown> = {};
|
||||
|
||||
// Always include continue — controls whether the agent proceeds
|
||||
output.continue = result.continue ?? true;
|
||||
|
||||
if (result.suppressOutput !== undefined) {
|
||||
output.suppressOutput = result.suppressOutput;
|
||||
}
|
||||
|
||||
if (result.systemMessage) {
|
||||
output.systemMessage = result.systemMessage;
|
||||
}
|
||||
|
||||
// hookSpecificOutput carries context injection data
|
||||
if (result.hookSpecificOutput) {
|
||||
output.systemMessage = result.hookSpecificOutput.additionalContext || output.systemMessage;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user