flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/crates/contracts/src/integrations.rs

610 lines21,643 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>,
356}
357
358/// What the model proxy needs to forward one run's requests.
359#[derive(Clone, Debug, Serialize, Deserialize)]
360#[serde(rename_all = "camelCase")]
361pub struct ModelUpstream {
362 /// `g1t`, `anthropic` or `endpoint`.
363 pub route: String,
Models per workspace: several providers, routed by kind of work364 /// The API the provider speaks: `anthropic` or `openai`, which the proxy
365 /// translates to.
366 pub api: String,
367 /// The model every request of the run is sent to, when the route names
368 /// one.
369 pub model: Option<String>,
370 /// For `openai`: OpenAI's own API, which shapes requests its own way.
371 pub official: bool,
A catalogue of model providers, and settings that feel like settings372 /// Which provider it is, by name, so the proxy can meet its quirks.
373 #[serde(default)]
374 pub provider: String,
Integrations: your own model provider, alerts that open issues, tickets agents read375 pub workspace: String,
376 pub repo: String,
377 pub number: u32,
378 pub task: String,
379 /// For `endpoint`: where to send requests.
380 pub base_url: Option<String>,
381 /// For `anthropic` and `endpoint`: the workspace's key.
382 pub api_key: Option<String>,
383 /// `x-api-key` or `authorization`.
384 pub auth_header: Option<String>,
Model providers: gateway tokens for endpoints, tidier rows, and the docs385 /// For an endpoint behind an authenticated Cloudflare AI Gateway: the
386 /// gateway's own token, sent as `cf-aig-authorization`.
387 #[serde(default)]
388 pub gateway_token: Option<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read389}
390
391// --- Methods -----------------------------------------------------------------
392
393/// `list`. Returns `Outcome<Vec<Connection>>`. Members only.
394#[derive(Debug, Serialize, Deserialize)]
395pub struct ListArgs {
396 pub workspace: String,
397 pub viewer: Viewer,
398}
399
400/// `connect`. Returns `Outcome<Connected>`. Owners only.
401#[derive(Debug, Serialize, Deserialize)]
402#[serde(rename_all = "camelCase")]
403pub struct ConnectArgs {
404 pub actor: User,
405 pub workspace: String,
406 pub provider: Provider,
407 #[serde(default)]
408 pub name: Option<String>,
409 #[serde(default)]
410 pub config: ConnectionConfig,
411 /// The API key or token g1t uses to call it.
412 #[serde(default)]
413 pub secret: Option<String>,
414 /// What it signs its requests to g1t with: Sentry's client secret.
Model providers: gateway tokens for endpoints, tidier rows, and the docs415 /// Made by g1t for Datadog and webhooks, and shown once. For a model
416 /// endpoint behind an authenticated Cloudflare AI Gateway, the gateway's
417 /// token.
Integrations: your own model provider, alerts that open issues, tickets agents read418 #[serde(default)]
419 pub signing_secret: Option<String>,
420}
421
422#[derive(Clone, Debug, Serialize, Deserialize)]
423#[serde(rename_all = "camelCase")]
424pub struct Connected {
425 pub connection: Connection,
426 /// A signing secret g1t made, shown this once.
427 pub signing_secret: Option<String>,
428}
429
430/// `update`: only the fields given change. Returns `Outcome<Connection>`.
431/// Owners only.
432#[derive(Debug, Serialize, Deserialize)]
433#[serde(rename_all = "camelCase")]
434pub struct UpdateArgs {
435 pub actor: User,
436 pub workspace: String,
437 pub id: String,
438 #[serde(default)]
439 pub name: Option<String>,
440 #[serde(default)]
441 pub config: Option<ConnectionConfig>,
442 #[serde(default)]
443 pub secret: Option<String>,
444 #[serde(default)]
445 pub signing_secret: Option<String>,
446}
447
448/// `disconnect` and `test`. `disconnect` returns `Outcome<bool>`; `test`
449/// returns `Outcome<Tested>`. Owners only.
450#[derive(Debug, Serialize, Deserialize)]
451pub struct ConnectionArgs {
452 pub actor: User,
453 pub workspace: String,
454 pub id: String,
455}
456
457#[derive(Clone, Debug, Serialize, Deserialize)]
458pub struct Tested {
459 pub ok: bool,
460 pub message: String,
461}
462
463/// `deliveries`: the latest requests a connection received, newest first.
464/// Returns `Outcome<Vec<Delivery>>`. Members only.
465#[derive(Debug, Serialize, Deserialize)]
466pub struct DeliveriesArgs {
467 pub workspace: String,
468 pub viewer: Viewer,
469 pub id: String,
470}
471
472/// `receive`: a request an outside system sent to a connection's address.
473/// Returns `Received`. Anyone can send one; only a signed one is acted on.
474#[derive(Debug, Serialize, Deserialize)]
475pub struct ReceiveArgs {
476 pub id: String,
477 /// Header names in lowercase.
478 pub headers: std::collections::HashMap<String, String>,
479 pub body: String,
480}
481
482#[derive(Clone, Debug, Serialize, Deserialize)]
483pub struct Received {
484 /// The HTTP status to answer with.
485 pub status: u16,
486 pub message: String,
487}
488
489/// `resolve`: fetches one outside reference. Returns `Outcome<ContextItem>`.
490/// Members of the workspace only.
491#[derive(Debug, Serialize, Deserialize)]
492pub struct ResolveArgs {
493 pub workspace: String,
494 pub viewer: Viewer,
495 /// `TECH-1234`, or a Jira, Linear or Sentry address.
496 pub reference: String,
497}
498
499/// `references`: every outside reference in `text` that one of the
500/// workspace's connections answers for, fetched. Returns `Vec<ContextItem>`.
501/// For g1t's own agents, about work in that workspace.
502#[derive(Debug, Serialize, Deserialize)]
503pub struct ReferencesArgs {
504 pub workspace: String,
505 pub text: String,
506 #[serde(default)]
507 pub limit: Option<u32>,
508}
509
510/// `import`: opens an issue from a ticket. Returns `Outcome<Imported>`.
511#[derive(Debug, Serialize, Deserialize)]
512pub struct ImportArgs {
513 pub actor: User,
514 pub repo: RepoPath,
515 pub reference: String,
516 /// Put a g1t agent on it.
517 #[serde(default)]
518 pub assign: bool,
519}
520
521#[derive(Clone, Debug, Serialize, Deserialize)]
522pub struct Imported {
523 pub number: u32,
524 pub item: ContextItem,
525 /// False when the ticket had been imported already, and `number` is
526 /// that issue.
527 pub created: bool,
528}
529
530/// `links`: what an issue is tied to outside g1t. Returns `Vec<Link>`.
531/// Callers must have checked the viewer may see the issue.
532#[derive(Debug, Serialize, Deserialize)]
533#[serde(rename_all = "camelCase")]
534pub struct LinksArgs {
535 pub repo: RepoPath,
536 pub number: u32,
537}
538
Models per workspace: several providers, routed by kind of work539/// `open_model_session`: where one run's model requests go, by the
540/// workspace's routes. Returns `Outcome<ModelSession>`: a failure, with the
541/// reason to show, when the route goes nowhere it can use.
Integrations: your own model provider, alerts that open issues, tickets agents read542#[derive(Debug, Serialize, Deserialize)]
Models per workspace: several providers, routed by kind of work543#[serde(rename_all = "camelCase")]
Integrations: your own model provider, alerts that open issues, tickets agents read544pub struct OpenModelSessionArgs {
545 pub workspace: String,
546 pub repo: RepoPath,
547 pub number: u32,
548 pub task: String,
Models per workspace: several providers, routed by kind of work549 /// Whether g1t's hosted models are open to the workspace. The runner
550 /// decides that; this service only follows the routes.
551 #[serde(default = "yes")]
552 pub hosted_open: bool,
553}
554
555/// `routes`: a workspace's model routes, one per kind of work that has its
556/// own. Returns `Outcome<Vec<ModelRoute>>`. Members only.
557#[derive(Debug, Serialize, Deserialize)]
558pub struct RoutesArgs {
559 pub workspace: String,
560 pub viewer: Viewer,
561}
562
563/// `set_routes`: replaces a workspace's model routes. A kind of work left
564/// out follows `default`; with no `default`, g1t's hosted models where they
565/// are open. Returns `Outcome<Vec<ModelRoute>>`. Owners only.
566#[derive(Debug, Serialize, Deserialize)]
567pub struct SetRoutesArgs {
568 pub actor: User,
569 pub workspace: String,
570 pub routes: Vec<ModelRoute>,
Integrations: your own model provider, alerts that open issues, tickets agents read571}
572
573/// `model_upstream`: what a model session's token stands for, or null when
574/// it is unknown or expired. Returns `Option<ModelUpstream>`.
575#[derive(Debug, Serialize, Deserialize)]
576pub struct ModelUpstreamArgs {
577 pub token: String,
578}
579
580/// `model_provider`: the workspace's own model connection, if it has one.
581/// Returns `Option<Connection>`.
582#[derive(Debug, Serialize, Deserialize)]
583pub struct ModelProviderArgs {
584 pub workspace: String,
585}
A catalogue of model providers, and settings that feel like settings586
587#[cfg(test)]
588mod tests {
589 use super::*;
590
591 #[test]
592 fn every_provider_is_named_once_and_found_again() {
593 let mut names: Vec<&str> = PROVIDERS.iter().map(|spec| spec.name).collect();
594 for provider in Provider::all() {
595 assert_eq!(Provider::parse(provider.name()), Some(provider));
596 }
597 names.sort();
598 names.dedup();
599 assert_eq!(names.len(), PROVIDERS.len());
600 }
601
602 #[test]
603 fn model_providers_say_how_to_reach_them() {
604 for spec in PROVIDERS.iter().filter(|spec| spec.kind == ProviderKind::Models) {
605 assert!(spec.api == "anthropic" || spec.api == "openai", "{}", spec.name);
606 assert!(!spec.auth_header.is_empty(), "{}", spec.name);
607 assert!(spec.base_url.is_empty() || spec.base_url.starts_with("https://"), "{}", spec.name);
608 }
609 }
610}