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
165 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 caps, from its guardrails. */
96 budgetUsd?: number | null;
97 timeCapMinutes?: number | null;
98};
99
100export type RunFilter = {
101 repo?: RepoPath;
102 workspace?: string;
103 active?: boolean;
104 kind?: RunKind;
105 status?: AgentRunStatus;
106 number?: number;
107 limit?: number;
108};
109
110export type SessionSummary = {
111 number: number;
112 title: string;
113 status: PullStatus;
114 agent: string;
115 entries: number;
116 tools: number;
117 prompt: string | null;
118 kinds: RunKind[];
119 runs: number;
120 costUsd: number | null;
121 active: boolean;
122 startedAt: string;
123 lastAt: string;
124};
125
126export type SessionFilter = { kind?: RunKind; outcome?: PullStatus; number?: number };
127
128export type SessionView = {
129 pull: Pull;
130 entries: SessionEntry[];
131 runs: AgentRun[];
132 costUsd: number | null;
133};
134
135export interface AgentsApi {
136 /** For the runner: records a sandbox it is starting. */
137 openRun(input: OpenRunInput): Promise<Result<AgentRunTicket>>;
138 /** For the runner: ends a run when its sandbox stops. Refused once it has ended. */
139 closeRun(runId: string, token: string, outcome: "succeeded" | "failed", error?: string): Promise<Result<AgentRunStatus>>;
140 listRuns(viewer: Viewer, filter: RunFilter): Promise<Result<AgentRun[]>>;
141 getRun(viewer: Viewer, repo: RepoPath, id: string): Promise<Result<AgentRun>>;
142 listSessions(viewer: Viewer, repo: RepoPath, filter?: SessionFilter): Promise<Result<SessionSummary[]>>;
143 getSession(viewer: Viewer, repo: RepoPath, number: number): Promise<Result<SessionView>>;
144}
145
146/** The runs and sessions methods of the work service. */
147export function agentsClient(service: ServiceBinding): AgentsApi {
148 const call = async <T>(method: string, args: object): Promise<T> => {
149 const response = await service.fetch(`https://service/rpc/${method}`, {
150 method: "POST",
151 headers: { "content-type": "application/json" },
152 body: JSON.stringify(args),
153 });
154 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
155 return (await response.json()) as T;
156 };
157 return {
158 openRun: (input) => call("open_run", input),
159 closeRun: (runId, token, outcome, error) => call("report_run", { runId, token, outcome, error }),
160 listRuns: (viewer, filter) => call("list_runs", { viewer, ...filter }),
161 getRun: (viewer, repo, id) => call("get_run", { viewer, repo, id }),
162 listSessions: (viewer, repo, filter = {}) => call("list_sessions", { viewer, repo, ...filter }),
163 getSession: (viewer, repo, number) => call("get_session", { viewer, repo, number }),
164 };
165}