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"), "context_window?": type.number.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; 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; } function resolveNonPromptInputs() { const contextWindowRaw = core.getInput("context_window"); return Inputs.omit("prompt").assert({ model: core.getInput("model") || undefined, context_window: contextWindowRaw ? parseInt(contextWindowRaw, 10) : 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 | 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 contextWindowStr = str(jsonPayload, "context_window"); const contextWindow = (contextWindowStr ? parseInt(contextWindowStr, 10) : undefined) ?? inputs.context_window ?? 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, contextWindow, 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; 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; export function resolveOutputSchema(): Record | 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; }