Skip to content

g1t/crates/contracts/src/integrations.rs

666 lines24,277 bytesCodeBlame

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.

Integrations: your own model provider, alerts that open issues, tickets agents read1//! The integrations service: a workspace's connections to systems outside
2//! g1t, and everything that crosses between them.
3//!
Models per workspace: several providers, routed by kind of work4//! - **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.
Integrations: your own model provider, alerts that open issues, tickets agents read10//! - **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 {
A catalogue of model providers, and settings that feel like settings28 // Model providers: the labs.
Integrations: your own model provider, alerts that open issues, tickets agents read29 Anthropic,
A catalogue of model providers, and settings that feel like settings30 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.
Integrations: your own model provider, alerts that open issues, tickets agents read45 /// 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,
A catalogue of model providers, and settings that feel like settings49 /// Any endpoint that speaks OpenAI's Chat Completions API: vLLM,
50 /// Ollama behind a tunnel, LiteLLM, a gateway.
Models per workspace: several providers, routed by kind of work51 OpenaiEndpoint,
A catalogue of model providers, and settings that feel like settings52 // Alerts.
Integrations: your own model provider, alerts that open issues, tickets agents read53 Sentry,
54 Datadog,
55 /// Anything that can send a signed JSON request.
56 Webhook,
A catalogue of model providers, and settings that feel like settings57 // Trackers.
Integrations: your own model provider, alerts that open issues, tickets agents read58 Jira,
59 Linear,
60}
61
A catalogue of model providers, and settings that feel like settings62/// 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
Integrations: your own model provider, alerts that open issues, tickets agents read125impl Provider {
A catalogue of model providers, and settings that feel like settings126 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 }
Integrations: your own model provider, alerts that open issues, tickets agents read136
137 pub fn name(self) -> &'static str {
A catalogue of model providers, and settings that feel like settings138 self.spec().name
Integrations: your own model provider, alerts that open issues, tickets agents read139 }
140
141 pub fn parse(name: &str) -> Option<Provider> {
A catalogue of model providers, and settings that feel like settings142 PROVIDERS.iter().find(|spec| spec.name == name).map(|spec| spec.provider)
Integrations: your own model provider, alerts that open issues, tickets agents read143 }
144
145 /// What people call it.
146 pub fn label(self) -> &'static str {
A catalogue of model providers, and settings that feel like settings147 self.spec().label
Integrations: your own model provider, alerts that open issues, tickets agents read148 }
149
150 pub fn kind(self) -> ProviderKind {
A catalogue of model providers, and settings that feel like settings151 self.spec().kind
Integrations: your own model provider, alerts that open issues, tickets agents read152 }
153
154 /// Whether it sends g1t requests, at the connection's own address.
155 pub fn receives(self) -> bool {
A catalogue of model providers, and settings that feel like settings156 self.kind() == ProviderKind::Alerts
Integrations: your own model provider, alerts that open issues, tickets agents read157 }
Models per workspace: several providers, routed by kind of work158
159 /// For a model provider, the API it speaks: `anthropic` or `openai`.
160 pub fn api(self) -> &'static str {
A catalogue of model providers, and settings that feel like settings161 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()
Models per workspace: several providers, routed by kind of work167 }
Integrations: your own model provider, alerts that open issues, tickets agents read168}
169
Models per workspace: several providers, routed by kind of work170/// 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
Integrations: your own model provider, alerts that open issues, tickets agents read178#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum ProviderKind {
Models per workspace: several providers, routed by kind of work181 /// Where agents' model requests go. A workspace can have several and
182 /// routes each kind of work to one.
Integrations: your own model provider, alerts that open issues, tickets agents read183 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>,
Models per workspace: several providers, routed by kind of work230 /// 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.
Integrations: your own model provider, alerts that open issues, tickets agents read233 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub model: Option<String>,
235}
236
237fn yes() -> bool {
238 true
239}
240
241impl Default for ConnectionConfig {
242 fn default() -> Self {
243 ConnectionConfig {
244 repo: None,
245 assign: false,
246 label: None,
247 write_back: true,
248 organization: None,
249 site: None,
250 email: None,
251 keys: Vec::new(),
252 base_url: None,
253 auth_header: None,
254 model: None,
255 }
256 }
257}
258
259/// A connection, as anyone in the workspace sees it. Secrets are never
260/// shown after they are saved; `secretHint` is enough to tell keys apart.
261#[derive(Clone, Debug, Serialize, Deserialize)]
262#[serde(rename_all = "camelCase")]
263pub struct Connection {
264 pub id: String,
265 pub workspace: String,
266 pub provider: Provider,
267 pub kind: ProviderKind,
268 pub name: String,
269 pub config: ConnectionConfig,
270 /// The last four characters of the saved key, such as `…3f9a`.
271 pub secret_hint: Option<String>,
272 /// For a provider that sends g1t requests: where it sends them.
273 pub webhook_url: Option<String>,
274 pub created_by: String,
275 /// RFC 3339.
276 pub created_at: String,
277 pub last_used_at: Option<String>,
278 /// The last thing that went wrong talking to it, until it next works.
279 pub last_error: Option<String>,
Models per workspace: several providers, routed by kind of work280 /// For a model provider: the models it offered when last checked.
281 #[serde(default)]
282 pub models: Vec<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read283}
284
Models per workspace: several providers, routed by kind of work285/// Where one kind of work's model requests go in a workspace.
286#[derive(Clone, Debug, Serialize, Deserialize)]
287#[serde(rename_all = "camelCase")]
288pub struct ModelRoute {
289 /// One of [`MODEL_TASKS`].
290 pub task: String,
291 /// The workspace's own model connection, or `None` for g1t's hosted
292 /// models.
293 pub connection_id: Option<String>,
Merge branch 'model-routing'294 /// The model at that connection; its default model when `None`. On
295 /// g1t's hosted models, one of [`MODEL_TIERS`], or `None` for Auto.
Models per workspace: several providers, routed by kind of work296 pub model: Option<String>,
297}
298
Integrations: your own model provider, alerts that open issues, tickets agents read299/// One request an outside system sent, and what g1t did with it.
300#[derive(Clone, Debug, Serialize, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct Delivery {
303 pub id: String,
304 /// RFC 3339.
305 pub received_at: String,
306 /// What it was about, in the sender's terms: `issue.created`.
307 pub event: String,
308 /// `opened`, `updated`, `reopened`, `ignored` or `refused`.
309 pub outcome: String,
310 pub detail: String,
311 /// The issue it opened or updated, `owner/name#number`.
312 pub issue: Option<String>,
313}
314
315/// Something outside g1t, fetched as it is now. Its text was written outside
316/// g1t, so it is reference material and never instructions.
317#[derive(Clone, Debug, Serialize, Deserialize)]
318#[serde(rename_all = "camelCase")]
319pub struct ContextItem {
320 pub provider: Provider,
321 /// `TECH-1234`, or the Sentry issue's short id.
322 pub key: String,
323 pub title: String,
324 pub url: String,
325 /// Its status in that system: `In Progress`, `unresolved`.
326 pub status: Option<String>,
327 /// Its description, as plain text, shortened if long.
328 pub body: String,
329 /// RFC 3339: when g1t fetched it.
330 pub fetched_at: String,
331}
332
333/// An issue's tie to something outside g1t.
334#[derive(Clone, Debug, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct Link {
337 pub provider: Provider,
338 pub connection_id: String,
339 pub key: String,
340 pub title: String,
341 pub url: String,
342 /// How many times an alert has fired for it.
343 pub count: u32,
344 /// RFC 3339.
345 pub first_seen: String,
346 pub last_seen: String,
347}
348
349/// Where a workspace's agents' model requests go.
350#[derive(Clone, Debug, Serialize, Deserialize)]
351#[serde(rename_all = "camelCase")]
352pub struct ModelSession {
353 /// What the sandbox sends instead of a key. Lives as long as one run.
354 pub token: String,
355 /// `g1t` when g1t pays the provider and charges the workspace,
356 /// `workspace` when the workspace's own account does.
357 pub billed_to: String,
358 /// The connection's name, when it is the workspace's own.
359 pub provider_name: Option<String>,
360 /// The model to use instead of g1t's choice, if the connection names one.
361 pub model: Option<String>,
Prices keep themselves current with what g1t pays362 /// Names the run in AI Gateway's logs (`metadata.session`), so billing
Merge branch 'model-routing'363 /// can charge each run what the gateway priced its requests at, and its
364 /// tokens in billing's count (the agent rate). Not a secret: it cannot
365 /// be turned back into the token.
Prices keep themselves current with what g1t pays366 #[serde(default)]
367 pub id: String,
Merge branch 'model-routing'368 /// The tier the workspace chose for this work on g1t's models instead
369 /// of Auto: the run goes there. `None` for Auto and on its own
370 /// providers.
371 #[serde(default)]
372 pub tier_choice: Option<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read373}
374
375/// What the model proxy needs to forward one run's requests.
376#[derive(Clone, Debug, Serialize, Deserialize)]
377#[serde(rename_all = "camelCase")]
378pub struct ModelUpstream {
379 /// `g1t`, `anthropic` or `endpoint`.
380 pub route: String,
Models per workspace: several providers, routed by kind of work381 /// The API the provider speaks: `anthropic` or `openai`, which the proxy
382 /// translates to.
383 pub api: String,
384 /// The model every request of the run is sent to, when the route names
385 /// one.
386 pub model: Option<String>,
387 /// For `openai`: OpenAI's own API, which shapes requests its own way.
388 pub official: bool,
A catalogue of model providers, and settings that feel like settings389 /// Which provider it is, by name, so the proxy can meet its quirks.
390 #[serde(default)]
391 pub provider: String,
Integrations: your own model provider, alerts that open issues, tickets agents read392 pub workspace: String,
393 pub repo: String,
394 pub number: u32,
395 pub task: String,
Prices keep themselves current with what g1t pays396 /// The session's id; see `ModelSession::id`.
397 #[serde(default)]
398 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 tier399 /// For `g1t`: the tier the run was routed to, `small` or `large`.
400 #[serde(default)]
401 pub tier: Option<String>,
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix402 /// The person the run is for, by username: who asked g1t for the work.
403 /// Null when nobody did. Never `g1t`, the agent itself.
404 #[serde(default)]
405 pub requested_by: Option<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read406 /// For `endpoint`: where to send requests.
407 pub base_url: Option<String>,
408 /// For `anthropic` and `endpoint`: the workspace's key.
409 pub api_key: Option<String>,
410 /// `x-api-key` or `authorization`.
411 pub auth_header: Option<String>,
Model providers: gateway tokens for endpoints, tidier rows, and the docs412 /// For an endpoint behind an authenticated Cloudflare AI Gateway: the
413 /// gateway's own token, sent as `cf-aig-authorization`.
414 #[serde(default)]
415 pub gateway_token: Option<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read416}
417
418// --- Methods -----------------------------------------------------------------
419
420/// `list`. Returns `Outcome<Vec<Connection>>`. Members only.
421#[derive(Debug, Serialize, Deserialize)]
422pub struct ListArgs {
423 pub workspace: String,
424 pub viewer: Viewer,
425}
426
427/// `connect`. Returns `Outcome<Connected>`. Owners only.
428#[derive(Debug, Serialize, Deserialize)]
429#[serde(rename_all = "camelCase")]
430pub struct ConnectArgs {
431 pub actor: User,
432 pub workspace: String,
433 pub provider: Provider,
434 #[serde(default)]
435 pub name: Option<String>,
436 #[serde(default)]
437 pub config: ConnectionConfig,
438 /// The API key or token g1t uses to call it.
439 #[serde(default)]
440 pub secret: Option<String>,
441 /// What it signs its requests to g1t with: Sentry's client secret.
Model providers: gateway tokens for endpoints, tidier rows, and the docs442 /// Made by g1t for Datadog and webhooks, and shown once. For a model
443 /// endpoint behind an authenticated Cloudflare AI Gateway, the gateway's
444 /// token.
Integrations: your own model provider, alerts that open issues, tickets agents read445 #[serde(default)]
446 pub signing_secret: Option<String>,
447}
448
449#[derive(Clone, Debug, Serialize, Deserialize)]
450#[serde(rename_all = "camelCase")]
451pub struct Connected {
452 pub connection: Connection,
453 /// A signing secret g1t made, shown this once.
454 pub signing_secret: Option<String>,
455}
456
457/// `update`: only the fields given change. Returns `Outcome<Connection>`.
458/// Owners only.
459#[derive(Debug, Serialize, Deserialize)]
460#[serde(rename_all = "camelCase")]
461pub struct UpdateArgs {
462 pub actor: User,
463 pub workspace: String,
464 pub id: String,
465 #[serde(default)]
466 pub name: Option<String>,
467 #[serde(default)]
468 pub config: Option<ConnectionConfig>,
469 #[serde(default)]
470 pub secret: Option<String>,
471 #[serde(default)]
472 pub signing_secret: Option<String>,
473}
474
475/// `disconnect` and `test`. `disconnect` returns `Outcome<bool>`; `test`
476/// returns `Outcome<Tested>`. Owners only.
477#[derive(Debug, Serialize, Deserialize)]
478pub struct ConnectionArgs {
479 pub actor: User,
480 pub workspace: String,
481 pub id: String,
482}
483
484#[derive(Clone, Debug, Serialize, Deserialize)]
485pub struct Tested {
486 pub ok: bool,
487 pub message: String,
488}
489
490/// `deliveries`: the latest requests a connection received, newest first.
491/// Returns `Outcome<Vec<Delivery>>`. Members only.
492#[derive(Debug, Serialize, Deserialize)]
493pub struct DeliveriesArgs {
494 pub workspace: String,
495 pub viewer: Viewer,
496 pub id: String,
497}
498
499/// `receive`: a request an outside system sent to a connection's address.
500/// Returns `Received`. Anyone can send one; only a signed one is acted on.
501#[derive(Debug, Serialize, Deserialize)]
502pub struct ReceiveArgs {
503 pub id: String,
504 /// Header names in lowercase.
505 pub headers: std::collections::HashMap<String, String>,
506 pub body: String,
507}
508
509#[derive(Clone, Debug, Serialize, Deserialize)]
510pub struct Received {
511 /// The HTTP status to answer with.
512 pub status: u16,
513 pub message: String,
514}
515
516/// `resolve`: fetches one outside reference. Returns `Outcome<ContextItem>`.
517/// Members of the workspace only.
518#[derive(Debug, Serialize, Deserialize)]
519pub struct ResolveArgs {
520 pub workspace: String,
521 pub viewer: Viewer,
522 /// `TECH-1234`, or a Jira, Linear or Sentry address.
523 pub reference: String,
524}
525
526/// `references`: every outside reference in `text` that one of the
527/// workspace's connections answers for, fetched. Returns `Vec<ContextItem>`.
528/// For g1t's own agents, about work in that workspace.
529#[derive(Debug, Serialize, Deserialize)]
530pub struct ReferencesArgs {
531 pub workspace: String,
532 pub text: String,
533 #[serde(default)]
534 pub limit: Option<u32>,
535}
536
537/// `import`: opens an issue from a ticket. Returns `Outcome<Imported>`.
538#[derive(Debug, Serialize, Deserialize)]
539pub struct ImportArgs {
540 pub actor: User,
541 pub repo: RepoPath,
542 pub reference: String,
543 /// Put a g1t agent on it.
544 #[serde(default)]
545 pub assign: bool,
546}
547
548#[derive(Clone, Debug, Serialize, Deserialize)]
549pub struct Imported {
550 pub number: u32,
551 pub item: ContextItem,
552 /// False when the ticket had been imported already, and `number` is
553 /// that issue.
554 pub created: bool,
555}
556
557/// `links`: what an issue is tied to outside g1t. Returns `Vec<Link>`.
558/// Callers must have checked the viewer may see the issue.
559#[derive(Debug, Serialize, Deserialize)]
560#[serde(rename_all = "camelCase")]
561pub struct LinksArgs {
562 pub repo: RepoPath,
563 pub number: u32,
564}
565
Models per workspace: several providers, routed by kind of work566/// `open_model_session`: where one run's model requests go, by the
567/// workspace's routes. Returns `Outcome<ModelSession>`: a failure, with the
568/// reason to show, when the route goes nowhere it can use.
Integrations: your own model provider, alerts that open issues, tickets agents read569#[derive(Debug, Serialize, Deserialize)]
Models per workspace: several providers, routed by kind of work570#[serde(rename_all = "camelCase")]
Integrations: your own model provider, alerts that open issues, tickets agents read571pub struct OpenModelSessionArgs {
572 pub workspace: String,
573 pub repo: RepoPath,
574 pub number: u32,
575 pub task: String,
Models per workspace: several providers, routed by kind of work576 /// Whether g1t's hosted models are open to the workspace. The runner
577 /// decides that; this service only follows the routes.
578 #[serde(default = "yes")]
579 pub hosted_open: bool,
Merge branch 'model-routing'580 /// `small`, `large` or `frontier`: the tier the runner routed the run
581 /// to on g1t's hosted models, tagged on its requests at the gateway.
582 /// Kept only when the run goes to g1t's models, where a tier the
583 /// 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 tier584 #[serde(default)]
585 pub tier: Option<String>,
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix586 /// The person the run is for, by username, so usage can be shown per
587 /// person. Null when nobody asked; `g1t`, the agent, is kept as null.
588 #[serde(default)]
589 pub requested_by: Option<String>,
Models per workspace: several providers, routed by kind of work590}
591
592/// `routes`: a workspace's model routes, one per kind of work that has its
593/// own. Returns `Outcome<Vec<ModelRoute>>`. Members only.
594#[derive(Debug, Serialize, Deserialize)]
595pub struct RoutesArgs {
596 pub workspace: String,
597 pub viewer: Viewer,
598}
599
600/// `set_routes`: replaces a workspace's model routes. A kind of work left
601/// out follows `default`; with no `default`, g1t's hosted models where they
602/// are open. Returns `Outcome<Vec<ModelRoute>>`. Owners only.
603#[derive(Debug, Serialize, Deserialize)]
604pub struct SetRoutesArgs {
605 pub actor: User,
606 pub workspace: String,
607 pub routes: Vec<ModelRoute>,
Integrations: your own model provider, alerts that open issues, tickets agents read608}
609
610/// `model_upstream`: what a model session's token stands for, or null when
611/// it is unknown or expired. Returns `Option<ModelUpstream>`.
612#[derive(Debug, Serialize, Deserialize)]
613pub struct ModelUpstreamArgs {
614 pub token: String,
615}
616
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens617/// `gateway_upstream`: where a workspace's AI Gateway requests go when it
618/// has its own Anthropic key: its first Anthropic or Anthropic-compatible
619/// model provider, with the credentials to forward them. Null sends them to
620/// g1t's models. Returns `Option<ModelUpstream>`, with `task` `gateway`.
621#[derive(Debug, Serialize, Deserialize)]
622pub struct GatewayUpstreamArgs {
623 pub workspace: String,
624}
625
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily626/// `close_model_sessions`: ends the model sessions whose tokens hash to
627/// these (SHA-256, lowercase hex), so a run's model token stops working
628/// when its run does rather than when it would lapse. Returns how many
629/// were open.
630#[derive(Debug, Serialize, Deserialize)]
631pub struct CloseModelSessionsArgs {
632 #[serde(alias = "tokenHashes")]
633 pub token_hashes: Vec<String>,
634}
635
Integrations: your own model provider, alerts that open issues, tickets agents read636/// `model_provider`: the workspace's own model connection, if it has one.
637/// Returns `Option<Connection>`.
638#[derive(Debug, Serialize, Deserialize)]
639pub struct ModelProviderArgs {
640 pub workspace: String,
641}
A catalogue of model providers, and settings that feel like settings642
643#[cfg(test)]
644mod tests {
645 use super::*;
646
647 #[test]
648 fn every_provider_is_named_once_and_found_again() {
649 let mut names: Vec<&str> = PROVIDERS.iter().map(|spec| spec.name).collect();
650 for provider in Provider::all() {
651 assert_eq!(Provider::parse(provider.name()), Some(provider));
652 }
653 names.sort();
654 names.dedup();
655 assert_eq!(names.len(), PROVIDERS.len());
656 }
657
658 #[test]
659 fn model_providers_say_how_to_reach_them() {
660 for spec in PROVIDERS.iter().filter(|spec| spec.kind == ProviderKind::Models) {
661 assert!(spec.api == "anthropic" || spec.api == "openai", "{}", spec.name);
662 assert!(!spec.auth_header.is_empty(), "{}", spec.name);
663 assert!(spec.base_url.is_empty() || spec.base_url.starts_with("https://"), "{}", spec.name);
664 }
665 }
666}

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