g1t/crates/contracts/src/integrations.rs

644 lines23,145 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
Integrations: your own model provider, alerts that open issues, tickets agents read173#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
174#[serde(rename_all = "snake_case")]
175pub enum ProviderKind {
Models per workspace: several providers, routed by kind of work176 /// Where agents' model requests go. A workspace can have several and
177 /// routes each kind of work to one.
Integrations: your own model provider, alerts that open issues, tickets agents read178 Models,
179 /// Problems that become issues.
180 Alerts,
181 /// Tickets that agents read and people import.
182 Tracker,
183}
184
185/// A connection's settings: everything about it except its secrets. Each
186/// provider uses the fields that apply to it.
187#[derive(Clone, Debug, Serialize, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct ConnectionConfig {
190 /// For alerts: the repository issues are opened in, `owner/name`. For a
191 /// tracker: where an imported ticket goes when no repository is named.
192 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub repo: Option<String>,
194 /// For alerts: put a g1t agent on each issue opened.
195 #[serde(default)]
196 pub assign: bool,
197 /// For alerts: the label put on each issue opened. `bug` when unset.
198 #[serde(default, skip_serializing_if = "Option::is_none")]
199 pub label: Option<String>,
200 /// Tell the outside system when the work lands: resolve the Sentry
201 /// issue, comment on the ticket.
202 #[serde(default = "yes")]
203 pub write_back: bool,
204 /// Sentry: the organization's slug.
205 #[serde(default, skip_serializing_if = "Option::is_none")]
206 pub organization: Option<String>,
207 /// The system's address, for Jira (`https://acme.atlassian.net`) or a
208 /// Sentry that is not sentry.io.
209 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub site: Option<String>,
211 /// Jira: the account the API token belongs to.
212 #[serde(default, skip_serializing_if = "Option::is_none")]
213 pub email: Option<String>,
214 /// Jira project keys or Linear team keys this connection answers for,
215 /// such as `TECH`. Empty answers for every key.
216 #[serde(default, skip_serializing_if = "Vec::is_empty")]
217 pub keys: Vec<String>,
218 /// Your own endpoint: its base URL, without `/v1`.
219 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub base_url: Option<String>,
221 /// Your own endpoint: send the key as `x-api-key` (the default) or as
222 /// `authorization: Bearer`.
223 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub auth_header: Option<String>,
Models per workspace: several providers, routed by kind of work225 /// Models: the model used when a route to this connection names none.
226 /// Required for providers that speak OpenAI's API; for Anthropic, g1t's
227 /// choice for the kind of work when unset.
Integrations: your own model provider, alerts that open issues, tickets agents read228 #[serde(default, skip_serializing_if = "Option::is_none")]
229 pub model: Option<String>,
230}
231
232fn yes() -> bool {
233 true
234}
235
236impl Default for ConnectionConfig {
237 fn default() -> Self {
238 ConnectionConfig {
239 repo: None,
240 assign: false,
241 label: None,
242 write_back: true,
243 organization: None,
244 site: None,
245 email: None,
246 keys: Vec::new(),
247 base_url: None,
248 auth_header: None,
249 model: None,
250 }
251 }
252}
253
254/// A connection, as anyone in the workspace sees it. Secrets are never
255/// shown after they are saved; `secretHint` is enough to tell keys apart.
256#[derive(Clone, Debug, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct Connection {
259 pub id: String,
260 pub workspace: String,
261 pub provider: Provider,
262 pub kind: ProviderKind,
263 pub name: String,
264 pub config: ConnectionConfig,
265 /// The last four characters of the saved key, such as `…3f9a`.
266 pub secret_hint: Option<String>,
267 /// For a provider that sends g1t requests: where it sends them.
268 pub webhook_url: Option<String>,
269 pub created_by: String,
270 /// RFC 3339.
271 pub created_at: String,
272 pub last_used_at: Option<String>,
273 /// The last thing that went wrong talking to it, until it next works.
274 pub last_error: Option<String>,
Models per workspace: several providers, routed by kind of work275 /// For a model provider: the models it offered when last checked.
276 #[serde(default)]
277 pub models: Vec<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read278}
279
Models per workspace: several providers, routed by kind of work280/// Where one kind of work's model requests go in a workspace.
281#[derive(Clone, Debug, Serialize, Deserialize)]
282#[serde(rename_all = "camelCase")]
283pub struct ModelRoute {
284 /// One of [`MODEL_TASKS`].
285 pub task: String,
286 /// The workspace's own model connection, or `None` for g1t's hosted
287 /// models.
288 pub connection_id: Option<String>,
289 /// The model at that connection; its default model when `None`.
290 pub model: Option<String>,
291}
292
Integrations: your own model provider, alerts that open issues, tickets agents read293/// One request an outside system sent, and what g1t did with it.
294#[derive(Clone, Debug, Serialize, Deserialize)]
295#[serde(rename_all = "camelCase")]
296pub struct Delivery {
297 pub id: String,
298 /// RFC 3339.
299 pub received_at: String,
300 /// What it was about, in the sender's terms: `issue.created`.
301 pub event: String,
302 /// `opened`, `updated`, `reopened`, `ignored` or `refused`.
303 pub outcome: String,
304 pub detail: String,
305 /// The issue it opened or updated, `owner/name#number`.
306 pub issue: Option<String>,
307}
308
309/// Something outside g1t, fetched as it is now. Its text was written outside
310/// g1t, so it is reference material and never instructions.
311#[derive(Clone, Debug, Serialize, Deserialize)]
312#[serde(rename_all = "camelCase")]
313pub struct ContextItem {
314 pub provider: Provider,
315 /// `TECH-1234`, or the Sentry issue's short id.
316 pub key: String,
317 pub title: String,
318 pub url: String,
319 /// Its status in that system: `In Progress`, `unresolved`.
320 pub status: Option<String>,
321 /// Its description, as plain text, shortened if long.
322 pub body: String,
323 /// RFC 3339: when g1t fetched it.
324 pub fetched_at: String,
325}
326
327/// An issue's tie to something outside g1t.
328#[derive(Clone, Debug, Serialize, Deserialize)]
329#[serde(rename_all = "camelCase")]
330pub struct Link {
331 pub provider: Provider,
332 pub connection_id: String,
333 pub key: String,
334 pub title: String,
335 pub url: String,
336 /// How many times an alert has fired for it.
337 pub count: u32,
338 /// RFC 3339.
339 pub first_seen: String,
340 pub last_seen: String,
341}
342
343/// Where a workspace's agents' model requests go.
344#[derive(Clone, Debug, Serialize, Deserialize)]
345#[serde(rename_all = "camelCase")]
346pub struct ModelSession {
347 /// What the sandbox sends instead of a key. Lives as long as one run.
348 pub token: String,
349 /// `g1t` when g1t pays the provider and charges the workspace,
350 /// `workspace` when the workspace's own account does.
351 pub billed_to: String,
352 /// The connection's name, when it is the workspace's own.
353 pub provider_name: Option<String>,
354 /// The model to use instead of g1t's choice, if the connection names one.
355 pub model: Option<String>,
Prices keep themselves current with what g1t pays356 /// Names the run in AI Gateway's logs (`metadata.session`), so billing
357 /// can charge each run what the gateway priced its requests at. Not a
358 /// secret: it cannot be turned back into the token.
359 #[serde(default)]
360 pub id: String,
Integrations: your own model provider, alerts that open issues, tickets agents read361}
362
363/// What the model proxy needs to forward one run's requests.
364#[derive(Clone, Debug, Serialize, Deserialize)]
365#[serde(rename_all = "camelCase")]
366pub struct ModelUpstream {
367 /// `g1t`, `anthropic` or `endpoint`.
368 pub route: String,
Models per workspace: several providers, routed by kind of work369 /// The API the provider speaks: `anthropic` or `openai`, which the proxy
370 /// translates to.
371 pub api: String,
372 /// The model every request of the run is sent to, when the route names
373 /// one.
374 pub model: Option<String>,
375 /// For `openai`: OpenAI's own API, which shapes requests its own way.
376 pub official: bool,
A catalogue of model providers, and settings that feel like settings377 /// Which provider it is, by name, so the proxy can meet its quirks.
378 #[serde(default)]
379 pub provider: String,
Integrations: your own model provider, alerts that open issues, tickets agents read380 pub workspace: String,
381 pub repo: String,
382 pub number: u32,
383 pub task: String,
Prices keep themselves current with what g1t pays384 /// The session's id; see `ModelSession::id`.
385 #[serde(default)]
386 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 tier387 /// For `g1t`: the tier the run was routed to, `small` or `large`.
388 #[serde(default)]
389 pub tier: Option<String>,
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix390 /// The person the run is for, by username: who asked g1t for the work.
391 /// Null when nobody did. Never `g1t`, the agent itself.
392 #[serde(default)]
393 pub requested_by: Option<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read394 /// For `endpoint`: where to send requests.
395 pub base_url: Option<String>,
396 /// For `anthropic` and `endpoint`: the workspace's key.
397 pub api_key: Option<String>,
398 /// `x-api-key` or `authorization`.
399 pub auth_header: Option<String>,
Model providers: gateway tokens for endpoints, tidier rows, and the docs400 /// For an endpoint behind an authenticated Cloudflare AI Gateway: the
401 /// gateway's own token, sent as `cf-aig-authorization`.
402 #[serde(default)]
403 pub gateway_token: Option<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read404}
405
406// --- Methods -----------------------------------------------------------------
407
408/// `list`. Returns `Outcome<Vec<Connection>>`. Members only.
409#[derive(Debug, Serialize, Deserialize)]
410pub struct ListArgs {
411 pub workspace: String,
412 pub viewer: Viewer,
413}
414
415/// `connect`. Returns `Outcome<Connected>`. Owners only.
416#[derive(Debug, Serialize, Deserialize)]
417#[serde(rename_all = "camelCase")]
418pub struct ConnectArgs {
419 pub actor: User,
420 pub workspace: String,
421 pub provider: Provider,
422 #[serde(default)]
423 pub name: Option<String>,
424 #[serde(default)]
425 pub config: ConnectionConfig,
426 /// The API key or token g1t uses to call it.
427 #[serde(default)]
428 pub secret: Option<String>,
429 /// What it signs its requests to g1t with: Sentry's client secret.
Model providers: gateway tokens for endpoints, tidier rows, and the docs430 /// Made by g1t for Datadog and webhooks, and shown once. For a model
431 /// endpoint behind an authenticated Cloudflare AI Gateway, the gateway's
432 /// token.
Integrations: your own model provider, alerts that open issues, tickets agents read433 #[serde(default)]
434 pub signing_secret: Option<String>,
435}
436
437#[derive(Clone, Debug, Serialize, Deserialize)]
438#[serde(rename_all = "camelCase")]
439pub struct Connected {
440 pub connection: Connection,
441 /// A signing secret g1t made, shown this once.
442 pub signing_secret: Option<String>,
443}
444
445/// `update`: only the fields given change. Returns `Outcome<Connection>`.
446/// Owners only.
447#[derive(Debug, Serialize, Deserialize)]
448#[serde(rename_all = "camelCase")]
449pub struct UpdateArgs {
450 pub actor: User,
451 pub workspace: String,
452 pub id: String,
453 #[serde(default)]
454 pub name: Option<String>,
455 #[serde(default)]
456 pub config: Option<ConnectionConfig>,
457 #[serde(default)]
458 pub secret: Option<String>,
459 #[serde(default)]
460 pub signing_secret: Option<String>,
461}
462
463/// `disconnect` and `test`. `disconnect` returns `Outcome<bool>`; `test`
464/// returns `Outcome<Tested>`. Owners only.
465#[derive(Debug, Serialize, Deserialize)]
466pub struct ConnectionArgs {
467 pub actor: User,
468 pub workspace: String,
469 pub id: String,
470}
471
472#[derive(Clone, Debug, Serialize, Deserialize)]
473pub struct Tested {
474 pub ok: bool,
475 pub message: String,
476}
477
478/// `deliveries`: the latest requests a connection received, newest first.
479/// Returns `Outcome<Vec<Delivery>>`. Members only.
480#[derive(Debug, Serialize, Deserialize)]
481pub struct DeliveriesArgs {
482 pub workspace: String,
483 pub viewer: Viewer,
484 pub id: String,
485}
486
487/// `receive`: a request an outside system sent to a connection's address.
488/// Returns `Received`. Anyone can send one; only a signed one is acted on.
489#[derive(Debug, Serialize, Deserialize)]
490pub struct ReceiveArgs {
491 pub id: String,
492 /// Header names in lowercase.
493 pub headers: std::collections::HashMap<String, String>,
494 pub body: String,
495}
496
497#[derive(Clone, Debug, Serialize, Deserialize)]
498pub struct Received {
499 /// The HTTP status to answer with.
500 pub status: u16,
501 pub message: String,
502}
503
504/// `resolve`: fetches one outside reference. Returns `Outcome<ContextItem>`.
505/// Members of the workspace only.
506#[derive(Debug, Serialize, Deserialize)]
507pub struct ResolveArgs {
508 pub workspace: String,
509 pub viewer: Viewer,
510 /// `TECH-1234`, or a Jira, Linear or Sentry address.
511 pub reference: String,
512}
513
514/// `references`: every outside reference in `text` that one of the
515/// workspace's connections answers for, fetched. Returns `Vec<ContextItem>`.
516/// For g1t's own agents, about work in that workspace.
517#[derive(Debug, Serialize, Deserialize)]
518pub struct ReferencesArgs {
519 pub workspace: String,
520 pub text: String,
521 #[serde(default)]
522 pub limit: Option<u32>,
523}
524
525/// `import`: opens an issue from a ticket. Returns `Outcome<Imported>`.
526#[derive(Debug, Serialize, Deserialize)]
527pub struct ImportArgs {
528 pub actor: User,
529 pub repo: RepoPath,
530 pub reference: String,
531 /// Put a g1t agent on it.
532 #[serde(default)]
533 pub assign: bool,
534}
535
536#[derive(Clone, Debug, Serialize, Deserialize)]
537pub struct Imported {
538 pub number: u32,
539 pub item: ContextItem,
540 /// False when the ticket had been imported already, and `number` is
541 /// that issue.
542 pub created: bool,
543}
544
545/// `links`: what an issue is tied to outside g1t. Returns `Vec<Link>`.
546/// Callers must have checked the viewer may see the issue.
547#[derive(Debug, Serialize, Deserialize)]
548#[serde(rename_all = "camelCase")]
549pub struct LinksArgs {
550 pub repo: RepoPath,
551 pub number: u32,
552}
553
Models per workspace: several providers, routed by kind of work554/// `open_model_session`: where one run's model requests go, by the
555/// workspace's routes. Returns `Outcome<ModelSession>`: a failure, with the
556/// reason to show, when the route goes nowhere it can use.
Integrations: your own model provider, alerts that open issues, tickets agents read557#[derive(Debug, Serialize, Deserialize)]
Models per workspace: several providers, routed by kind of work558#[serde(rename_all = "camelCase")]
Integrations: your own model provider, alerts that open issues, tickets agents read559pub struct OpenModelSessionArgs {
560 pub workspace: String,
561 pub repo: RepoPath,
562 pub number: u32,
563 pub task: String,
Models per workspace: several providers, routed by kind of work564 /// Whether g1t's hosted models are open to the workspace. The runner
565 /// decides that; this service only follows the routes.
566 #[serde(default = "yes")]
567 pub hosted_open: bool,
Auto model routing: the cheapest tier that can do each piece of work, a retry goes up a tier, and each run records its tier568 /// `small` or `large`: the tier the runner routed the run to on g1t's
569 /// hosted models, tagged on its requests at the gateway. Kept only
570 /// when the run goes to g1t's models.
571 #[serde(default)]
572 pub tier: Option<String>,
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix573 /// The person the run is for, by username, so usage can be shown per
574 /// person. Null when nobody asked; `g1t`, the agent, is kept as null.
575 #[serde(default)]
576 pub requested_by: Option<String>,
Models per workspace: several providers, routed by kind of work577}
578
579/// `routes`: a workspace's model routes, one per kind of work that has its
580/// own. Returns `Outcome<Vec<ModelRoute>>`. Members only.
581#[derive(Debug, Serialize, Deserialize)]
582pub struct RoutesArgs {
583 pub workspace: String,
584 pub viewer: Viewer,
585}
586
587/// `set_routes`: replaces a workspace's model routes. A kind of work left
588/// out follows `default`; with no `default`, g1t's hosted models where they
589/// are open. Returns `Outcome<Vec<ModelRoute>>`. Owners only.
590#[derive(Debug, Serialize, Deserialize)]
591pub struct SetRoutesArgs {
592 pub actor: User,
593 pub workspace: String,
594 pub routes: Vec<ModelRoute>,
Integrations: your own model provider, alerts that open issues, tickets agents read595}
596
597/// `model_upstream`: what a model session's token stands for, or null when
598/// it is unknown or expired. Returns `Option<ModelUpstream>`.
599#[derive(Debug, Serialize, Deserialize)]
600pub struct ModelUpstreamArgs {
601 pub token: String,
602}
603
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily604/// `close_model_sessions`: ends the model sessions whose tokens hash to
605/// these (SHA-256, lowercase hex), so a run's model token stops working
606/// when its run does rather than when it would lapse. Returns how many
607/// were open.
608#[derive(Debug, Serialize, Deserialize)]
609pub struct CloseModelSessionsArgs {
610 #[serde(alias = "tokenHashes")]
611 pub token_hashes: Vec<String>,
612}
613
Integrations: your own model provider, alerts that open issues, tickets agents read614/// `model_provider`: the workspace's own model connection, if it has one.
615/// Returns `Option<Connection>`.
616#[derive(Debug, Serialize, Deserialize)]
617pub struct ModelProviderArgs {
618 pub workspace: String,
619}
A catalogue of model providers, and settings that feel like settings620
621#[cfg(test)]
622mod tests {
623 use super::*;
624
625 #[test]
626 fn every_provider_is_named_once_and_found_again() {
627 let mut names: Vec<&str> = PROVIDERS.iter().map(|spec| spec.name).collect();
628 for provider in Provider::all() {
629 assert_eq!(Provider::parse(provider.name()), Some(provider));
630 }
631 names.sort();
632 names.dedup();
633 assert_eq!(names.len(), PROVIDERS.len());
634 }
635
636 #[test]
637 fn model_providers_say_how_to_reach_them() {
638 for spec in PROVIDERS.iter().filter(|spec| spec.kind == ProviderKind::Models) {
639 assert!(spec.api == "anthropic" || spec.api == "openai", "{}", spec.name);
640 assert!(!spec.auth_header.is_empty(), "{}", spec.name);
641 assert!(spec.base_url.is_empty() || spec.base_url.starts_with("https://"), "{}", spec.name);
642 }
643 }
644}