/** * ⚠️ LIMITED IMPORTS - this file is imported by Next.js and must avoid pulling in backend code. * All shared constants, types, and data used by both the Next.js app and the action runtime live here. * Other files in action/ re-export from this file for backward compatibility. */ import { type } from "arktype"; // mcp name constant export const ghPullfrogMcpName = "gh_pullfrog"; export interface AgentManifest { displayName: string; /** empty array means accepts any *API_KEY* env var */ apiKeyNames: string[]; url: string; } // agent manifest - static metadata about available agents export const agentsManifest = { claude: { displayName: "Claude Code", apiKeyNames: ["ANTHROPIC_API_KEY"], url: "https://claude.com/claude-code", }, codex: { displayName: "Codex CLI", apiKeyNames: ["OPENAI_API_KEY"], url: "https://platform.openai.com/docs/guides/codex", }, cursor: { displayName: "Cursor CLI", apiKeyNames: ["CURSOR_API_KEY"], url: "https://cursor.com/", }, gemini: { displayName: "Gemini CLI", apiKeyNames: ["GOOGLE_API_KEY", "GEMINI_API_KEY"], url: "https://ai.google.dev/gemini-api/docs", }, opencode: { displayName: "OpenCode", apiKeyNames: [], url: "https://opencode.ai", }, } as const satisfies Record; // agent name type - union of agent slugs export type AgentName = keyof typeof agentsManifest; export const AgentName = type.enumerated(...(Object.keys(agentsManifest) as AgentName[])); export type AgentApiKeyName = (typeof agentsManifest)[AgentName]["apiKeyNames"][number]; // effort level type - controls model selection and thinking level // mini = fast/minimal, auto = balanced/default, max = maximum capability export const Effort = type.enumerated("mini", "auto", "max"); export type Effort = typeof Effort.infer; // tool permission types shared with server dispatch export type ToolPermission = "disabled" | "enabled"; export type BashPermission = "disabled" | "restricted" | "enabled"; export type PushPermission = "disabled" | "restricted" | "enabled"; // workflow yml permissions for GITHUB_TOKEN export type WorkflowPermissionValue = "read" | "write" | "none"; export type WorkflowIdTokenPermissionValue = "write" | "none"; export interface WorkflowPermissions { actions?: WorkflowPermissionValue; attestations?: WorkflowPermissionValue; checks?: WorkflowPermissionValue; contents?: WorkflowPermissionValue; deployments?: WorkflowPermissionValue; discussions?: WorkflowPermissionValue; "id-token"?: WorkflowIdTokenPermissionValue; issues?: WorkflowPermissionValue; models?: WorkflowPermissionValue; packages?: WorkflowPermissionValue; pages?: WorkflowPermissionValue; "pull-requests"?: WorkflowPermissionValue; "repository-projects"?: WorkflowPermissionValue; "security-events"?: WorkflowPermissionValue; statuses?: WorkflowPermissionValue; } // permission level for the author who triggered the event // matches GitHub's permission levels: admin > write > maintain > triage > read > none export type AuthorPermission = "admin" | "maintain" | "write" | "triage" | "read" | "none"; // base interface for common payload event fields interface BasePayloadEvent { issue_number?: number; is_pr?: boolean; branch?: string; /** title of the issue/PR (or contextual title for comments) */ title?: string; /** primary content for this trigger (issue body, PR body, comment body, review body, etc.) */ body?: string | null; comment_id?: number; review_id?: number; review_state?: string; thread?: any; pull_request?: any; check_suite?: { id: number; head_sha: string; head_branch: string | null; status: string | null; conclusion: string | null; url: string; }; comment_ids?: number[] | "all"; /** permission level of the user who triggered this event */ authorPermission?: AuthorPermission; /** when true, runs silently without progress comments (e.g., auto-labeling) */ silent?: boolean; [key: string]: any; } interface PullRequestOpenedEvent extends BasePayloadEvent { trigger: "pull_request_opened"; issue_number: number; is_pr: true; title: string; body: string | null; branch: string; } interface PullRequestReadyForReviewEvent extends BasePayloadEvent { trigger: "pull_request_ready_for_review"; issue_number: number; is_pr: true; title: string; body: string | null; branch: string; } interface PullRequestReviewRequestedEvent extends BasePayloadEvent { trigger: "pull_request_review_requested"; issue_number: number; is_pr: true; title: string; body: string | null; branch: string; } interface PullRequestReviewSubmittedEvent extends BasePayloadEvent { trigger: "pull_request_review_submitted"; issue_number: number; is_pr: true; review_id: number; /** review body is the primary content */ body: string | null; review_state: string; branch: string; } interface PullRequestReviewCommentCreatedEvent extends BasePayloadEvent { trigger: "pull_request_review_comment_created"; issue_number: number; is_pr: true; title: string; comment_id: number; /** comment body is the primary content (null if already in prompt) */ body: string | null; thread?: any; branch: string; } interface IssuesOpenedEvent extends BasePayloadEvent { trigger: "issues_opened"; issue_number: number; title: string; body: string | null; } interface IssuesAssignedEvent extends BasePayloadEvent { trigger: "issues_assigned"; issue_number: number; title: string; body: string | null; } interface IssuesLabeledEvent extends BasePayloadEvent { trigger: "issues_labeled"; issue_number: number; title: string; body: string | null; } interface IssueCommentCreatedEvent extends BasePayloadEvent { trigger: "issue_comment_created"; comment_id: number; /** distinguishes this from PR review comments (which use pull_request_review_comment_created) */ comment_type: "issue"; /** comment body is the primary content (null if already in prompt) */ body: string | null; issue_number: number; // PR-specific fields (only present when is_pr is true) is_pr?: true; branch?: string; title?: string; } interface CheckSuiteCompletedEvent extends BasePayloadEvent { trigger: "check_suite_completed"; issue_number: number; is_pr: true; title: string; body: string | null; pull_request: any; branch: string; check_suite: { id: number; head_sha: string; head_branch: string | null; status: string | null; conclusion: string | null; url: string; }; } interface WorkflowDispatchEvent extends BasePayloadEvent { trigger: "workflow_dispatch"; } interface FixReviewEvent extends BasePayloadEvent { trigger: "fix_review"; issue_number: number; is_pr: true; review_id: number; /** username of the person who triggered this action - use with get_review_comments approved_by */ triggerer: string; } interface ImplementPlanEvent extends BasePayloadEvent { trigger: "implement_plan"; issue_number: number; plan_comment_id: number; /** plan content is the primary content (null if already in prompt) */ body: string | null; } interface UnknownEvent extends BasePayloadEvent { trigger: "unknown"; } // discriminated union for payload event based on trigger // note: all events use issue_number for consistency (PRs are issues in GitHub's API) export type PayloadEvent = | PullRequestOpenedEvent | PullRequestReadyForReviewEvent | PullRequestReviewRequestedEvent | PullRequestReviewSubmittedEvent | PullRequestReviewCommentCreatedEvent | IssuesOpenedEvent | IssuesAssignedEvent | IssuesLabeledEvent | IssueCommentCreatedEvent | CheckSuiteCompletedEvent | WorkflowDispatchEvent | FixReviewEvent | ImplementPlanEvent | UnknownEvent; // writeable payload type for building payloads export interface WriteablePayload { "~pullfrog": true; /** semantic version of the payload to ensure compatibility */ version: string; /** agent slug identifier (e.g., "claude", "codex", "gemini") */ agent?: AgentName | undefined; /** the user's actual request (body if @pullfrog tagged) */ prompt: string; /** event-level instructions for this trigger type (flag-expanded server-side) */ eventInstructions?: string | undefined; /** repo-level instructions (flag-expanded server-side) */ repoInstructions?: string | undefined; /** event data from webhook payload - discriminated union based on trigger field */ event: PayloadEvent; /** effort level for model selection (mini, auto, max) - defaults to "auto" */ effort?: Effort | undefined; /** timeout for agent run (e.g., "10m", "1h30m") - defaults to "1h" */ timeout?: string | undefined; /** working directory for the agent */ cwd?: string | undefined; /** pre-created progress comment ID for updating status */ progressCommentId?: string | undefined; /** whether debug mode is enabled (LOG_LEVEL=debug) */ debug?: boolean | undefined; } // immutable payload type for agent execution export type Payload = Readonly;