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
96 linesCodeBlameRaw
1/**
2 * Starting work on an issue in a session on the person's own Claude.
3 * Mirrors `crates/contracts/src/agent_sessions.rs` (the integrations
4 * service's `start_agent_session`, `agent_sessions_setup`,
5 * `sync_agent_session` and `stop_agent_session`).
6 */
7import type { ServiceBinding } from "./clients";
8import type { User, Viewer } from "./identity";
9import type { RepoPath } from "./repos";
10import type { Result } from "./result";
11import type { WorkSession } from "./work";
12
13/** What kind of work a session is asked for. */
14export type AgentMode = "plan" | "implement" | "review";
15
16export const AGENT_MODES: { mode: AgentMode; label: string; hint: string }[] = [
17 { mode: "implement", label: "Implement", hint: "Make the change and open a pull request." },
18 { mode: "plan", label: "Plan", hint: "Work out how, and report the plan. No code." },
19 { mode: "review", label: "Review", hint: "Review the issue's pull request." },
20];
21
22/** What `start_agent_session` started, or the hand-off to run. */
23export type AgentSessionStarted = {
24 issue: number;
25 /** The pull request it carries on with, when it was started on one. */
26 pull?: number | null;
27 session: WorkSession | null;
28 sessionUrl: string | null;
29 provider: string | null;
30 providerSessionId: string | null;
31 providerSessionUrl: string | null;
32 handoff: boolean;
33 command: string | null;
34 prompt: string;
35};
36
37/** Whether sessions can start on the workspace's Anthropic account. */
38export type AgentSessionsSetup = {
39 available: boolean;
40 connectionName: string | null;
41 model: string | null;
42 problem: string | null;
43};
44
45/** What to start: an issue, or a new one from a title and body. */
46export type StartAgentSessionInput = {
47 issue?: number;
48 title?: string;
49 body?: string;
50 instruction?: string;
51 mode?: AgentMode;
52 /** true: the Claude Code hand-off only; false: a session on the workspace's account, or a refusal. */
53 handoff?: boolean;
54};
55
56/** The integrations service's methods for sessions on a workspace's Anthropic account. */
57export interface AgentSessionsApi {
58 setup(workspace: string, viewer: Viewer): Promise<Result<AgentSessionsSetup>>;
59 start(actor: User, repo: RepoPath, input: StartAgentSessionInput): Promise<Result<AgentSessionStarted>>;
60 /** Reads the session now (it is otherwise followed every minute). Whether it is still going. */
61 sync(repo: RepoPath, id: string, viewer: Viewer): Promise<Result<boolean>>;
62 /** Interrupts it and ends its record. Only whoever started it. */
63 stop(actor: User, repo: RepoPath, id: string): Promise<Result<boolean>>;
64}
65
66async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
67 const response = await service.fetch(`https://service/rpc/${method}`, {
68 method: "POST",
69 headers: { "content-type": "application/json" },
70 body: JSON.stringify(args),
71 });
72 if (!response.ok) {
73 throw new Error(`${method} failed with status ${response.status}`);
74 }
75 return (await response.json()) as T;
76}
77
78export function agentSessionsClient(service: ServiceBinding): AgentSessionsApi {
79 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
80 return {
81 setup: (workspace, viewer) => call("agent_sessions_setup", { workspace, viewer }),
82 start: (actor, repo, input) =>
83 call("start_agent_session", {
84 actor,
85 repo,
86 issue: input.issue ?? null,
87 title: input.title ?? null,
88 body: input.body ?? null,
89 instruction: input.instruction ?? null,
90 mode: input.mode ?? null,
91 handoff: input.handoff ?? null,
92 }),
93 sync: (repo, id, viewer) => call("sync_agent_session", { repo, id, viewer }),
94 stop: (actor, repo, id) => call("stop_agent_session", { actor, repo, id }),
95 };
96}