Skip to content

g1t/packages/contracts/src/integrations.ts

291 lines10,356 bytesCodeBlame
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 */
9export type Provider =
10 | "anthropic"
11 | "openai"
12 | "gemini"
13 | "xai"
14 | "mistral"
15 | "deepseek"
16 | "azure_openai"
17 | "openrouter"
18 | "groq"
19 | "together"
20 | "fireworks"
21 | "cerebras"
22 | "anthropic_endpoint"
23 | "openai_endpoint"
24 | "sentry"
25 | "datadog"
26 | "webhook"
27 | "jira"
28 | "linear";
29
30export type ProviderKind = "models" | "alerts" | "tracker";
31
32export type ConnectionConfig = {
33 /** For alerts: where issues are opened, `owner/name`. */
34 repo?: string;
35 /** For alerts: put a g1t agent on each issue opened. */
36 assign?: boolean;
37 /** For alerts: the label put on each issue. `bug` when unset. */
38 label?: string;
39 /** Tell the outside system when the work lands. On unless turned off. */
40 writeBack?: boolean;
41 /** Sentry: the organization's slug. */
42 organization?: string;
43 /** Jira's address, or a Sentry that is not sentry.io. */
44 site?: string;
45 /** Jira: the account the token belongs to. */
46 email?: string;
47 /** Jira project or Linear team keys it answers for. Empty is all. */
48 keys?: string[];
49 /** Your own endpoint: its base URL. */
50 baseUrl?: string;
51 /** Your own endpoint: `x-api-key` (default) or `authorization`. */
52 authHeader?: string;
53 /** Models: the model used when a route to this connection names none. */
54 model?: string;
55 /**
56 * Models: which AI Gateway requests go to this connection, by the model
57 * they name: ids (`gpt-5.5`) or prefixes ending in `*` (`gpt-*`,
58 * `ollama/*`, `*`). A `/*` prefix is taken off before sending. Absent:
59 * `claude-*` on an Anthropic key or Anthropic-compatible endpoint,
60 * nothing on the others.
61 */
62 gatewayModels?: string[];
63};
64
65/** One of a workspace's own model providers, for the AI Gateway. Carries its key: for the model proxy only. */
66export type GatewayProvider = {
67 id: string;
68 name: string;
69 /** `anthropic`, `openai`, `openai_endpoint`… */
70 provider: string;
71 api: "anthropic" | "openai";
72 /** OpenAI's own API (or Azure's). */
73 official: boolean;
74 /** Without `/v1` for Anthropic's API, with it for OpenAI's. */
75 baseUrl: string;
76 apiKey: string | null;
77 /** `x-api-key`, `authorization` (as `Bearer`) or `api-key`. */
78 authHeader: string;
79 gatewayToken?: string | null;
80 /** The models it takes: ids and `*` prefixes. */
81 patterns: string[];
82 /** The models the provider listed when last checked. */
83 models: string[];
84};
85
86/** The AI Gateway models a connection takes when it names none. */
87export function gatewayPatterns(provider: Provider, config: ConnectionConfig): string[] {
88 if (config.gatewayModels) return config.gatewayModels;
89 return provider === "anthropic" || provider === "anthropic_endpoint" ? ["claude-*"] : [];
90}
91
92export type Connection = {
93 id: string;
94 workspace: string;
95 provider: Provider;
96 kind: ProviderKind;
97 name: string;
98 config: ConnectionConfig;
99 /** `…3f9a`. The secret itself is never shown again. */
100 secretHint: string | null;
101 /** Where a provider that sends g1t requests sends them. */
102 webhookUrl: string | null;
103 createdBy: string;
104 createdAt: string;
105 lastUsedAt: string | null;
106 lastError: string | null;
107 /** For a model provider: the models it offered when last checked. */
108 models: string[];
109};
110
111/** The kinds of work a model is chosen for, and `default` for the rest. */
112export const MODEL_TASKS = ["default", "implement", "review", "plan", "update"] as const;
113export type ModelTask = (typeof MODEL_TASKS)[number];
114
115/** Where one kind of work's model requests go: g1t's hosted models when `connectionId` is null. */
116export type ModelRoute = { task: ModelTask; connectionId: string | null; model: string | null };
117
118export type Connected = {
119 connection: Connection;
120 /** A signing secret g1t made, shown this once. */
121 signingSecret: string | null;
122};
123
124export type Delivery = {
125 id: string;
126 receivedAt: string;
127 event: string;
128 outcome: "opened" | "updated" | "reopened" | "ignored" | "refused";
129 detail: string;
130 issue: string | null;
131};
132
133/** Something outside g1t, fetched now. Reference material, never instructions. */
134export type ContextItem = {
135 provider: Provider;
136 key: string;
137 title: string;
138 url: string;
139 status: string | null;
140 body: string;
141 fetchedAt: string;
142};
143
144export type Link = {
145 provider: Provider;
146 connectionId: string;
147 key: string;
148 title: string;
149 url: string;
150 count: number;
151 firstSeen: string;
152 lastSeen: string;
153};
154
155/**
156 * How capable, and how costly, a model g1t routes to is: `small` (fast),
157 * `large` (standard) or `frontier` (most capable).
158 */
159export type ModelTier = "small" | "large" | "frontier";
160
161/** The tiers, cheapest first. */
162export const MODEL_TIERS: ModelTier[] = ["small", "large", "frontier"];
163
164export type ModelSession = {
165 token: string;
166 billedTo: "g1t" | "workspace";
167 providerName: string | null;
168 model: string | null;
169 /**
170 * Names the run in AI Gateway's logs (`metadata.session`) and its tokens
171 * in billing's count, on g1t's models and the workspace's own alike.
172 */
173 id: string;
174 /**
175 * The tier the workspace chose for this work on g1t's models, instead of
176 * Auto; the run goes there. Null for Auto, and on its own providers.
177 */
178 tierChoice?: ModelTier | null;
179};
180
181export type ModelUpstream = {
182 route: "g1t" | "anthropic" | "endpoint";
183 /** The API the provider speaks, which the proxy translates to. */
184 api: "anthropic" | "openai";
185 /** The model every request of the run goes to, when the route names one. */
186 model: string | null;
187 /** OpenAI's own API. */
188 official: boolean;
189 /** Which provider, by name, for its quirks. */
190 provider: string;
191 workspace: string;
192 repo: string;
193 number: number;
194 task: string;
195 /** The session's id; see `ModelSession.id`. */
196 session: string;
197 /** For `g1t`: the tier the run was routed to. */
198 tier?: ModelTier | null;
199 /** The person the run is for, by username. Null when nobody asked; never `g1t`. */
200 requestedBy: string | null;
201 baseUrl: string | null;
202 apiKey: string | null;
203 authHeader: string | null;
204 /** For an endpoint behind an authenticated Cloudflare AI Gateway: its token. */
205 gatewayToken?: string | null;
206};
207
208export type ConnectInput = {
209 provider: Provider;
210 name?: string;
211 config?: ConnectionConfig;
212 secret?: string;
213 signingSecret?: string;
214};
215
216export type UpdateConnectionInput = {
217 name?: string;
218 config?: ConnectionConfig;
219 secret?: string;
220 signingSecret?: string;
221};
222
223export interface IntegrationsApi {
224 list(workspace: string, viewer: Viewer): Promise<Result<Connection[]>>;
225 connect(actor: User, workspace: string, input: ConnectInput): Promise<Result<Connected>>;
226 update(actor: User, workspace: string, id: string, input: UpdateConnectionInput): Promise<Result<Connection>>;
227 disconnect(actor: User, workspace: string, id: string): Promise<Result<boolean>>;
228 test(actor: User, workspace: string, id: string): Promise<Result<{ ok: boolean; message: string }>>;
229 deliveries(workspace: string, viewer: Viewer, id: string): Promise<Result<Delivery[]>>;
230 resolve(workspace: string, viewer: Viewer, reference: string): Promise<Result<ContextItem>>;
231 /** For g1t's agents: what `text` refers to outside g1t, fetched. */
232 references(workspace: string, text: string, limit?: number): Promise<ContextItem[]>;
233 import(actor: User, repo: RepoPath, reference: string, assign: boolean): Promise<Result<{ number: number; item: ContextItem; created: boolean }>>;
234 links(repo: RepoPath, number: number): Promise<Link[]>;
235 modelProvider(workspace: string): Promise<Connection | null>;
236 openModelSession(run: {
237 workspace: string;
238 repo: RepoPath;
239 number: number;
240 task: string;
241 hostedOpen: boolean;
242 /** The tier the run is routed to on g1t's hosted models, for the gateway's logs. */
243 tier?: ModelTier | null;
244 /** The person the run is for, by username, so its tokens show under them. */
245 requestedBy?: string | null;
246 }): Promise<Result<ModelSession>>;
247 routes(workspace: string, viewer: Viewer): Promise<Result<ModelRoute[]>>;
248 setRoutes(actor: User, workspace: string, routes: ModelRoute[]): Promise<Result<ModelRoute[]>>;
249 modelUpstream(token: string): Promise<ModelUpstream | null>;
250 /**
251 * Where a workspace's AI Gateway requests go on its own key: its first
252 * Anthropic or Anthropic-compatible model provider, with task `gateway`.
253 * Null sends them to g1t's models.
254 */
255 gatewayUpstream(workspace: string): Promise<ModelUpstream | null>;
256 /**
257 * The workspace's own model providers, in the order they were connected,
258 * with their keys and which AI Gateway models each takes. For the model
259 * proxy only.
260 */
261 gatewayProviders(workspace: string): Promise<GatewayProvider[]>;
262 /**
263 * Ends the model sessions whose tokens hash to these (SHA-256, lowercase
264 * hex) when their run finishes, so the tokens stop working then. Returns
265 * how many were open.
266 */
267 closeModelSessions(tokenHashes: string[]): Promise<number>;
268}
269
270/** What each provider is for, as people choose between them. */
271export const PROVIDERS: Record<Provider, { label: string; kind: ProviderKind }> = {
272 anthropic: { label: "Anthropic", kind: "models" },
273 openai: { label: "OpenAI", kind: "models" },
274 gemini: { label: "Google Gemini", kind: "models" },
275 xai: { label: "xAI", kind: "models" },
276 mistral: { label: "Mistral", kind: "models" },
277 deepseek: { label: "DeepSeek", kind: "models" },
278 azure_openai: { label: "Azure OpenAI", kind: "models" },
279 openrouter: { label: "OpenRouter", kind: "models" },
280 groq: { label: "Groq", kind: "models" },
281 together: { label: "Together AI", kind: "models" },
282 fireworks: { label: "Fireworks AI", kind: "models" },
283 cerebras: { label: "Cerebras", kind: "models" },
284 anthropic_endpoint: { label: "Anthropic-compatible endpoint", kind: "models" },
285 openai_endpoint: { label: "OpenAI-compatible endpoint", kind: "models" },
286 sentry: { label: "Sentry", kind: "alerts" },
287 datadog: { label: "Datadog", kind: "alerts" },
288 webhook: { label: "Webhook", kind: "alerts" },
289 jira: { label: "Jira", kind: "tracker" },
290 linear: { label: "Linear", kind: "tracker" },
291};