e6d34ee01b
* add OpenRouter proxy for managed model routing and OSS program proxy layer that mints ephemeral OpenRouter keys for users without BYOK API keys. two paths: pro plan users get their selected model proxied via OpenRouter; OSS program repos (hard-coded allowlist in config.ts) get free Claude Opus. BYOK env vars (PULLFROG_MODEL/OPENCODE_MODEL) always take precedence. frontend: OSS repos see a static "Opus (Free)" badge with the model dropdown disabled and no API key requirement. all models now carry openRouterResolve metadata for proxy target resolution. Made-with: Cursor * implement OSS program: proxy infrastructure for free model credits server-side OSS allowlist determines eligible repos. action mints ephemeral OpenRouter keys via OIDC-authenticated /api/proxy-token endpoint (idempotent on runId, $10 per-key safety limit). keys are disabled on workflow completion when no running refs remain. HWM-based usage sync tracks cumulative spend per account. schema: ProxyKey model, Account.usageUsd/activeKeyId, WorkflowRun.proxyKeyId action: OIDC credential stashing, resolveProxyModel uses server oss flag frontend: isOss flows from server page to components (no client allowlist) Made-with: Cursor * address PR review: repo cross-check, key retirement lifecycle, dead code removal - proxy-token: verify runId belongs to OIDC-authenticated repo via repo relation - add retireKey() shared primitive: disable in OpenRouter first, then mark disabled in DB - rotateKey: retire old active key after swap to prevent orphans - webhook: replace inline cleanupProxyKey with retireKey calls - syncAccountUsage: skip disabled keys - remove vestigial AccountPlan/plan field from action types - add disabled field to ProxyKey schema + migration Made-with: Cursor * replace deprecated opencode/mimo-v2-flash-free with mimo-v2-pro-free Made-with: Cursor * fix migration ordering: rename disabled migration to sort after table creation Made-with: Cursor * squash proxy key migrations into single migration Made-with: Cursor * add preview repo to OSS allowlist for testing Made-with: Cursor * populate OSS allowlist from oss-program-invitees.json Made-with: Cursor * format oss-program-invitees.json Made-with: Cursor * add installed public repos to OSS allowlist split ossRepos into three provenance-tracked lists: - internalRepos (pullfrog, colinhacks, RobinTail) - installedPublicRepos (external public non-fork repos with active installs) - invitees (from oss-program-invitees.json) also adds scripts/list-oss-candidates.ts to regenerate the installed list Made-with: Cursor * fix: resolve tokens before clearing OIDC env vars resolveTokens → acquireNewToken → isOIDCAvailable() checks ACTIONS_ID_TOKEN_REQUEST_URL/TOKEN env vars. The new OIDC stashing code was deleting them in restricted shell mode before resolveTokens ran, causing it to fall through to the GitHub App path which requires GITHUB_APP_ID/GITHUB_PRIVATE_KEY. Made-with: Cursor * derive proxy-token auth from OIDC claims, add ensureWorkflowRun upsert - proxy-token no longer requires body.runId; uses claims.run_id + claims.repository - shared ensureWorkflowRun upsert called from both webhook and proxy-token - workflow_run_requested handler now eagerly creates WorkflowRun records - eliminates race condition between webhook and action proxy-token call Made-with: Cursor * hardcode PULLFROG_ACCOUNT_ID, document preview debugging lessons GitHub node IDs are constant — no reason for this to be an env var. Removes the trailing-newline bug that caused P2025 errors. Adds wiki docs on workflow testing, Vercel env gotchas, and Neon preview branch discovery. Made-with: Cursor * fix: parse OpenRouter create-key response correctly the API returns `key` at the top level, not inside `data` Made-with: Cursor * onboarding cards, unlock OSS model selection, simplify console - add OnboardingCard component with two states: workflow install and model+test (dispatches "Tell me a joke" for test run) - replace PromptBox overlay gates with dedicated onboarding cards; PromptBox is now just the form, always enabled - use hasWorkflowRuns DB check to decide onboarding vs promptbox - unlock ModelSelector for OSS repos (was locked to Opus badge); resolve proxyModel from repo's selected model alias in run-context - rename "API key" row to "Model costs" with pure client-side states: OSS covered, auto-resolve, free model, BYOK with env var names - add "(Recommended)" badge to model aliases with recommended: true - remove OSS_MODEL_DISPLAY_NAME constant and secrets-fetching logic Made-with: Cursor * update stale xai model snapshot Made-with: Cursor * rename Permissions to Security, hide git push toggle, add shell isolation toggle with disabled state for public repos, remove opentoad agent name from logs Made-with: Cursor * chevron hover states, sidebar hooks/security entries Made-with: Cursor * address PR review feedback: rename recommended→preferred, fix dispatch orphan, update wiki docs - rename `recommended` to `preferred` in model alias registry to distinguish from the UI "Recommended" badge (which is hardcoded for opus + codex only) - cancel precreated WorkflowRun when workflow lookup fails in dispatch-workflow - replace run_sql/vercel env pull in wiki docs with neonctl + Prisma pattern - extend scripts/neon-branch.ts to output DATABASE_URL via neonctl - add Recommended badge to GPT Codex alongside Claude Opus in ModelSelector Made-with: Cursor
267 lines
7.9 KiB
TypeScript
267 lines
7.9 KiB
TypeScript
/**
|
|
* ⚠️ 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.
|
|
*/
|
|
|
|
// mcp name constant
|
|
export const ghPullfrogMcpName = "gh_pullfrog";
|
|
|
|
// model alias registry lives in models.ts — re-exported here for shared access
|
|
export type { ModelAlias, ModelProvider, ProviderConfig } from "./models.ts";
|
|
export {
|
|
getModelEnvVars,
|
|
getModelProvider,
|
|
getProviderDisplayName,
|
|
modelAliases,
|
|
parseModel,
|
|
providers,
|
|
resolveCliModel,
|
|
resolveModelSlug,
|
|
} from "./models.ts";
|
|
|
|
// tool permission types shared with server dispatch
|
|
export type ToolPermission = "disabled" | "enabled";
|
|
export type ShellPermission = "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;
|
|
/** when true, only address comments the triggerer approved with 👍 (vs all comments) */
|
|
approved_only?: boolean | undefined;
|
|
}
|
|
|
|
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 PullRequestSynchronizeEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_synchronize";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
title: string;
|
|
body: string | null;
|
|
branch: string;
|
|
/** SHA before the push -- used to compute incremental diff via `git diff before_sha...HEAD` */
|
|
before_sha: string;
|
|
}
|
|
|
|
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
|
|
| PullRequestSynchronizeEvent
|
|
| 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;
|
|
/** provider/model slug (e.g. "anthropic/claude-opus") */
|
|
model?: string | undefined;
|
|
/** the user's actual request (body if @pullfrog tagged) */
|
|
prompt: string;
|
|
/** github username of the human who triggered this workflow run */
|
|
triggerer?: string | undefined;
|
|
/** event-level instructions for this trigger type (flag-expanded server-side) */
|
|
eventInstructions?: string | undefined;
|
|
/** event data from webhook payload - discriminated union based on trigger field */
|
|
event: PayloadEvent;
|
|
/** 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;
|
|
}
|
|
|
|
// immutable payload type for agent execution
|
|
export type Payload = Readonly<WriteablePayload>;
|