mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
feat: add unified update_claude_comment tool
- Add new update_claude_comment tool that automatically handles both issue and PR comments - Remove individual update_issue_comment and update_pull_request_comment tools - Pass CLAUDE_COMMENT_ID, GITHUB_EVENT_NAME, and IS_PR to MCP server environment - Use Octokit instead of raw fetch for better type safety and error handling - Simplify Claude's comment update workflow by removing need for owner/repo/commentId params - Update prompts and tests to use the new unified tool
This commit is contained in:
@@ -35,10 +35,7 @@ const BASE_ALLOWED_TOOLS = [
|
|||||||
];
|
];
|
||||||
const DISALLOWED_TOOLS = ["WebSearch", "WebFetch"];
|
const DISALLOWED_TOOLS = ["WebSearch", "WebFetch"];
|
||||||
|
|
||||||
export function buildAllowedToolsString(
|
export function buildAllowedToolsString(customAllowedTools?: string): string {
|
||||||
eventData: EventData,
|
|
||||||
customAllowedTools?: string,
|
|
||||||
): string {
|
|
||||||
let baseTools = [...BASE_ALLOWED_TOOLS];
|
let baseTools = [...BASE_ALLOWED_TOOLS];
|
||||||
|
|
||||||
let allAllowedTools = baseTools.join(",");
|
let allAllowedTools = baseTools.join(",");
|
||||||
@@ -639,7 +636,6 @@ export async function createPrompt(
|
|||||||
|
|
||||||
// Set allowed tools
|
// Set allowed tools
|
||||||
const allAllowedTools = buildAllowedToolsString(
|
const allAllowedTools = buildAllowedToolsString(
|
||||||
preparedContext.eventData,
|
|
||||||
preparedContext.allowedTools,
|
preparedContext.allowedTools,
|
||||||
);
|
);
|
||||||
const allDisallowedTools = buildDisallowedToolsString(
|
const allDisallowedTools = buildDisallowedToolsString(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { readFile } from "fs/promises";
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import { GITHUB_API_URL } from "../github/api/config";
|
import { GITHUB_API_URL } from "../github/api/config";
|
||||||
|
import { Octokit } from "@octokit/rest";
|
||||||
|
|
||||||
type GitHubRef = {
|
type GitHubRef = {
|
||||||
object: {
|
object: {
|
||||||
@@ -451,7 +452,6 @@ server.tool(
|
|||||||
const githubToken = process.env.GITHUB_TOKEN;
|
const githubToken = process.env.GITHUB_TOKEN;
|
||||||
const claudeCommentId = process.env.CLAUDE_COMMENT_ID;
|
const claudeCommentId = process.env.CLAUDE_COMMENT_ID;
|
||||||
const eventName = process.env.GITHUB_EVENT_NAME;
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
||||||
const isPR = process.env.IS_PR === "true";
|
|
||||||
|
|
||||||
if (!githubToken) {
|
if (!githubToken) {
|
||||||
throw new Error("GITHUB_TOKEN environment variable is required");
|
throw new Error("GITHUB_TOKEN environment variable is required");
|
||||||
@@ -464,73 +464,58 @@ server.tool(
|
|||||||
const repo = REPO_NAME;
|
const repo = REPO_NAME;
|
||||||
const commentId = parseInt(claudeCommentId, 10);
|
const commentId = parseInt(claudeCommentId, 10);
|
||||||
|
|
||||||
|
// Create Octokit instance
|
||||||
|
const octokit = new Octokit({
|
||||||
|
auth: githubToken,
|
||||||
|
});
|
||||||
|
|
||||||
// Determine if this is a PR review comment based on event type
|
// Determine if this is a PR review comment based on event type
|
||||||
const isPullRequestReviewComment =
|
const isPullRequestReviewComment =
|
||||||
eventName === "pull_request_review_comment";
|
eventName === "pull_request_review_comment";
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
let updateUrl: string;
|
|
||||||
|
|
||||||
if (isPullRequestReviewComment) {
|
try {
|
||||||
// Use the PR review comment API
|
if (isPullRequestReviewComment) {
|
||||||
updateUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/pulls/comments/${commentId}`;
|
// Try PR review comment API first
|
||||||
} else {
|
response = await octokit.rest.pulls.updateReviewComment({
|
||||||
// Use the issue comment API (works for both issues and PR general comments)
|
owner,
|
||||||
updateUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/issues/comments/${commentId}`;
|
repo,
|
||||||
}
|
comment_id: commentId,
|
||||||
|
body,
|
||||||
response = await fetch(updateUrl, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
Accept: "application/vnd.github+json",
|
|
||||||
Authorization: `Bearer ${githubToken}`,
|
|
||||||
"X-GitHub-Api-Version": "2022-11-28",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ body }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
|
|
||||||
// If PR review comment update fails, fall back to issue comment API
|
|
||||||
if (isPullRequestReviewComment && response.status === 404) {
|
|
||||||
const fallbackUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/issues/comments/${commentId}`;
|
|
||||||
response = await fetch(fallbackUrl, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
Accept: "application/vnd.github+json",
|
|
||||||
Authorization: `Bearer ${githubToken}`,
|
|
||||||
"X-GitHub-Api-Version": "2022-11-28",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ body }),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const fallbackErrorText = await response.text();
|
|
||||||
throw new Error(
|
|
||||||
`Failed to update comment: ${response.status} - ${fallbackErrorText}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
// Use issue comment API (works for both issues and PR general comments)
|
||||||
`Failed to update comment: ${response.status} - ${errorText}`,
|
response = await octokit.rest.issues.updateComment({
|
||||||
);
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: commentId,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
// If PR review comment update fails with 404, fall back to issue comment API
|
||||||
|
if (isPullRequestReviewComment && error.status === 404) {
|
||||||
|
response = await octokit.rest.issues.updateComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: commentId,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedComment = await response.json();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: JSON.stringify(
|
text: JSON.stringify(
|
||||||
{
|
{
|
||||||
id: updatedComment.id,
|
id: response.data.id,
|
||||||
html_url: updatedComment.html_url,
|
html_url: response.data.html_url,
|
||||||
updated_at: updatedComment.updated_at,
|
updated_at: response.data.updated_at,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
buildDisallowedToolsString,
|
buildDisallowedToolsString,
|
||||||
} from "../src/create-prompt";
|
} from "../src/create-prompt";
|
||||||
import type { PreparedContext } from "../src/create-prompt";
|
import type { PreparedContext } from "../src/create-prompt";
|
||||||
import type { EventData } from "../src/create-prompt/types";
|
|
||||||
|
|
||||||
describe("generatePrompt", () => {
|
describe("generatePrompt", () => {
|
||||||
const mockGitHubData = {
|
const mockGitHubData = {
|
||||||
@@ -619,15 +618,7 @@ describe("getEventTypeAndContext", () => {
|
|||||||
|
|
||||||
describe("buildAllowedToolsString", () => {
|
describe("buildAllowedToolsString", () => {
|
||||||
test("should return issue comment tool for regular events", () => {
|
test("should return issue comment tool for regular events", () => {
|
||||||
const mockEventData: EventData = {
|
const result = buildAllowedToolsString();
|
||||||
eventName: "issue_comment",
|
|
||||||
commentId: "123",
|
|
||||||
isPR: true,
|
|
||||||
prNumber: "456",
|
|
||||||
commentBody: "Test comment",
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = buildAllowedToolsString(mockEventData);
|
|
||||||
|
|
||||||
// The base tools should be in the result
|
// The base tools should be in the result
|
||||||
expect(result).toContain("Edit");
|
expect(result).toContain("Edit");
|
||||||
@@ -644,15 +635,7 @@ describe("buildAllowedToolsString", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should return PR comment tool for inline review comments", () => {
|
test("should return PR comment tool for inline review comments", () => {
|
||||||
const mockEventData: EventData = {
|
const result = buildAllowedToolsString();
|
||||||
eventName: "pull_request_review_comment",
|
|
||||||
isPR: true,
|
|
||||||
prNumber: "456",
|
|
||||||
commentBody: "Test review comment",
|
|
||||||
commentId: "789",
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = buildAllowedToolsString(mockEventData);
|
|
||||||
|
|
||||||
// The base tools should be in the result
|
// The base tools should be in the result
|
||||||
expect(result).toContain("Edit");
|
expect(result).toContain("Edit");
|
||||||
@@ -669,16 +652,8 @@ describe("buildAllowedToolsString", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should append custom tools when provided", () => {
|
test("should append custom tools when provided", () => {
|
||||||
const mockEventData: EventData = {
|
|
||||||
eventName: "issue_comment",
|
|
||||||
commentId: "123",
|
|
||||||
isPR: true,
|
|
||||||
prNumber: "456",
|
|
||||||
commentBody: "Test comment",
|
|
||||||
};
|
|
||||||
|
|
||||||
const customTools = "Tool1,Tool2,Tool3";
|
const customTools = "Tool1,Tool2,Tool3";
|
||||||
const result = buildAllowedToolsString(mockEventData, customTools);
|
const result = buildAllowedToolsString(customTools);
|
||||||
|
|
||||||
// Base tools should be present
|
// Base tools should be present
|
||||||
expect(result).toContain("Edit");
|
expect(result).toContain("Edit");
|
||||||
|
|||||||
Reference in New Issue
Block a user