import * as core from "@actions/core"; import { readFile, writeFile, access } from "fs/promises"; import { dirname, join } from "path"; import { query } from "@anthropic-ai/claude-agent-sdk"; import type { SDKMessage, SDKResultMessage, SDKUserMessage, } from "@anthropic-ai/claude-agent-sdk"; import type { ParsedSdkOptions } from "./parse-sdk-options"; const EXECUTION_FILE = `${process.env.RUNNER_TEMP}/claude-execution-output.json`; /** Filename for the user request file, written by prompt generation */ const USER_REQUEST_FILENAME = "claude-user-request.txt"; /** * Check if a file exists */ async function fileExists(path: string): Promise { try { await access(path); return true; } catch { return false; } } /** * Creates a prompt configuration for the SDK. * If a user request file exists alongside the prompt file, returns a multi-block * SDKUserMessage that enables slash command processing in the CLI. * Otherwise, returns the prompt as a simple string. */ async function createPromptConfig( promptPath: string, showFullOutput: boolean, ): Promise> { const promptContent = await readFile(promptPath, "utf-8"); // Check for user request file in the same directory const userRequestPath = join(dirname(promptPath), USER_REQUEST_FILENAME); const hasUserRequest = await fileExists(userRequestPath); if (!hasUserRequest) { // No user request file - use simple string prompt return promptContent; } // User request file exists - create multi-block message const userRequest = await readFile(userRequestPath, "utf-8"); if (showFullOutput) { console.log("Using multi-block message with user request:", userRequest); } else { console.log("Using multi-block message with user request (content hidden)"); } // Create an async generator that yields a single multi-block message // The context/instructions go first, then the user's actual request last // This allows the CLI to detect and process slash commands in the user request async function* createMultiBlockMessage(): AsyncGenerator { yield { type: "user", session_id: "", message: { role: "user", content: [ { type: "text", text: promptContent }, // Instructions + GitHub context { type: "text", text: userRequest }, // User's request (may be a slash command) ], }, parent_tool_use_id: null, }; } return createMultiBlockMessage(); } /** * Sanitizes SDK output to match CLI sanitization behavior */ function sanitizeSdkOutput( message: SDKMessage, showFullOutput: boolean, ): string | null { if (showFullOutput) { return JSON.stringify(message, null, 2); } // System initialization - safe to show if (message.type === "system" && message.subtype === "init") { return JSON.stringify( { type: "system", subtype: "init", message: "Claude Code initialized", model: "model" in message ? message.model : "unknown", }, null, 2, ); } // Result messages - show sanitized summary if (message.type === "result") { const resultMsg = message as SDKResultMessage; return JSON.stringify( { type: "result", subtype: resultMsg.subtype, is_error: resultMsg.is_error, duration_ms: resultMsg.duration_ms, num_turns: resultMsg.num_turns, total_cost_usd: resultMsg.total_cost_usd, permission_denials: resultMsg.permission_denials, }, null, 2, ); } // Suppress other message types in non-full-output mode return null; } /** * Run Claude using the Agent SDK */ export async function runClaudeWithSdk( promptPath: string, { sdkOptions, showFullOutput, hasJsonSchema }: ParsedSdkOptions, ): Promise { // Create prompt configuration - may be a string or multi-block message const prompt = await createPromptConfig(promptPath, showFullOutput); if (!showFullOutput) { console.log( "Running Claude Code via SDK (full output hidden for security)...", ); console.log( "Rerun in debug mode or enable `show_full_output: true` in your workflow file for full output.", ); } console.log(`Running Claude with prompt from file: ${promptPath}`); // Log SDK options without env (which could contain sensitive data) const { env, ...optionsToLog } = sdkOptions; console.log("SDK options:", JSON.stringify(optionsToLog, null, 2)); const messages: SDKMessage[] = []; let resultMessage: SDKResultMessage | undefined; try { for await (const message of query({ prompt, options: sdkOptions })) { messages.push(message); const sanitized = sanitizeSdkOutput(message, showFullOutput); if (sanitized) { console.log(sanitized); } if (message.type === "result") { resultMessage = message as SDKResultMessage; } } } catch (error) { console.error("SDK execution error:", error); core.setOutput("conclusion", "failure"); process.exit(1); } // Write execution file try { await writeFile(EXECUTION_FILE, JSON.stringify(messages, null, 2)); console.log(`Log saved to ${EXECUTION_FILE}`); core.setOutput("execution_file", EXECUTION_FILE); } catch (error) { core.warning(`Failed to write execution file: ${error}`); } // Extract and set session_id from system.init message const initMessage = messages.find( (m) => m.type === "system" && "subtype" in m && m.subtype === "init", ); if (initMessage && "session_id" in initMessage && initMessage.session_id) { core.setOutput("session_id", initMessage.session_id); core.info(`Set session_id: ${initMessage.session_id}`); } if (!resultMessage) { core.setOutput("conclusion", "failure"); core.error("No result message received from Claude"); process.exit(1); } const isSuccess = resultMessage.subtype === "success"; core.setOutput("conclusion", isSuccess ? "success" : "failure"); // Handle structured output if (hasJsonSchema) { if ( isSuccess && "structured_output" in resultMessage && resultMessage.structured_output ) { const structuredOutputJson = JSON.stringify( resultMessage.structured_output, ); core.setOutput("structured_output", structuredOutputJson); core.info( `Set structured_output with ${Object.keys(resultMessage.structured_output as object).length} field(s)`, ); } else { core.setFailed( `--json-schema was provided but Claude did not return structured_output. Result subtype: ${resultMessage.subtype}`, ); core.setOutput("conclusion", "failure"); process.exit(1); } } if (!isSuccess) { if ("errors" in resultMessage && resultMessage.errors) { core.error(`Execution failed: ${resultMessage.errors.join(", ")}`); } process.exit(1); } }