configuration.ts
Rama main · réplica de lectura del repositorio original.
import { z } from "zod";
export type ConfigScope = "org" | "repo";
export type ConfigValue = string | number | boolean | null;
export interface ConfigField {
key: string;
field: string;
kind:
| "text"
| "model"
| "effort"
| "boolean"
| "toggle"
| "permission"
| "enum"
| "usd"
| "status"
| "prompt";
description: string;
nullable?: boolean;
privileged?: boolean;
choices?: readonly string[];
min?: number;
max?: number;
}
const defaults: ConfigField[] = [
{
key: "model",
field: "model",
kind: "model",
nullable: true,
description: "default provider/model; unset to inherit",
},
{
key: "effort",
field: "effort",
kind: "effort",
nullable: true,
description: "reasoning effort from 0 to 1; unset to inherit",
},
{
key: "instructions",
field: "baseInstructions",
kind: "text",
nullable: true,
description: "standing instructions for every run",
},
];
export const repoConfigFields: ConfigField[] = [
{
key: "enabled",
field: "status",
kind: "status",
description: "enable or disable Pullfrog on this repository; does not install a workflow",
},
...defaults,
{
key: "progress-comments",
field: "progressComments",
kind: "toggle",
description: "temporary progress comments during a run",
},
{
key: "oss",
field: "ossProgramEnabled",
kind: "boolean",
privileged: true,
description: "use an existing OSS funding grant; does not enroll this repo",
},
{
key: "push",
field: "push",
kind: "permission",
privileged: true,
description: "git push permission",
},
{
key: "shell",
field: "shell",
kind: "permission",
privileged: true,
description: "agent shell permission and isolation",
},
{
key: "env-allowlist",
field: "envAllowlist",
kind: "text",
nullable: true,
privileged: true,
description: "environment variable names exposed to shell commands, one per line",
},
{
key: "signed-commits",
field: "signedCommits",
kind: "toggle",
privileged: true,
description: "sign commits when Pro or grandfathered access permits",
},
{
key: "auto-merge",
field: "autoMergeEnabled",
kind: "toggle",
privileged: true,
description: "allow autonomous merging, subject to runtime safety gates",
},
{
key: "hooks.setup",
field: "setupScript",
kind: "text",
nullable: true,
description: "script run at setup",
},
{
key: "hooks.post-checkout",
field: "postCheckoutScript",
kind: "text",
nullable: true,
description: "script run after checkout",
},
{
key: "hooks.pre-push",
field: "prepushScript",
kind: "text",
nullable: true,
description: "script run before pushing",
},
{
key: "hooks.stop",
field: "stopScript",
kind: "text",
nullable: true,
description: "script run at agent stop",
},
{
key: "prompts.review",
field: "Review",
kind: "prompt",
nullable: true,
description: "additional instructions for reviews",
},
{
key: "prompts.build",
field: "Build",
kind: "prompt",
nullable: true,
description: "additional instructions for implementation",
},
{
key: "prompts.plan",
field: "Plan",
kind: "prompt",
nullable: true,
description: "additional instructions for planning",
},
{
key: "prompts.address-reviews",
field: "AddressReviews",
kind: "prompt",
nullable: true,
description: "additional instructions for addressing review feedback",
},
{
key: "prompts.fix-ci",
field: "Fix",
kind: "prompt",
nullable: true,
description: "additional instructions for fixing CI failures",
},
{ key: "mention.enabled", field: "mention", kind: "toggle", description: "respond to mentions" },
{
key: "mention.instructions",
field: "mentionInstructions",
kind: "text",
nullable: true,
description: "instructions for mention-triggered runs",
},
{
key: "mention.non-collaborators",
field: "mentionAllowNonCollaborator",
kind: "toggle",
description: "allow mentions from non-collaborators",
},
{
key: "review.mode",
field: "prCreated",
kind: "enum",
choices: ["none", "links", "agent"],
description: "behavior when a PR is opened",
},
{
key: "review.non-collaborators",
field: "prCreatedAllowNonCollaborator",
kind: "toggle",
description: "review PRs from non-collaborators",
},
{
key: "review.re-review",
field: "prReReview",
kind: "toggle",
description: "re-review PRs after updates",
},
{
key: "review.approve",
field: "prApproveEnabled",
kind: "toggle",
description: "allow approving reviews",
},
{
key: "review.drafts",
field: "prCreatedReviewDrafts",
kind: "toggle",
description: "review draft PRs",
},
{
key: "review.own-prs",
field: "reviewOwnPrs",
kind: "toggle",
description: "review Pullfrog-authored PRs",
},
{
key: "review.status-check",
field: "statusChecks",
kind: "toggle",
description: "publish the run status check",
},
{
key: "review.approval-check",
field: "approvalCheck",
kind: "toggle",
description: "publish the approval verdict check",
},
{
key: "issue.mode",
field: "issueCreated",
kind: "enum",
choices: ["none", "links", "plan", "build", "custom"],
description: "behavior when an issue is opened",
},
{
key: "issue.instructions",
field: "issueCreatedInstructions",
kind: "text",
nullable: true,
description: "instructions for new issues",
},
{
key: "issue.non-collaborators",
field: "issueCreatedAllowNonCollaborator",
kind: "toggle",
description: "handle issues from non-collaborators",
},
{
key: "label.enabled",
field: "autoLabelIssues",
kind: "toggle",
description: "automatically label issues",
},
{
key: "label.instructions",
field: "autoLabelIssuesInstructions",
kind: "text",
nullable: true,
description: "instructions for issue labeling",
},
{
key: "address-reviews.enabled",
field: "codingAddressReviews",
kind: "toggle",
description: "address reviews on Pullfrog-authored PRs",
},
{
key: "fix-ci.own-prs",
field: "codingAutoFixCiFailuresSelf",
kind: "toggle",
description: "fix CI on Pullfrog-authored PRs",
},
{
key: "fix-ci.reviewed-prs",
field: "codingAutoFixCiFailuresReview",
kind: "toggle",
description: "fix CI on reviewed PRs",
},
];
export const orgConfigFields: ConfigField[] = [
...defaults.map((field) => ({
...field,
field:
field.key === "model"
? "defaultModel"
: field.key === "effort"
? "defaultEffort"
: field.field,
})),
{
key: "xrepo.brief",
field: "xrepoBrief",
kind: "text",
nullable: true,
description: "instructions for cross-repository work",
},
{
key: "billing.mode",
field: "modelAccessMode",
kind: "enum",
choices: ["byok", "router"],
privileged: true,
description: "provider credentials or Pullfrog Router",
},
{
key: "billing.auto-reload",
field: "autoReloadEnabled",
kind: "boolean",
privileged: true,
description: "automatically charge for Router credit reloads",
},
{
key: "billing.reload-threshold-usd",
field: "autoReloadThresholdCents",
kind: "usd",
min: 5,
max: 10000,
privileged: true,
description: "balance below which automatic reloads occur (USD)",
},
{
key: "billing.reload-amount-usd",
field: "autoReloadAmountCents",
kind: "usd",
min: 10,
max: 100000,
privileged: true,
description: "amount added per automatic reload (USD)",
},
{
key: "billing.monthly-limit-usd",
field: "routerMonthlyLimitCents",
kind: "usd",
min: 10,
max: 1000000,
nullable: true,
privileged: true,
description: "monthly automatic top-up ceiling (USD); unset for unlimited",
},
{
key: "billing.limit-mode",
field: "routerLimitMode",
kind: "enum",
choices: ["hard_cap", "alert_only"],
privileged: true,
description: "stop automatic top-ups at the cap or only alert",
},
];
export function configFields(scope: ConfigScope): ConfigField[] {
return scope === "org" ? orgConfigFields : repoConfigFields;
}
export function configChoices(field: ConfigField): string {
if (field.kind === "permission") return "disabled | restricted | enabled";
if (["boolean", "toggle", "status"].includes(field.kind)) return "true | false";
if (field.choices) return field.choices.join(" | ");
if (field.kind === "usd") return `${field.min}..${field.max} USD`;
if (field.kind === "effort") return "0..1";
return field.kind === "model" ? "provider/model" : "text";
}
export function configValueSchema(field: ConfigField) {
let schema: z.ZodType<ConfigValue>;
switch (field.kind) {
case "boolean":
case "toggle":
case "status":
schema = z.boolean();
break;
case "effort":
schema = z.number().min(0).max(1);
break;
case "usd":
schema = z
.number()
.int()
.min(field.min ?? 0)
.max(field.max ?? Infinity);
break;
case "permission":
schema = z.enum(["disabled", "restricted", "enabled"]);
break;
case "enum":
schema = z
.string()
.refine((v) => field.choices?.includes(v), `expected ${configChoices(field)}`);
break;
case "model":
schema = z.string().regex(/^[^/\s]+\/[^/\s]+$/, "expected provider/model");
break;
case "text":
case "prompt":
schema = z.string().max(100000);
break;
default:
field.kind satisfies never;
throw new Error("unknown config kind");
}
return field.nullable ? schema.nullable() : schema;
}
export const configEntrySchema = z.object({
key: z.string(),
value: z.union([z.string(), z.number(), z.boolean(), z.null()]),
source: z.enum(["repo", "org", "default", "unavailable"]),
writable: z.boolean(),
});
export const configResponseSchema = z.object({
target: z.string(),
entries: z.array(configEntrySchema),
warnings: z.array(z.string()),
});