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.

100 linesCodeBlameRaw
1/**
2 * Starting work on a work item 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`, `agent_session_link` 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 work item's pull request." },
20];
21
22/** What `start_agent_session` started, or the hand-off to run. */
23export type AgentSessionStarted = {
24 /** The work item it works on, by number in the workspace: for a pull request with none, its number. */
25 item: number;
26 /** The pull request it carries on with, when it was started on one. */
27 pull?: number | null;
28 session: WorkSession | null;
29 sessionUrl: string | null;
30 provider: string | null;
31 providerSessionId: string | null;
32 providerSessionUrl: string | null;
33 handoff: boolean;
34 command: string | null;
35 prompt: string;
36};
37
38/** Whether sessions can start on the workspace's Anthropic account. */
39export type AgentSessionsSetup = {
40 available: boolean;
41 connectionName: string | null;
42 model: string | null;
43 problem: string | null;
44};
45
46/** What to start: a work item, or a new one from a title and body. */
47export type StartAgentSessionInput = {
48 item?: number;
49 title?: string;
50 body?: string;
51 instruction?: string;
52 mode?: AgentMode;
53 /** true: the Claude Code hand-off only; false: a session on the workspace's account, or a refusal. */
54 handoff?: boolean;
55};
56
57/** The integrations service's methods for sessions on a workspace's Anthropic account. */
58export interface AgentSessionsApi {
59 setup(workspace: string, viewer: Viewer): Promise<Result<AgentSessionsSetup>>;
60 start(actor: User, repo: RepoPath, input: StartAgentSessionInput): Promise<Result<AgentSessionStarted>>;
61 /** Reads the session now (it is otherwise followed every minute). Whether it is still going. */
62 sync(repo: RepoPath, id: string, viewer: Viewer): Promise<Result<boolean>>;
63 /** Where it opens on the Claude Console. Only whoever started it is told. */
64 link(actor: User, repo: RepoPath, id: string): Promise<Result<string>>;
65 /** Interrupts it and ends its record. Only whoever started it. */
66 stop(actor: User, repo: RepoPath, id: string): Promise<Result<boolean>>;
67}
68
69async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
70 const response = await service.fetch(`https://service/rpc/${method}`, {
71 method: "POST",
72 headers: { "content-type": "application/json" },
73 body: JSON.stringify(args),
74 });
75 if (!response.ok) {
76 throw new Error(`${method} failed with status ${response.status}`);
77 }
78 return (await response.json()) as T;
79}
80
81export function agentSessionsClient(service: ServiceBinding): AgentSessionsApi {
82 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
83 return {
84 setup: (workspace, viewer) => call("agent_sessions_setup", { workspace, viewer }),
85 start: (actor, repo, input) =>
86 call("start_agent_session", {
87 actor,
88 repo,
89 item: input.item ?? null,
90 title: input.title ?? null,
91 body: input.body ?? null,
92 instruction: input.instruction ?? null,
93 mode: input.mode ?? null,
94 handoff: input.handoff ?? null,
95 }),
96 sync: (repo, id, viewer) => call("sync_agent_session", { repo, id, viewer }),
97 link: (actor, repo, id) => call("agent_session_link", { actor, repo, id }),
98 stop: (actor, repo, id) => call("stop_agent_session", { actor, repo, id }),
99 };
100}