Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

155 linesCodeBlameRaw
1/**
2 * Runs and recorded sessions, kept by the work service. Mirrors
3 * `g1t_contracts::agents`.
4 */
5import type { ServiceBinding } from "./clients";
6import type { User, Viewer } from "./identity";
7import type { RepoPath } from "./repos";
8import type { Result } from "./result";
9import type { Pull, PullStatus, SessionEntry } from "./work";
10
11/**
12 * The work a run does. Only checks, queue and mergecheck are started now;
13 * the others are runs of the agents g1t once ran itself, kept so older
14 * runs still read.
15 */
16export type RunKind = "implement" | "revise" | "review" | "answer" | "update" | "plan" | "checks" | "queue" | "mergecheck";
17
18export const RUN_KINDS: RunKind[] = ["implement", "revise", "review", "answer", "update", "plan", "checks", "queue", "mergecheck"];
19
20/** How a kind of run reads in a sentence: "is implementing", "a review run". */
21export const RUN_KIND_LABEL: Record<RunKind, string> = {
22 implement: "Implementing",
23 revise: "Revising",
24 review: "Reviewing",
25 answer: "Answering",
26 update: "Catching up",
27 plan: "Planning",
28 checks: "Checks",
29 queue: "Merge queue",
30 mergecheck: "Merge check",
31};
32
33export type AgentRunStatus = "queued" | "running" | "succeeded" | "failed" | "stopped";
34
35export function isActiveRun(status: AgentRunStatus): boolean {
36 return status === "queued" || status === "running";
37}
38
39export type RunStep = { at: string; text: string };
40
41export type AgentRun = {
42 id: string;
43 repo: RepoPath;
44 number: number | null;
45 title: string | null;
46 kind: RunKind;
47 /** `g1t`, for its own agent and for a sandbox that runs commands. */
48 agent: string;
49 /** Members only. */
50 model: string | null;
51 status: AgentRunStatus;
52 step: string | null;
53 /** Oldest first; empty in lists. */
54 steps: RunStep[];
55 stepCount: number;
56 startedBy: string | null;
57 error: string | null;
58 /** Members only. */
59 costUsd: number | null;
60 turns: number | null;
61 /** The most it may cost, from its guardrails. Null: no cap, or not a member. */
62 budgetUsd?: number | null;
63 /** The longest it may take, in minutes, from its guardrails. */
64 timeCapMinutes?: number | null;
65 /** `budget` or `time` when g1t stopped it for reaching that cap. */
66 halted?: "budget" | "time" | "abuse" | null;
67 createdAt: string;
68 startedAt: string | null;
69 finishedAt: string | null;
70 updatedAt: string;
71};
72
73export type AgentRunTicket = { runId: string; token: string };
74
75export type OpenRunInput = {
76 actor: User;
77 repo: RepoPath;
78 number?: number | null;
79 pullId?: string | null;
80 title?: string | null;
81 kind: RunKind;
82 agent?: string | null;
83 model?: string | null;
84 sandbox: string;
85 startedBy?: string | null;
86 /** Its time cap, from its guardrails. */
87 timeCapMinutes?: number | null;
88};
89
90export type RunFilter = {
91 repo?: RepoPath;
92 workspace?: string;
93 active?: boolean;
94 kind?: RunKind;
95 status?: AgentRunStatus;
96 number?: number;
97 limit?: number;
98};
99
100export type SessionSummary = {
101 number: number;
102 title: string;
103 status: PullStatus;
104 agent: string;
105 entries: number;
106 tools: number;
107 prompt: string | null;
108 kinds: RunKind[];
109 runs: number;
110 costUsd: number | null;
111 active: boolean;
112 startedAt: string;
113 lastAt: string;
114};
115
116export type SessionFilter = { kind?: RunKind; outcome?: PullStatus; number?: number };
117
118export type SessionView = {
119 pull: Pull;
120 entries: SessionEntry[];
121 runs: AgentRun[];
122 costUsd: number | null;
123};
124
125export interface AgentsApi {
126 /** For the runner: records a sandbox it is starting. */
127 openRun(input: OpenRunInput): Promise<Result<AgentRunTicket>>;
128 /** For the runner: ends a run when its sandbox stops. Refused once it has ended. */
129 closeRun(runId: string, token: string, outcome: "succeeded" | "failed", error?: string): Promise<Result<AgentRunStatus>>;
130 listRuns(viewer: Viewer, filter: RunFilter): Promise<Result<AgentRun[]>>;
131 getRun(viewer: Viewer, repo: RepoPath, id: string): Promise<Result<AgentRun>>;
132 listSessions(viewer: Viewer, repo: RepoPath, filter?: SessionFilter): Promise<Result<SessionSummary[]>>;
133 getSession(viewer: Viewer, repo: RepoPath, number: number): Promise<Result<SessionView>>;
134}
135
136/** The runs and sessions methods of the work service. */
137export function agentsClient(service: ServiceBinding): AgentsApi {
138 const call = async <T>(method: string, args: object): Promise<T> => {
139 const response = await service.fetch(`https://service/rpc/${method}`, {
140 method: "POST",
141 headers: { "content-type": "application/json" },
142 body: JSON.stringify(args),
143 });
144 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
145 return (await response.json()) as T;
146 };
147 return {
148 openRun: (input) => call("open_run", input),
149 closeRun: (runId, token, outcome, error) => call("report_run", { runId, token, outcome, error }),
150 listRuns: (viewer, filter) => call("list_runs", { viewer, ...filter }),
151 getRun: (viewer, repo, id) => call("get_run", { viewer, repo, id }),
152 listSessions: (viewer, repo, filter = {}) => call("list_sessions", { viewer, repo, ...filter }),
153 getSession: (viewer, repo, number) => call("get_session", { viewer, repo, number }),
154 };
155}