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.

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