Skip to content
823 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Usage while free is shown at cost; agents get rustfmt and clippy1//! The integrations service: a workspace's connections to systems outside
2//! g1t, and everything that crosses between them.
3//!
4//! - **Models.** A workspace connects as many model providers as it uses
5//! (Anthropic, OpenAI, Gemini, and anything compatible with either API)
6//! and routes each kind of work to one of them, or to g1t's hosted models.
7//! Sandboxes never hold a key: they hold a token for one run, and the
8//! model proxy puts the credentials on each request, translating to
9//! OpenAI's API where the provider speaks it.
10//! - **Alerts.** Sentry, Datadog or any signed webhook opens an issue in a
11//! repository, once per problem however often it fires, and can put an
12//! agent on it.
13//! - **Trackers.** A Jira or Linear key, such as `TECH-1234`, resolves to the
14//! ticket: agents read it, people import it as an issue, and when the work
15//! lands the ticket is told.
16//!
17//! Mirrors `packages/contracts/src/integrations.ts`.
18
19use serde::{Deserialize, Serialize};
20
21use crate::repos::RepoPath;
22use crate::{User, Viewer};
23
24/// Which outside system a connection is to.
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27pub enum Provider {
28 // Model providers: the labs.
29 Anthropic,
30 Openai,
31 /// Through Gemini's OpenAI-compatible endpoint.
32 Gemini,
33 Xai,
34 Mistral,
35 Deepseek,
36 // Model providers: platforms that serve many labs' models.
37 /// A deployment on the workspace's own Azure OpenAI resource.
38 AzureOpenai,
39 Openrouter,
40 Groq,
41 Together,
42 Fireworks,
43 Cerebras,
44 // Model providers: anything else.
45 /// Any endpoint that speaks Anthropic's Messages API: the workspace's
46 /// own Cloudflare AI Gateway, LiteLLM, a proxy in front of Bedrock or
47 /// Vertex, or a self-hosted model.
48 AnthropicEndpoint,
49 /// Any endpoint that speaks OpenAI's Chat Completions API: vLLM,
50 /// Ollama behind a tunnel, LiteLLM, a gateway.
51 OpenaiEndpoint,
52 // Alerts.
53 Sentry,
54 Datadog,
55 /// Anything that can send a signed JSON request.
56 Webhook,
57 // Trackers.
58 Jira,
59 Linear,
60}
61
62/// What g1t knows about a provider.
63pub struct Spec {
64 pub provider: Provider,
65 pub name: &'static str,
66 pub label: &'static str,
67 pub kind: ProviderKind,
68 /// For a model provider: the API it speaks, `anthropic` or `openai`.
69 pub api: &'static str,
70 /// For a model provider with a fixed address: where its API is, with
71 /// the version for OpenAI's API and without it for Anthropic's. Empty
72 /// when the connection gives its own.
73 pub base_url: &'static str,
74 /// The header the key goes in; `authorization` means `Bearer <key>`.
75 pub auth_header: &'static str,
76}
77
78const fn model(provider: Provider, name: &'static str, label: &'static str, api: &'static str, base_url: &'static str, auth_header: &'static str) -> Spec {
79 Spec {
80 provider,
81 name,
82 label,
83 kind: ProviderKind::Models,
84 api,
85 base_url,
86 auth_header,
87 }
88}
89
90const fn other(provider: Provider, name: &'static str, label: &'static str, kind: ProviderKind) -> Spec {
91 Spec {
92 provider,
93 name,
94 label,
95 kind,
96 api: "",
97 base_url: "",
98 auth_header: "",
99 }
100}
101
102/// Every provider, in the order people are shown them.
103pub const PROVIDERS: [Spec; 19] = [
104 model(Provider::Anthropic, "anthropic", "Anthropic", "anthropic", "https://api.anthropic.com", "x-api-key"),
105 model(Provider::Openai, "openai", "OpenAI", "openai", "https://api.openai.com/v1", "authorization"),
106 model(Provider::Gemini, "gemini", "Google Gemini", "openai", "https://generativelanguage.googleapis.com/v1beta/openai", "authorization"),
107 model(Provider::Xai, "xai", "xAI", "openai", "https://api.x.ai/v1", "authorization"),
108 model(Provider::Mistral, "mistral", "Mistral", "openai", "https://api.mistral.ai/v1", "authorization"),
109 model(Provider::Deepseek, "deepseek", "DeepSeek", "openai", "https://api.deepseek.com/v1", "authorization"),
110 model(Provider::AzureOpenai, "azure_openai", "Azure OpenAI", "openai", "", "api-key"),
111 model(Provider::Openrouter, "openrouter", "OpenRouter", "openai", "https://openrouter.ai/api/v1", "authorization"),
112 model(Provider::Groq, "groq", "Groq", "openai", "https://api.groq.com/openai/v1", "authorization"),
113 model(Provider::Together, "together", "Together AI", "openai", "https://api.together.xyz/v1", "authorization"),
114 model(Provider::Fireworks, "fireworks", "Fireworks AI", "openai", "https://api.fireworks.ai/inference/v1", "authorization"),
115 model(Provider::Cerebras, "cerebras", "Cerebras", "openai", "https://api.cerebras.ai/v1", "authorization"),
116 model(Provider::AnthropicEndpoint, "anthropic_endpoint", "Anthropic-compatible endpoint", "anthropic", "", "x-api-key"),
117 model(Provider::OpenaiEndpoint, "openai_endpoint", "OpenAI-compatible endpoint", "openai", "", "authorization"),
118 other(Provider::Sentry, "sentry", "Sentry", ProviderKind::Alerts),
119 other(Provider::Datadog, "datadog", "Datadog", ProviderKind::Alerts),
120 other(Provider::Webhook, "webhook", "Webhook", ProviderKind::Alerts),
121 other(Provider::Jira, "jira", "Jira", ProviderKind::Tracker),
122 other(Provider::Linear, "linear", "Linear", ProviderKind::Tracker),
123];
124
125impl Provider {
126 pub fn spec(self) -> &'static Spec {
127 PROVIDERS
128 .iter()
129 .find(|spec| spec.provider == self)
130 .expect("every provider is in the catalogue")
131 }
132
133 pub fn all() -> impl Iterator<Item = Provider> {
134 PROVIDERS.iter().map(|spec| spec.provider)
135 }
136
137 pub fn name(self) -> &'static str {
138 self.spec().name
139 }
140
141 pub fn parse(name: &str) -> Option<Provider> {
142 PROVIDERS.iter().find(|spec| spec.name == name).map(|spec| spec.provider)
143 }
144
145 /// What people call it.
146 pub fn label(self) -> &'static str {
147 self.spec().label
148 }
149
150 pub fn kind(self) -> ProviderKind {
151 self.spec().kind
152 }
153
154 /// Whether it sends g1t requests, at the connection's own address.
155 pub fn receives(self) -> bool {
156 self.kind() == ProviderKind::Alerts
157 }
158
159 /// For a model provider, the API it speaks: `anthropic` or `openai`.
160 pub fn api(self) -> &'static str {
161 self.spec().api
162 }
163
164 /// Whether the connection gives the address, rather than g1t knowing it.
165 pub fn own_address(self) -> bool {
166 self.kind() == ProviderKind::Models && self.spec().base_url.is_empty()
167 }
168}
169
170/// The kinds of work a model is chosen for, and `default` for the rest.
171pub const MODEL_TASKS: [&str; 5] = ["default", "implement", "review", "plan", "update"];
172
Merge branch 'model-routing'173/// The tiers g1t routes its hosted models' work to, cheapest first: `small`
174/// (fast), `large` (standard) and `frontier` (most capable). A route to
175/// g1t's models may name one instead of leaving the choice to Auto.
176pub const MODEL_TIERS: [&str; 3] = ["small", "large", "frontier"];
177
Usage while free is shown at cost; agents get rustfmt and clippy178#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum ProviderKind {
181 /// Where agents' model requests go. A workspace can have several and
182 /// routes each kind of work to one.
183 Models,
184 /// Problems that become issues.
185 Alerts,
186 /// Tickets that agents read and people import.
187 Tracker,
188}
189
190/// A connection's settings: everything about it except its secrets. Each
191/// provider uses the fields that apply to it.
192#[derive(Clone, Debug, Serialize, Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct ConnectionConfig {
195 /// For alerts: the repository issues are opened in, `owner/name`. For a
196 /// tracker: where an imported ticket goes when no repository is named.
197 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub repo: Option<String>,
199 /// For alerts: put a g1t agent on each issue opened.
200 #[serde(default)]
201 pub assign: bool,
202 /// For alerts: the label put on each issue opened. `bug` when unset.
203 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub label: Option<String>,
205 /// Tell the outside system when the work lands: resolve the Sentry
206 /// issue, comment on the ticket.
207 #[serde(default = "yes")]
208 pub write_back: bool,
209 /// Sentry: the organization's slug.
210 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub organization: Option<String>,
212 /// The system's address, for Jira (`https://acme.atlassian.net`) or a
213 /// Sentry that is not sentry.io.
214 #[serde(default, skip_serializing_if = "Option::is_none")]
215 pub site: Option<String>,
216 /// Jira: the account the API token belongs to.
217 #[serde(default, skip_serializing_if = "Option::is_none")]
218 pub email: Option<String>,
219 /// Jira project keys or Linear team keys this connection answers for,
220 /// such as `TECH`. Empty answers for every key.
221 #[serde(default, skip_serializing_if = "Vec::is_empty")]
222 pub keys: Vec<String>,
223 /// Your own endpoint: its base URL, without `/v1`.
224 #[serde(default, skip_serializing_if = "Option::is_none")]
225 pub base_url: Option<String>,
226 /// Your own endpoint: send the key as `x-api-key` (the default) or as
227 /// `authorization: Bearer`.
228 #[serde(default, skip_serializing_if = "Option::is_none")]
229 pub auth_header: Option<String>,
230 /// Models: the model used when a route to this connection names none.
231 /// Required for providers that speak OpenAI's API; for Anthropic, g1t's
232 /// choice for the kind of work when unset.
233 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub model: Option<String>,
AI Gateway: OpenAI's format, open models, and your own providers235 /// Models: which AI Gateway requests go to this connection, by the
236 /// model they name. Each is a model id (`gpt-5.5`), or a prefix ending
237 /// in `*` (`gpt-*`, `ollama/*`, or `*` for every model). A prefix that
238 /// ends in `/*` is taken off before the request is sent, so
239 /// `ollama/llama3.3` reaches the endpoint as `llama3.3`. Absent: Claude
240 /// models (`claude-*`) on an Anthropic key or Anthropic-compatible
241 /// endpoint, and nothing on the others. Empty: nothing.
242 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub gateway_models: Option<Vec<String>>,
Usage while free is shown at cost; agents get rustfmt and clippy244}
245
AI Gateway: OpenAI's format, open models, and your own providers246impl ConnectionConfig {
247 /// The AI Gateway models this connection takes: its own list, or its
248 /// provider's default.
249 pub fn gateway_patterns(&self, provider: Provider) -> Vec<String> {
250 match &self.gateway_models {
251 Some(models) => models.clone(),
252 None if provider.kind() == ProviderKind::Models && provider.api() == "anthropic" => vec!["claude-*".to_owned()],
253 None => Vec::new(),
254 }
255 }
256}
257
258/// The most models a connection can list for the AI Gateway, and how long
259/// each may be.
260pub const GATEWAY_MODELS_MAX: usize = 100;
261pub const GATEWAY_MODEL_LEN: usize = 200;
262
263/// Tidies a connection's AI Gateway models, or says what is wrong.
264pub fn tidy_gateway_models(models: &[String]) -> std::result::Result<Vec<String>, String> {
265 let mut tidy: Vec<String> = Vec::new();
266 for model in models {
267 let model = model.trim();
268 if model.is_empty() {
269 continue;
270 }
271 if model.len() > GATEWAY_MODEL_LEN || model.chars().any(|c| c.is_whitespace() || c.is_control()) {
272 return Err(format!("{model} is not a model id: one word of at most {GATEWAY_MODEL_LEN} characters, such as gpt-5.5 or gpt-*."));
273 }
274 if model.contains('*') && !model.ends_with('*') || model.matches('*').count() > 1 {
275 return Err(format!("{model}: a * can only end a model id, as in gpt-*."));
276 }
277 if !tidy.iter().any(|seen| seen == model) {
278 tidy.push(model.to_owned());
279 }
280 }
281 if tidy.len() > GATEWAY_MODELS_MAX {
282 return Err(format!("A connection can take at most {GATEWAY_MODELS_MAX} AI Gateway models."));
283 }
284 Ok(tidy)
285}
286
Usage while free is shown at cost; agents get rustfmt and clippy287fn yes() -> bool {
288 true
289}
290
291impl Default for ConnectionConfig {
292 fn default() -> Self {
293 ConnectionConfig {
294 repo: None,
295 assign: false,
296 label: None,
297 write_back: true,
298 organization: None,
299 site: None,
300 email: None,
301 keys: Vec::new(),
302 base_url: None,
303 auth_header: None,
304 model: None,
AI Gateway: OpenAI's format, open models, and your own providers305 gateway_models: None,
Usage while free is shown at cost; agents get rustfmt and clippy306 }
307 }
308}
309
310/// A connection, as anyone in the workspace sees it. Secrets are never
311/// shown after they are saved; `secretHint` is enough to tell keys apart.
312#[derive(Clone, Debug, Serialize, Deserialize)]
313#[serde(rename_all = "camelCase")]
314pub struct Connection {
315 pub id: String,
316 pub workspace: String,
317 pub provider: Provider,
318 pub kind: ProviderKind,
319 pub name: String,
320 pub config: ConnectionConfig,
321 /// The last four characters of the saved key, such as `…3f9a`.
322 pub secret_hint: Option<String>,
323 /// For a provider that sends g1t requests: where it sends them.
324 pub webhook_url: Option<String>,
325 pub created_by: String,
326 /// RFC 3339.
327 pub created_at: String,
328 pub last_used_at: Option<String>,
329 /// The last thing that went wrong talking to it, until it next works.
330 pub last_error: Option<String>,
331 /// For a model provider: the models it offered when last checked.
332 #[serde(default)]
333 pub models: Vec<String>,
334}
335
336/// Where one kind of work's model requests go in a workspace.
337#[derive(Clone, Debug, Serialize, Deserialize)]
338#[serde(rename_all = "camelCase")]
339pub struct ModelRoute {
340 /// One of [`MODEL_TASKS`].
341 pub task: String,
342 /// The workspace's own model connection, or `None` for g1t's hosted
343 /// models.
344 pub connection_id: Option<String>,
Merge branch 'model-routing'345 /// The model at that connection; its default model when `None`. On
346 /// g1t's hosted models, one of [`MODEL_TIERS`], or `None` for Auto.
Usage while free is shown at cost; agents get rustfmt and clippy347 pub model: Option<String>,
348}
349
350/// One request an outside system sent, and what g1t did with it.
351#[derive(Clone, Debug, Serialize, Deserialize)]
352#[serde(rename_all = "camelCase")]
353pub struct Delivery {
354 pub id: String,
355 /// RFC 3339.
356 pub received_at: String,
357 /// What it was about, in the sender's terms: `issue.created`.
358 pub event: String,
359 /// `opened`, `updated`, `reopened`, `ignored` or `refused`.
360 pub outcome: String,
361 pub detail: String,
362 /// The issue it opened or updated, `owner/name#number`.
363 pub issue: Option<String>,
364}
365
366/// Something outside g1t, fetched as it is now. Its text was written outside
367/// g1t, so it is reference material and never instructions.
368#[derive(Clone, Debug, Serialize, Deserialize)]
369#[serde(rename_all = "camelCase")]
370pub struct ContextItem {
371 pub provider: Provider,
372 /// `TECH-1234`, or the Sentry issue's short id.
373 pub key: String,
374 pub title: String,
375 pub url: String,
376 /// Its status in that system: `In Progress`, `unresolved`.
377 pub status: Option<String>,
378 /// Its description, as plain text, shortened if long.
379 pub body: String,
380 /// RFC 3339: when g1t fetched it.
381 pub fetched_at: String,
382}
383
384/// An issue's tie to something outside g1t.
385#[derive(Clone, Debug, Serialize, Deserialize)]
386#[serde(rename_all = "camelCase")]
387pub struct Link {
388 pub provider: Provider,
389 pub connection_id: String,
390 pub key: String,
391 pub title: String,
392 pub url: String,
393 /// How many times an alert has fired for it.
394 pub count: u32,
395 /// RFC 3339.
396 pub first_seen: String,
397 pub last_seen: String,
398}
399
400/// Where a workspace's agents' model requests go.
401#[derive(Clone, Debug, Serialize, Deserialize)]
402#[serde(rename_all = "camelCase")]
403pub struct ModelSession {
404 /// What the sandbox sends instead of a key. Lives as long as one run.
405 pub token: String,
406 /// `g1t` when g1t pays the provider and charges the workspace,
407 /// `workspace` when the workspace's own account does.
408 pub billed_to: String,
409 /// The connection's name, when it is the workspace's own.
410 pub provider_name: Option<String>,
411 /// The model to use instead of g1t's choice, if the connection names one.
412 pub model: Option<String>,
Prices keep themselves current with what g1t pays413 /// Names the run in AI Gateway's logs (`metadata.session`), so billing
Merge branch 'model-routing'414 /// can charge each run what the gateway priced its requests at, and its
415 /// tokens in billing's count (the agent rate). Not a secret: it cannot
416 /// be turned back into the token.
Prices keep themselves current with what g1t pays417 #[serde(default)]
418 pub id: String,
Merge branch 'model-routing'419 /// The tier the workspace chose for this work on g1t's models instead
420 /// of Auto: the run goes there. `None` for Auto and on its own
421 /// providers.
422 #[serde(default)]
423 pub tier_choice: Option<String>,
Usage while free is shown at cost; agents get rustfmt and clippy424}
425
426/// What the model proxy needs to forward one run's requests.
427#[derive(Clone, Debug, Serialize, Deserialize)]
428#[serde(rename_all = "camelCase")]
429pub struct ModelUpstream {
430 /// `g1t`, `anthropic` or `endpoint`.
431 pub route: String,
432 /// The API the provider speaks: `anthropic` or `openai`, which the proxy
433 /// translates to.
434 pub api: String,
435 /// The model every request of the run is sent to, when the route names
436 /// one.
437 pub model: Option<String>,
438 /// For `openai`: OpenAI's own API, which shapes requests its own way.
439 pub official: bool,
440 /// Which provider it is, by name, so the proxy can meet its quirks.
441 #[serde(default)]
442 pub provider: String,
443 pub workspace: String,
444 pub repo: String,
445 pub number: u32,
446 pub task: String,
Prices keep themselves current with what g1t pays447 /// The session's id; see `ModelSession::id`.
448 #[serde(default)]
449 pub session: String,
Auto model routing: the cheapest tier that can do each piece of work, a retry goes up a tier, and each run records its tier450 /// For `g1t`: the tier the run was routed to, `small` or `large`.
451 #[serde(default)]
452 pub tier: Option<String>,
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix453 /// The person the run is for, by username: who asked g1t for the work.
454 /// Null when nobody did. Never `g1t`, the agent itself.
455 #[serde(default)]
456 pub requested_by: Option<String>,
Usage while free is shown at cost; agents get rustfmt and clippy457 /// For `endpoint`: where to send requests.
458 pub base_url: Option<String>,
459 /// For `anthropic` and `endpoint`: the workspace's key.
460 pub api_key: Option<String>,
461 /// `x-api-key` or `authorization`.
462 pub auth_header: Option<String>,
463 /// For an endpoint behind an authenticated Cloudflare AI Gateway: the
464 /// gateway's own token, sent as `cf-aig-authorization`.
465 #[serde(default)]
466 pub gateway_token: Option<String>,
Merge platform pause and the hourly usage watcher: staff can pause compute, schedules, indexing or renders for everyone, the watcher emails on a breach and is never blind quietly, and the models proxy holds each run to its cap (billing 0051, integrations 0006)467 /// The most the run may spend on models, in millionths of a dollar: the
468 /// lower of its project's cost cap and its plan's. The proxy refuses
469 /// the run's requests once it has spent this. `None` until the sandbox
470 /// sets it (`cap_model_sessions`), and for the AI Gateway.
471 #[serde(default)]
472 pub cap_micros: Option<i64>,
Usage while free is shown at cost; agents get rustfmt and clippy473}
474
475// --- Methods -----------------------------------------------------------------
476
477/// `list`. Returns `Outcome<Vec<Connection>>`. Members only.
478#[derive(Debug, Serialize, Deserialize)]
479pub struct ListArgs {
480 pub workspace: String,
481 pub viewer: Viewer,
482}
483
484/// `connect`. Returns `Outcome<Connected>`. Owners only.
485#[derive(Debug, Serialize, Deserialize)]
486#[serde(rename_all = "camelCase")]
487pub struct ConnectArgs {
488 pub actor: User,
489 pub workspace: String,
490 pub provider: Provider,
491 #[serde(default)]
492 pub name: Option<String>,
493 #[serde(default)]
494 pub config: ConnectionConfig,
495 /// The API key or token g1t uses to call it.
496 #[serde(default)]
497 pub secret: Option<String>,
498 /// What it signs its requests to g1t with: Sentry's client secret.
499 /// Made by g1t for Datadog and webhooks, and shown once. For a model
500 /// endpoint behind an authenticated Cloudflare AI Gateway, the gateway's
501 /// token.
502 #[serde(default)]
503 pub signing_secret: Option<String>,
504}
505
506#[derive(Clone, Debug, Serialize, Deserialize)]
507#[serde(rename_all = "camelCase")]
508pub struct Connected {
509 pub connection: Connection,
510 /// A signing secret g1t made, shown this once.
511 pub signing_secret: Option<String>,
512}
513
514/// `update`: only the fields given change. Returns `Outcome<Connection>`.
515/// Owners only.
516#[derive(Debug, Serialize, Deserialize)]
517#[serde(rename_all = "camelCase")]
518pub struct UpdateArgs {
519 pub actor: User,
520 pub workspace: String,
521 pub id: String,
522 #[serde(default)]
523 pub name: Option<String>,
524 #[serde(default)]
525 pub config: Option<ConnectionConfig>,
526 #[serde(default)]
527 pub secret: Option<String>,
528 #[serde(default)]
529 pub signing_secret: Option<String>,
530}
531
532/// `disconnect` and `test`. `disconnect` returns `Outcome<bool>`; `test`
533/// returns `Outcome<Tested>`. Owners only.
534#[derive(Debug, Serialize, Deserialize)]
535pub struct ConnectionArgs {
536 pub actor: User,
537 pub workspace: String,
538 pub id: String,
539}
540
541#[derive(Clone, Debug, Serialize, Deserialize)]
542pub struct Tested {
543 pub ok: bool,
544 pub message: String,
545}
546
547/// `deliveries`: the latest requests a connection received, newest first.
548/// Returns `Outcome<Vec<Delivery>>`. Members only.
549#[derive(Debug, Serialize, Deserialize)]
550pub struct DeliveriesArgs {
551 pub workspace: String,
552 pub viewer: Viewer,
553 pub id: String,
554}
555
556/// `receive`: a request an outside system sent to a connection's address.
557/// Returns `Received`. Anyone can send one; only a signed one is acted on.
558#[derive(Debug, Serialize, Deserialize)]
559pub struct ReceiveArgs {
560 pub id: String,
561 /// Header names in lowercase.
562 pub headers: std::collections::HashMap<String, String>,
563 pub body: String,
564}
565
566#[derive(Clone, Debug, Serialize, Deserialize)]
567pub struct Received {
568 /// The HTTP status to answer with.
569 pub status: u16,
570 pub message: String,
571}
572
573/// `resolve`: fetches one outside reference. Returns `Outcome<ContextItem>`.
574/// Members of the workspace only.
575#[derive(Debug, Serialize, Deserialize)]
576pub struct ResolveArgs {
577 pub workspace: String,
578 pub viewer: Viewer,
579 /// `TECH-1234`, or a Jira, Linear or Sentry address.
580 pub reference: String,
581}
582
583/// `references`: every outside reference in `text` that one of the
584/// workspace's connections answers for, fetched. Returns `Vec<ContextItem>`.
585/// For g1t's own agents, about work in that workspace.
586#[derive(Debug, Serialize, Deserialize)]
587pub struct ReferencesArgs {
588 pub workspace: String,
589 pub text: String,
590 #[serde(default)]
591 pub limit: Option<u32>,
592}
593
Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how.594/// `comment`: for g1t's agents, a comment on the item `reference` names in
595/// the system that knows it (Linear, Jira or Sentry), on the workspace's
596/// connection, with `link` (a g1t address) at the end. The actor is the
597/// person the agent acts for. Returns `Outcome<ContextItem>`.
598#[derive(Debug, Serialize, Deserialize)]
599pub struct CommentArgs {
600 pub actor: User,
601 pub workspace: String,
602 pub reference: String,
603 pub text: String,
604 pub link: String,
605}
606
607/// `close`: for g1t's agents, marks a Sentry issue resolved with a note and
608/// `link`. Only Sentry items can be closed. Returns `Outcome<ContextItem>`.
609#[derive(Debug, Serialize, Deserialize)]
610pub struct CloseArgs {
611 pub actor: User,
612 pub workspace: String,
613 pub reference: String,
614 pub text: String,
615 pub link: String,
616}
617
Usage while free is shown at cost; agents get rustfmt and clippy618/// `import`: opens an issue from a ticket. Returns `Outcome<Imported>`.
619#[derive(Debug, Serialize, Deserialize)]
620pub struct ImportArgs {
621 pub actor: User,
622 pub repo: RepoPath,
623 pub reference: String,
624 /// Put a g1t agent on it.
625 #[serde(default)]
626 pub assign: bool,
627}
628
629#[derive(Clone, Debug, Serialize, Deserialize)]
630pub struct Imported {
631 pub number: u32,
632 pub item: ContextItem,
633 /// False when the ticket had been imported already, and `number` is
634 /// that issue.
635 pub created: bool,
636}
637
638/// `links`: what an issue is tied to outside g1t. Returns `Vec<Link>`.
639/// Callers must have checked the viewer may see the issue.
640#[derive(Debug, Serialize, Deserialize)]
641#[serde(rename_all = "camelCase")]
642pub struct LinksArgs {
643 pub repo: RepoPath,
644 pub number: u32,
645}
646
647/// `open_model_session`: where one run's model requests go, by the
648/// workspace's routes. Returns `Outcome<ModelSession>`: a failure, with the
649/// reason to show, when the route goes nowhere it can use.
650#[derive(Debug, Serialize, Deserialize)]
651#[serde(rename_all = "camelCase")]
652pub struct OpenModelSessionArgs {
653 pub workspace: String,
654 pub repo: RepoPath,
655 pub number: u32,
656 pub task: String,
657 /// Whether g1t's hosted models are open to the workspace. The runner
658 /// decides that; this service only follows the routes.
659 #[serde(default = "yes")]
660 pub hosted_open: bool,
Merge branch 'model-routing'661 /// `small`, `large` or `frontier`: the tier the runner routed the run
662 /// to on g1t's hosted models, tagged on its requests at the gateway.
663 /// Kept only when the run goes to g1t's models, where a tier the
664 /// workspace chose for the work takes its place.
Auto model routing: the cheapest tier that can do each piece of work, a retry goes up a tier, and each run records its tier665 #[serde(default)]
666 pub tier: Option<String>,
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix667 /// The person the run is for, by username, so usage can be shown per
668 /// person. Null when nobody asked; `g1t`, the agent, is kept as null.
669 #[serde(default)]
670 pub requested_by: Option<String>,
Usage while free is shown at cost; agents get rustfmt and clippy671}
672
673/// `routes`: a workspace's model routes, one per kind of work that has its
674/// own. Returns `Outcome<Vec<ModelRoute>>`. Members only.
675#[derive(Debug, Serialize, Deserialize)]
676pub struct RoutesArgs {
677 pub workspace: String,
678 pub viewer: Viewer,
679}
680
681/// `set_routes`: replaces a workspace's model routes. A kind of work left
682/// out follows `default`; with no `default`, g1t's hosted models where they
683/// are open. Returns `Outcome<Vec<ModelRoute>>`. Owners only.
684#[derive(Debug, Serialize, Deserialize)]
685pub struct SetRoutesArgs {
686 pub actor: User,
687 pub workspace: String,
688 pub routes: Vec<ModelRoute>,
689}
690
691/// `model_upstream`: what a model session's token stands for, or null when
692/// it is unknown or expired. Returns `Option<ModelUpstream>`.
693#[derive(Debug, Serialize, Deserialize)]
694pub struct ModelUpstreamArgs {
695 pub token: String,
696}
697
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens698/// `gateway_upstream`: where a workspace's AI Gateway requests go when it
699/// has its own Anthropic key: its first Anthropic or Anthropic-compatible
700/// model provider, with the credentials to forward them. Null sends them to
701/// g1t's models. Returns `Option<ModelUpstream>`, with `task` `gateway`.
702#[derive(Debug, Serialize, Deserialize)]
703pub struct GatewayUpstreamArgs {
704 pub workspace: String,
705}
706
AI Gateway: OpenAI's format, open models, and your own providers707/// `gateway_providers`: the workspace's own model providers, in the order
708/// they were connected, with what the AI Gateway needs to send requests to
709/// each and which models it takes. Returns `Vec<GatewayProvider>`. For the
710/// model proxy only: it carries keys.
711#[derive(Debug, Serialize, Deserialize)]
712pub struct GatewayProvidersArgs {
713 pub workspace: String,
714}
715
716/// One of a workspace's own model providers, for the AI Gateway.
717#[derive(Clone, Debug, Serialize, Deserialize)]
718#[serde(rename_all = "camelCase")]
719pub struct GatewayProvider {
720 /// The connection's id and name.
721 pub id: String,
722 pub name: String,
723 /// The provider, by name: `anthropic`, `openai`, `openai_endpoint`…
724 pub provider: String,
725 /// The API it speaks: `anthropic` or `openai`.
726 pub api: String,
727 /// OpenAI's own API (or Azure's), which shapes requests its own way.
728 pub official: bool,
729 /// Where requests go: without `/v1` for Anthropic's API, with it for OpenAI's.
730 pub base_url: String,
731 pub api_key: Option<String>,
732 /// `x-api-key`, `authorization` (as `Bearer`) or `api-key`.
733 pub auth_header: String,
734 /// For an endpoint behind an authenticated Cloudflare AI Gateway.
735 #[serde(default)]
736 pub gateway_token: Option<String>,
737 /// Which models it takes: ids and `*` prefixes (see
738 /// `ConnectionConfig::gateway_models`).
739 pub patterns: Vec<String>,
740 /// The models the provider listed when last checked.
741 #[serde(default)]
742 pub models: Vec<String>,
743}
744
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily745/// `close_model_sessions`: ends the model sessions whose tokens hash to
746/// these (SHA-256, lowercase hex), so a run's model token stops working
747/// when its run does rather than when it would lapse. Returns how many
748/// were open.
749#[derive(Debug, Serialize, Deserialize)]
750pub struct CloseModelSessionsArgs {
751 #[serde(alias = "tokenHashes")]
752 pub token_hashes: Vec<String>,
753}
754
Merge platform pause and the hourly usage watcher: staff can pause compute, schedules, indexing or renders for everyone, the watcher emails on a breach and is never blind quietly, and the models proxy holds each run to its cap (billing 0051, integrations 0006)755/// `cap_model_sessions`: sets the most the runs whose model tokens hash to
756/// these (SHA-256, lowercase hex) may spend on models, in millionths of a
757/// dollar, which the model proxy holds them to. The sandbox sets it once
758/// it knows the run's guardrails and plan; zero or less clears it. Returns
759/// how many open sessions it set.
760#[derive(Debug, Serialize, Deserialize)]
761pub struct CapModelSessionsArgs {
762 #[serde(alias = "tokenHashes")]
763 pub token_hashes: Vec<String>,
764 #[serde(alias = "capMicros")]
765 pub cap_micros: i64,
766}
767
Usage while free is shown at cost; agents get rustfmt and clippy768/// `model_provider`: the workspace's own model connection, if it has one.
769/// Returns `Option<Connection>`.
770#[derive(Debug, Serialize, Deserialize)]
771pub struct ModelProviderArgs {
772 pub workspace: String,
773}
774
775#[cfg(test)]
776mod tests {
777 use super::*;
778
779 #[test]
780 fn every_provider_is_named_once_and_found_again() {
781 let mut names: Vec<&str> = PROVIDERS.iter().map(|spec| spec.name).collect();
782 for provider in Provider::all() {
783 assert_eq!(Provider::parse(provider.name()), Some(provider));
784 }
785 names.sort();
786 names.dedup();
787 assert_eq!(names.len(), PROVIDERS.len());
788 }
789
790 #[test]
AI Gateway: OpenAI's format, open models, and your own providers791 fn gateway_models_default_to_claude_on_anthropic_and_nothing_elsewhere() {
792 let config = ConnectionConfig::default();
793 assert_eq!(config.gateway_patterns(Provider::Anthropic), ["claude-*"]);
794 assert_eq!(config.gateway_patterns(Provider::AnthropicEndpoint), ["claude-*"]);
795 assert!(config.gateway_patterns(Provider::Openai).is_empty());
796 assert!(config.gateway_patterns(Provider::OpenaiEndpoint).is_empty());
797 let chosen = ConnectionConfig { gateway_models: Some(vec!["gpt-*".into()]), ..ConnectionConfig::default() };
798 assert_eq!(chosen.gateway_patterns(Provider::Openai), ["gpt-*"]);
799 let none = ConnectionConfig { gateway_models: Some(Vec::new()), ..ConnectionConfig::default() };
800 assert!(none.gateway_patterns(Provider::Anthropic).is_empty());
801 }
802
803 #[test]
804 fn gateway_models_are_ids_or_prefixes() {
805 let tidy = tidy_gateway_models(&[" gpt-5.5 ".into(), "".into(), "ollama/*".into(), "gpt-5.5".into(), "*".into()]).unwrap();
806 assert_eq!(tidy, ["gpt-5.5", "ollama/*", "*"]);
807 assert!(tidy_gateway_models(&["gpt *".into()]).is_err());
808 assert!(tidy_gateway_models(&["g*t".into()]).is_err());
809 assert!(tidy_gateway_models(&["**".into()]).is_err());
810 assert!(tidy_gateway_models(&["x".repeat(201)]).is_err());
811 let many: Vec<String> = (0..101).map(|n| format!("m{n}")).collect();
812 assert!(tidy_gateway_models(&many).is_err());
813 }
814
815 #[test]
Usage while free is shown at cost; agents get rustfmt and clippy816 fn model_providers_say_how_to_reach_them() {
817 for spec in PROVIDERS.iter().filter(|spec| spec.kind == ProviderKind::Models) {
818 assert!(spec.api == "anthropic" || spec.api == "openai", "{}", spec.name);
819 assert!(!spec.auth_header.is_empty(), "{}", spec.name);
820 assert!(spec.base_url.is_empty() || spec.base_url.starts_with("https://"), "{}", spec.name);
821 }
822 }
823}

This file's history is long; its oldest lines are credited to the oldest commit read.