151 lines
4.9 KiB
TypeScript
151 lines
4.9 KiB
TypeScript
import { isAbsolute, resolve } from "node:path";
|
|
import * as core from "@actions/core";
|
|
import { type } from "arktype";
|
|
import type { AuthorPermission, PayloadEvent } from "../external.ts";
|
|
import { log } from "./cli.ts";
|
|
import type { RepoSettings } from "./runContext.ts";
|
|
|
|
const ShellPermissionInput = type.enumerated("disabled", "restricted", "enabled");
|
|
const PushPermissionInput = type.enumerated("disabled", "restricted", "enabled");
|
|
|
|
export const Inputs = type({
|
|
prompt: "string",
|
|
"model?": type.string.or("undefined"),
|
|
"timeout?": type.string.or("undefined"),
|
|
"push?": PushPermissionInput.or("undefined"),
|
|
"shell?": ShellPermissionInput.or("undefined"),
|
|
"cwd?": type.string.or("undefined"),
|
|
"output_schema?": type.string.or("undefined"),
|
|
});
|
|
|
|
export type Inputs = typeof Inputs.infer;
|
|
|
|
function isPayloadEvent(value: unknown): value is PayloadEvent {
|
|
return typeof value === "object" && value !== null && "trigger" in value;
|
|
}
|
|
|
|
function resolveCwd(cwd: string | undefined): string | undefined {
|
|
const workspace = process.env.GITHUB_WORKSPACE;
|
|
if (!cwd) return workspace;
|
|
if (isAbsolute(cwd)) return cwd;
|
|
return workspace ? resolve(workspace, cwd) : cwd;
|
|
}
|
|
|
|
const COLLABORATOR_PERMISSIONS: AuthorPermission[] = ["admin", "maintain", "write"];
|
|
|
|
function isCollaborator(event: PayloadEvent): boolean {
|
|
const perm = event.authorPermission;
|
|
return perm !== undefined && COLLABORATOR_PERMISSIONS.includes(perm);
|
|
}
|
|
|
|
export type ResolvedPromptInput = string | Record<string, unknown>;
|
|
|
|
export function resolvePromptInput(): ResolvedPromptInput {
|
|
const prompt = core.getInput("prompt", { required: true });
|
|
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = JSON.parse(prompt);
|
|
} catch {
|
|
return prompt;
|
|
}
|
|
|
|
if (!parsed || typeof parsed !== "object") {
|
|
return prompt;
|
|
}
|
|
|
|
return parsed as Record<string, unknown>;
|
|
}
|
|
|
|
function resolveNonPromptInputs() {
|
|
return Inputs.omit("prompt").assert({
|
|
model: core.getInput("model") || undefined,
|
|
timeout: core.getInput("timeout") || undefined,
|
|
cwd: core.getInput("cwd") || undefined,
|
|
push: (core.getInput("push") as "disabled" | "restricted" | "enabled") || undefined,
|
|
shell: (core.getInput("shell") as "disabled" | "restricted" | "enabled") || undefined,
|
|
});
|
|
}
|
|
|
|
export function resolvePayload(
|
|
resolvedPromptInput: ResolvedPromptInput,
|
|
repoSettings: RepoSettings
|
|
) {
|
|
// Helper: extract a string field from an unknown JSON payload
|
|
function str(obj: Record<string, unknown> | undefined, key: string): string | undefined {
|
|
const v = obj?.[key];
|
|
return typeof v === "string" ? v : undefined;
|
|
}
|
|
|
|
const [prompt, jsonPayload] =
|
|
typeof resolvedPromptInput !== "string"
|
|
? [
|
|
str(resolvedPromptInput, "prompt") ?? JSON.stringify(resolvedPromptInput),
|
|
resolvedPromptInput,
|
|
]
|
|
: [resolvedPromptInput, undefined];
|
|
|
|
const inputs = resolveNonPromptInputs();
|
|
|
|
const rawEvent = jsonPayload?.["event"];
|
|
const event: PayloadEvent = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
|
|
|
const model =
|
|
str(jsonPayload, "model") ?? inputs.model ?? repoSettings.model ?? process.env.OLLAMA_MODEL ?? undefined;
|
|
|
|
const isNonCollaborator = !isCollaborator(event);
|
|
const repoShell = repoSettings.shell ?? "restricted";
|
|
const inputShell = inputs.shell;
|
|
|
|
let resolvedShell = repoShell;
|
|
if (inputShell === "disabled") {
|
|
resolvedShell = "disabled";
|
|
} else if (inputShell === "restricted" && resolvedShell === "enabled") {
|
|
resolvedShell = "restricted";
|
|
}
|
|
if (isNonCollaborator && resolvedShell === "enabled") {
|
|
resolvedShell = "restricted";
|
|
}
|
|
|
|
return {
|
|
"~shockbot": true as const,
|
|
model,
|
|
prompt,
|
|
triggerer:
|
|
str(jsonPayload, "triggerer") ?? process.env.GITHUB_ACTOR ?? undefined,
|
|
eventInstructions: str(jsonPayload, "eventInstructions"),
|
|
event,
|
|
timeout: inputs.timeout ?? str(jsonPayload, "timeout"),
|
|
cwd: resolveCwd(inputs.cwd),
|
|
progressComment: (() => {
|
|
const pc = jsonPayload?.["progressComment"];
|
|
if (!pc || typeof pc !== "object") return undefined;
|
|
const p = pc as Record<string, unknown>;
|
|
if (typeof p.id !== "string" || p.type !== "issue") return undefined;
|
|
return { id: p.id, type: "issue" as const };
|
|
})(),
|
|
|
|
push: inputs.push ?? repoSettings.push ?? "restricted",
|
|
shell: resolvedShell,
|
|
};
|
|
}
|
|
|
|
export type ResolvedPayload = ReturnType<typeof resolvePayload>;
|
|
|
|
export function resolveOutputSchema(): Record<string, unknown> | undefined {
|
|
const raw = core.getInput("output_schema");
|
|
if (!raw) return undefined;
|
|
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = JSON.parse(raw);
|
|
} catch {
|
|
throw new Error(`invalid output_schema: not valid JSON`);
|
|
}
|
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
throw new Error(`invalid output_schema: must be a JSON object`);
|
|
}
|
|
log.info("» structured output schema provided — output will be required");
|
|
return parsed as Record<string, unknown>;
|
|
}
|