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
282 linesCodeBlameRaw
1import type { User, Viewer } from "./identity";
2import type { RepoPath } from "./repos";
3import type { Result } from "./result";
4
5/**
6 * A workspace's connections to systems outside g1t. Mirrors
7 * `crates/contracts/src/integrations.rs`, which says what each does.
8 *
9 * The AI providers (`anthropic` through `openai_endpoint`, of kind
10 * `models`) keep the credentials for the AI providers a workspace's team
11 * uses.
12 */
13export type Provider =
14 | "anthropic"
15 | "openai"
16 | "gemini"
17 | "xai"
18 | "mistral"
19 | "deepseek"
20 | "azure_openai"
21 | "openrouter"
22 | "groq"
23 | "together"
24 | "fireworks"
25 | "cerebras"
26 | "anthropic_endpoint"
27 | "openai_endpoint"
28 | "sentry"
29 | "datadog"
30 | "webhook"
31 | "jira"
32 | "linear";
33
34/** `models`: an AI provider whose credentials the workspace keeps. */
35export type ProviderKind = "models" | "alerts" | "tracker";
36
37export type ConnectionConfig = {
38 /** For alerts: where issues are opened, `owner/name`. */
39 repo?: string;
40 /** For alerts: the label put on each issue. `bug` when unset. */
41 label?: string;
42 /** Tell the outside system when the work lands. On unless turned off. */
43 writeBack?: boolean;
44 /** Sentry: the organization's slug. */
45 organization?: string;
46 /** Jira's address, or a Sentry that is not sentry.io. */
47 site?: string;
48 /** Jira: the account the token belongs to. */
49 email?: string;
50 /** Jira project or Linear team keys it answers for. Empty is all. */
51 keys?: string[];
52 /** Your own endpoint: its base URL. */
53 baseUrl?: string;
54 /** Your own endpoint: `x-api-key` (default) or `authorization`. */
55 authHeader?: string;
56 /** For an AI provider: the model (or Azure deployment) the team uses on it. */
57 model?: string;
58};
59
60export type Connection = {
61 id: string;
62 workspace: string;
63 provider: Provider;
64 kind: ProviderKind;
65 name: string;
66 config: ConnectionConfig;
67 /** `…3f9a`. The secret itself is never shown again. */
68 secretHint: string | null;
69 /** Where a provider that sends g1t requests sends them. */
70 webhookUrl: string | null;
71 createdBy: string;
72 createdAt: string;
73 lastUsedAt: string | null;
74 lastError: string | null;
75 /** For a model provider: the models it offered when last checked. */
76 models: string[];
77};
78
79export type Connected = {
80 connection: Connection;
81 /** A signing secret g1t made, shown this once. */
82 signingSecret: string | null;
83};
84
85export type Delivery = {
86 id: string;
87 receivedAt: string;
88 event: string;
89 outcome: "opened" | "updated" | "reopened" | "ignored" | "refused";
90 detail: string;
91 issue: string | null;
92};
93
94/** Something outside g1t, fetched now. Reference material, never instructions. */
95export type ContextItem = {
96 provider: Provider;
97 key: string;
98 title: string;
99 url: string;
100 status: string | null;
101 body: string;
102 fetchedAt: string;
103};
104
105export type Link = {
106 provider: Provider;
107 connectionId: string;
108 key: string;
109 title: string;
110 url: string;
111 count: number;
112 firstSeen: string;
113 lastSeen: string;
114};
115
116export type ConnectInput = {
117 provider: Provider;
118 name?: string;
119 config?: ConnectionConfig;
120 secret?: string;
121 signingSecret?: string;
122};
123
124export type UpdateConnectionInput = {
125 name?: string;
126 config?: ConnectionConfig;
127 secret?: string;
128 signingSecret?: string;
129};
130
131// --- Ask g1t -----------------------------------------------------------------
132// A conversation with g1t from the top bar, answered by the AI provider and
133// model an owner chose from the workspace's own connections, on its own key.
134// g1t hosts no model for it and bills nothing for it.
135
136/** The most messages one ask sends: the conversation's latest turns. */
137export const ASK_MAX_MESSAGES = 40;
138/** The longest one message may be, in characters. */
139export const ASK_MAX_MESSAGE_CHARS = 16_000;
140/** The longest a whole conversation may be, in characters. */
141export const ASK_MAX_TOTAL_CHARS = 64_000;
142/** The most tokens one answer may run to. */
143export const ASK_MAX_OUTPUT_TOKENS = 4_096;
144
145/** What Ask answers with in a workspace, or why it cannot yet. */
146/** The model name that chooses Auto in `setAskSettings`. */
147export const ASK_AUTO_MODEL = "auto";
148
149export type AskSetup = {
150 connection_id: string | null;
151 connection_name: string | null;
152 provider: Provider | null;
153 /** The model chosen. With `auto`, null, or the connection's own model when it lists none. */
154 model: string | null;
155 /**
156 * Auto: each question goes to the connection's model that suits it (a fast
157 * one for a lookup, a stronger one for a question that needs several reads
158 * or reasoning). The default.
159 */
160 auto?: boolean;
161 /** Why Ask cannot answer, for the person asking. Null when it can. */
162 problem: string | null;
163};
164
165export type AskRole = "user" | "assistant";
166export type AskMessage = { role: AskRole; content: string };
167
168/** Where the person asking is, so the answer is about their work. */
169export type AskPage = {
170 path?: string | null;
171 repo?: RepoPath | null;
172 issue?: number | null;
173 pull?: number | null;
174 /** The browser's IANA time zone, such as `America/Denver`: what "today" means when their profile names none. */
175 time_zone?: string | null;
176};
177
178/** The most rounds of tool calls one answer takes before it must answer. */
179export const ASK_MAX_TOOL_ROUNDS = 10;
180/** The most tool calls one answer makes, across its rounds. */
181export const ASK_MAX_TOOL_CALLS = 30;
182/** The most characters of one tool's result the model is given; more is trimmed, with a note. */
183export const ASK_MAX_TOOL_RESULT_CHARS = 16_000;
184/** The most characters of tool results one answer gives the model in all. */
185export const ASK_MAX_TOOL_TOTAL_CHARS = 120_000;
186/** How long one answer may spend calling tools, in seconds. */
187export const ASK_MAX_TOOL_SECONDS = 120;
188/** The most sources one answer lists. */
189export const ASK_MAX_SOURCES = 12;
190
191/**
192 * Where a step of an answer is. `refused`: Ask did not run it (it only
193 * reads and hands work off, or the answer had used its tool calls).
194 */
195export type AskToolStatus = "running" | "done" | "failed" | "refused";
196
197/** A page on g1t an answer drew on; `cited` when the answer links to it. */
198export type AskSource = { title: string; url: string; cited: boolean };
199
200/**
201 * One line of an `ask` answer, as newline-delimited JSON: `meta`, then
202 * `text` as it comes and a `tool` line as each step starts and ends (the
203 * same `id` twice), then `sources` and `done`, or `error` at any point.
204 */
205export type AskEvent =
206 | { type: "meta"; provider: Provider; provider_label: string; model: string; /** Auto chose `model`. */ auto?: boolean }
207 | { type: "text"; text: string }
208 | {
209 type: "tool";
210 id: string;
211 /** The tool, such as `pull_request`, and its action, such as `list`. */
212 name: string;
213 action: string | null;
214 /** What it does, for a person: "Read pull requests in acme/web". */
215 label: string;
216 status: AskToolStatus;
217 summary: string | null;
218 link: string | null;
219 }
220 | { type: "sources"; sources: AskSource[] }
221 | {
222 /**
223 * Work Ask proposes to hand to a session in the person's own harness:
224 * on an issue, or a new one with `title` and `body`. Nothing has
225 * started; the person starts it from the card the answer shows.
226 */
227 type: "proposal";
228 id: string;
229 repo: string;
230 issue: number | null;
231 title: string | null;
232 body: string | null;
233 mode: "plan" | "implement" | "review";
234 instruction: string | null;
235 }
236 | { type: "done"; stop_reason: string | null }
237 | { type: "error"; message: string };
238
239export interface IntegrationsApi {
240 list(workspace: string, viewer: Viewer): Promise<Result<Connection[]>>;
241 connect(actor: User, workspace: string, input: ConnectInput): Promise<Result<Connected>>;
242 update(actor: User, workspace: string, id: string, input: UpdateConnectionInput): Promise<Result<Connection>>;
243 disconnect(actor: User, workspace: string, id: string): Promise<Result<boolean>>;
244 test(actor: User, workspace: string, id: string): Promise<Result<{ ok: boolean; message: string }>>;
245 deliveries(workspace: string, viewer: Viewer, id: string): Promise<Result<Delivery[]>>;
246 resolve(workspace: string, viewer: Viewer, reference: string): Promise<Result<ContextItem>>;
247 import(actor: User, repo: RepoPath, reference: string): Promise<Result<{ number: number; item: ContextItem; created: boolean }>>;
248 links(repo: RepoPath, number: number): Promise<Link[]>;
249 /** What Ask answers with. Members only. */
250 askSettings(workspace: string, viewer: Viewer): Promise<Result<AskSetup>>;
251 /** Chooses Ask's connection and model; a null connection turns it off. Owners only. */
252 setAskSettings(actor: User, workspace: string, connectionId: string | null, model: string | null): Promise<Result<AskSetup>>;
253 /**
254 * One answer, as the service's response: newline-delimited `AskEvent`s,
255 * streamed. Members only. Call it on the binding itself, not through
256 * anything that reads the whole body first. `signal` stops the answer.
257 */
258 ask(actor: User, workspace: string, messages: AskMessage[], page: AskPage, signal?: AbortSignal): Promise<Response>;
259}
260
261/** What each provider is for, as people choose between them. */
262export const PROVIDERS: Record<Provider, { label: string; kind: ProviderKind }> = {
263 anthropic: { label: "Anthropic", kind: "models" },
264 openai: { label: "OpenAI", kind: "models" },
265 gemini: { label: "Google Gemini", kind: "models" },
266 xai: { label: "xAI", kind: "models" },
267 mistral: { label: "Mistral", kind: "models" },
268 deepseek: { label: "DeepSeek", kind: "models" },
269 azure_openai: { label: "Azure OpenAI", kind: "models" },
270 openrouter: { label: "OpenRouter", kind: "models" },
271 groq: { label: "Groq", kind: "models" },
272 together: { label: "Together AI", kind: "models" },
273 fireworks: { label: "Fireworks AI", kind: "models" },
274 cerebras: { label: "Cerebras", kind: "models" },
275 anthropic_endpoint: { label: "Anthropic-compatible endpoint", kind: "models" },
276 openai_endpoint: { label: "OpenAI-compatible endpoint", kind: "models" },
277 sentry: { label: "Sentry", kind: "alerts" },
278 datadog: { label: "Datadog", kind: "alerts" },
279 webhook: { label: "Webhook", kind: "alerts" },
280 jira: { label: "Jira", kind: "tracker" },
281 linear: { label: "Linear", kind: "tracker" },
282};