-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: send user request as separate content block for slash command support #785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,73 @@ | ||
| import * as core from "@actions/core"; | ||
| import { readFile, writeFile } from "fs/promises"; | ||
| 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`; | ||
|
|
||
| /** | ||
| * Check if a file exists | ||
| */ | ||
| async function fileExists(path: string): Promise<boolean> { | ||
| 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, | ||
| ): Promise<string | AsyncIterable<SDKUserMessage>> { | ||
| const promptContent = await readFile(promptPath, "utf-8"); | ||
|
|
||
| // Check for user request file in the same directory | ||
| const userRequestPath = join(dirname(promptPath), "claude-user-request.txt"); | ||
| 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"); | ||
| console.log("Using multi-block message with user request:", userRequest); | ||
|
||
|
|
||
| // 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<SDKUserMessage> { | ||
| 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(); | ||
| } | ||
ashwin-ant marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Sanitizes SDK output to match CLI sanitization behavior | ||
| */ | ||
|
|
@@ -63,7 +122,8 @@ export async function runClaudeWithSdk( | |
| promptPath: string, | ||
| { sdkOptions, showFullOutput, hasJsonSchema }: ParsedSdkOptions, | ||
| ): Promise<void> { | ||
| const prompt = await readFile(promptPath, "utf-8"); | ||
| // Create prompt configuration - may be a string or multi-block message | ||
| const prompt = await createPromptConfig(promptPath); | ||
|
|
||
| if (!showFullOutput) { | ||
| console.log( | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| /** | ||
| * Extracts the user's request from a trigger comment. | ||
| * | ||
| * Given a comment like "@claude /review-pr please check the auth module", | ||
| * this extracts "/review-pr please check the auth module". | ||
| * | ||
| * @param commentBody - The full comment body containing the trigger phrase | ||
| * @param triggerPhrase - The trigger phrase (e.g., "@claude") | ||
| * @returns The user's request (text after the trigger phrase), or null if not found | ||
| */ | ||
| export function extractUserRequest( | ||
| commentBody: string | undefined, | ||
| triggerPhrase: string, | ||
| ): string | null { | ||
| if (!commentBody) { | ||
| return null; | ||
| } | ||
|
|
||
| // Escape special regex characters in the trigger phrase | ||
| const escapedTrigger = triggerPhrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
|
|
||
| // Match the trigger phrase followed by optional whitespace and capture the rest | ||
| // The trigger phrase can appear anywhere in the comment | ||
| const regex = new RegExp(`${escapedTrigger}\\s*(.*)`, "is"); | ||
ashwin-ant marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const match = commentBody.match(regex); | ||
|
|
||
| if (match && match[1]) { | ||
| // Trim and return the captured text after the trigger phrase | ||
| const request = match[1].trim(); | ||
| return request || null; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Extracts the user's request from various GitHub event types. | ||
| * | ||
| * For comment events: extracts from the comment body | ||
| * For issue/PR events: extracts from the body or title | ||
| * | ||
| * @param eventData - The parsed event data containing comment/body info | ||
| * @param triggerPhrase - The trigger phrase (e.g., "@claude") | ||
| * @returns The user's request, or a default message if not found | ||
| */ | ||
| export function extractUserRequestFromEvent( | ||
| eventData: { | ||
| eventName: string; | ||
| commentBody?: string; | ||
| issueBody?: string; | ||
| prBody?: string; | ||
| }, | ||
| triggerPhrase: string, | ||
| ): string { | ||
| // For comment events, extract from comment body | ||
| if ( | ||
| eventData.eventName === "issue_comment" || | ||
| eventData.eventName === "pull_request_review_comment" || | ||
| eventData.eventName === "pull_request_review" | ||
| ) { | ||
| const request = extractUserRequest(eventData.commentBody, triggerPhrase); | ||
| if (request) { | ||
| return request; | ||
| } | ||
| } | ||
|
|
||
| // For issue events, try extracting from issue body | ||
| if (eventData.eventName === "issues" && eventData.issueBody) { | ||
| const request = extractUserRequest(eventData.issueBody, triggerPhrase); | ||
| if (request) { | ||
| return request; | ||
| } | ||
| } | ||
|
|
||
| // For PR events, try extracting from PR body | ||
| if ( | ||
| (eventData.eventName === "pull_request" || | ||
| eventData.eventName === "pull_request_target") && | ||
| eventData.prBody | ||
| ) { | ||
| const request = extractUserRequest(eventData.prBody, triggerPhrase); | ||
| if (request) { | ||
| return request; | ||
| } | ||
| } | ||
|
|
||
| // Default: return a generic request to analyze the context | ||
| return "Please analyze the context and help with this request."; | ||
| } | ||
ashwin-ant marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Quality: Magic string should be a constant
The filename
"claude-user-request.txt"is hardcoded here and insrc/create-prompt/index.ts:945. Define a shared constant: