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
TypeScript
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