UNPKG

framework

Version:

The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.

130 lines 6.66 kB
import type { Driver, DriverEvent, DriverPromptOptions, DriverSession, DriverStartOptions, DriverTurn } from './types.js'; /** * A {@link Driver} that runs the agent on **GitHub Actions** instead of on this * machine (#610): dispatch a workflow, poll it, read the transcript it uploads. * * This is the answer to "drive Claude Code on the web". The routines fire API was * the obvious candidate and turned out unusable — the prompt arrives wrapped as * untrusted data, and there is no read-back of any kind. The official * `anthropics/claude-code-action@v1` has neither problem: the prompt is passed * verbatim, and the run publishes its full transcript. Auth is the same * subscription posture as everywhere else (#495): a `claude setup-token` OAuth * token held by the repo, never an API key of ours. * * It fits `Driver`/`DriverSession` as written, with no new methods. What changes is * not the shape but the tempo, and those costs are real: * * - **Minutes, not seconds.** Every `prompt` is a fresh runner and a fresh * checkout. Continuity comes from the branch the previous turn pushed, which the * session tracks and dispatches onto next time. * - **No live stream.** The transcript arrives once, at the end, so the dashboard's * {@link DriverStartOptions.onEvent} feed replays in a burst rather than trickling. * - **Quota is the account's, not the runner's.** Free minutes on a public repo * change nothing about the subscription window every run draws down. * * The workspace lives on a runner that is gone by the time we read it, so * {@link ActionsSession.readCode} reads from the pushed branch over the contents * API rather than from disk. */ export declare class ActionsDriver implements Driver { private readonly opts; readonly id = "github-actions"; constructor(opts: ActionsDriverOptions); start(opts: DriverStartOptions): Promise<DriverSession>; } /** Options for {@link ActionsDriver}. */ export interface ActionsDriverOptions { /** Repository owner (user or org) that runs the workflow. */ owner: string; /** Repository name. */ repo: string; /** * GitHub token used to dispatch and to read runs, artifacts, and file contents. * Needs `repo` + `workflow` scope. Must belong to a **user**, not an App: the * action's `checkHumanActor` rejects a bot-triggered agent run unless the bot is * in its `allowed_bots`. */ token: string; /** Workflow file to dispatch. Default `"framework-agent.yml"`. */ workflow?: string; /** Git ref the first turn runs on. Later turns follow the branch the agent pushed. */ ref?: string; /** How often to poll the run, in ms. Default 5000. */ pollIntervalMs?: number; /** Give up on a run after this long, in ms. Default 1 hour (the job cap is 6). */ timeoutMs?: number; /** REST base. Default `"https://api.github.com"`. */ apiBase?: string; /** `fetch` override for tests. Default the global. */ fetch?: FetchLike; /** Clock override for tests. Default `Date.now`. */ now?: () => number; /** Sleep override for tests. Default a real timer. */ sleep?: (ms: number) => Promise<void>; /** * Unique tag mixed into the correlation id. Default a random token; injected in tests for * a stable id. Without it a fresh driver process restarts the session counter at 1, so * every run's first turn is `actions-1-turn-1` and runs collide (see {@link ActionsSession}). */ runTag?: () => string; /** * Prefix for the branch each run pushes its work to (#1085). Default `"claude/"`. The * driver names the branch (prefix + session id) and passes it to the workflow, rather than * discovering it after the fact: the action leaves `branch_name` empty for a * `workflow_dispatch` run, so there is nothing to discover. */ branchPrefix?: string; } /** The slice of `fetch` this driver uses. */ export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>; /** One Actions-backed session. Each `prompt` is one workflow run. */ export declare class ActionsSession implements DriverSession { private readonly config; private readonly startOpts; readonly id: string; readonly cwd: string; /** The branch the last run pushed; set once the run reports it, and the next turn builds on it. */ private branch; /** The branch this session asks each run to push to. Stable across turns, so they chain. */ private readonly runBranch; /** The agent's own session id, carried across turns so `resume` can continue it. */ private lastSessionId; private turnCounter; constructor(config: ActionsDriverOptions, startOpts: DriverStartOptions); prompt(text: string, opts?: DriverPromptOptions): Promise<DriverTurn>; /** * Read a file the agent produced. The runner is gone, so this reads the branch the * run pushed rather than the local workspace — the seam is still the code, just * fetched over the contents API. */ readCode(path: string): Promise<string>; dispose(): Promise<void>; /** Fire the workflow. Returns nothing useful: dispatch is 204 with no body, hence the correlation id. */ private dispatch; /** Poll until our run appears and finishes. Identified by the correlation id in its `run-name`. */ private awaitWorkflowRun; /** Our run among the workflow's recent ones, or undefined while GitHub is still creating it. */ private findWorkflowRun; /** Download the run's artifact and pull the transcript and the pushed branch out of it. */ private readRunArtifact; private get owner(); /** The session-wide signal or the per-prompt one (`DriverPromptOptions.signal`) both stop the poll. */ private throwIfAborted; /** A REST call that expects JSON back. */ private api; /** A REST call, with auth and error handling. */ private request; } /** * Turn the action's `execution_file` into a turn, replaying its events on the way. * * The adapter is thin on purpose: the file is a JSON **array** of exactly the * SDKMessage objects the CLI emits one-per-line, so the existing * {@link StreamJsonParser} reads it verbatim once the array is unwrapped. The whole * difference between running locally and running on a runner is array-vs-JSONL. * * Events replay in a burst at the end rather than live — that is the honest cost of * this driver, and the dashboard sees the same event stream either way. */ export declare function replayTranscript(json: string, emit?: (event: DriverEvent) => void): DriverTurn; //# sourceMappingURL=actions.d.ts.map