Skip to content

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.sh
613 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.

The Packages page names every registry it has1//! The integrations service: a workspace's connections to systems outside
2//! g1t, and everything that crosses between them.
3//!
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.4//! - **AI providers.** A workspace keeps the credentials for the AI
5//! providers its team uses (Anthropic, OpenAI, Gemini, and anything
6//! compatible with either API), sealed, so work can be handed to them.
7//! Workspace admins add, test, update and remove them.
The Packages page names every registry it has8//! - **Alerts.** Sentry, Datadog or any signed webhook opens an issue in a
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.9//! repository, once per problem however often it fires.
The Packages page names every registry it has10//! - **Trackers.** A Jira or Linear key, such as `TECH-1234`, resolves to the
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.11//! ticket: people and their tools read it, people import it as an issue,
12//! and when the work lands the ticket is told.
Merge branch 'ask-g1t'13//! - **Ask g1t.** A conversation with g1t from the top bar, answered by the
14//! AI provider and model an owner chose from the workspace's own
15//! connections, on the workspace's own key. g1t hosts no model for it and
16//! bills nothing for it; the key never leaves this service.
The Packages page names every registry it has17//!
18//! Mirrors `packages/contracts/src/integrations.ts`.
19
20use serde::{Deserialize, Serialize};
21
22use crate::repos::RepoPath;
23use crate::{User, Viewer};
24
25/// Which outside system a connection is to.
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum Provider {
29 // Model providers: the labs.
30 Anthropic,
31 Openai,
32 /// Through Gemini's OpenAI-compatible endpoint.
33 Gemini,
34 Xai,
35 Mistral,
36 Deepseek,
37 // Model providers: platforms that serve many labs' models.
38 /// A deployment on the workspace's own Azure OpenAI resource.
39 AzureOpenai,
40 Openrouter,
41 Groq,
42 Together,
43 Fireworks,
44 Cerebras,
45 // Model providers: anything else.
46 /// Any endpoint that speaks Anthropic's Messages API: the workspace's
47 /// own Cloudflare AI Gateway, LiteLLM, a proxy in front of Bedrock or
48 /// Vertex, or a self-hosted model.
49 AnthropicEndpoint,
50 /// Any endpoint that speaks OpenAI's Chat Completions API: vLLM,
51 /// Ollama behind a tunnel, LiteLLM, a gateway.
52 OpenaiEndpoint,
53 // Alerts.
54 Sentry,
55 Datadog,
56 /// Anything that can send a signed JSON request.
57 Webhook,
58 // Trackers.
59 Jira,
60 Linear,
61}
62
63/// What g1t knows about a provider.
64pub struct Spec {
65 pub provider: Provider,
66 pub name: &'static str,
67 pub label: &'static str,
68 pub kind: ProviderKind,
69 /// For a model provider: the API it speaks, `anthropic` or `openai`.
70 pub api: &'static str,
71 /// For a model provider with a fixed address: where its API is, with
72 /// the version for OpenAI's API and without it for Anthropic's. Empty
73 /// when the connection gives its own.
74 pub base_url: &'static str,
75 /// The header the key goes in; `authorization` means `Bearer <key>`.
76 pub auth_header: &'static str,
77}
78
79const fn model(provider: Provider, name: &'static str, label: &'static str, api: &'static str, base_url: &'static str, auth_header: &'static str) -> Spec {
80 Spec {
81 provider,
82 name,
83 label,
84 kind: ProviderKind::Models,
85 api,
86 base_url,
87 auth_header,
88 }
89}
90
91const fn other(provider: Provider, name: &'static str, label: &'static str, kind: ProviderKind) -> Spec {
92 Spec {
93 provider,
94 name,
95 label,
96 kind,
97 api: "",
98 base_url: "",
99 auth_header: "",
100 }
101}
102
103/// Every provider, in the order people are shown them.
104pub const PROVIDERS: [Spec; 19] = [
105 model(Provider::Anthropic, "anthropic", "Anthropic", "anthropic", "https://api.anthropic.com", "x-api-key"),
106 model(Provider::Openai, "openai", "OpenAI", "openai", "https://api.openai.com/v1", "authorization"),
107 model(Provider::Gemini, "gemini", "Google Gemini", "openai", "https://generativelanguage.googleapis.com/v1beta/openai", "authorization"),
108 model(Provider::Xai, "xai", "xAI", "openai", "https://api.x.ai/v1", "authorization"),
109 model(Provider::Mistral, "mistral", "Mistral", "openai", "https://api.mistral.ai/v1", "authorization"),
110 model(Provider::Deepseek, "deepseek", "DeepSeek", "openai", "https://api.deepseek.com/v1", "authorization"),
111 model(Provider::AzureOpenai, "azure_openai", "Azure OpenAI", "openai", "", "api-key"),
112 model(Provider::Openrouter, "openrouter", "OpenRouter", "openai", "https://openrouter.ai/api/v1", "authorization"),
113 model(Provider::Groq, "groq", "Groq", "openai", "https://api.groq.com/openai/v1", "authorization"),
114 model(Provider::Together, "together", "Together AI", "openai", "https://api.together.xyz/v1", "authorization"),
115 model(Provider::Fireworks, "fireworks", "Fireworks AI", "openai", "https://api.fireworks.ai/inference/v1", "authorization"),
116 model(Provider::Cerebras, "cerebras", "Cerebras", "openai", "https://api.cerebras.ai/v1", "authorization"),
117 model(Provider::AnthropicEndpoint, "anthropic_endpoint", "Anthropic-compatible endpoint", "anthropic", "", "x-api-key"),
118 model(Provider::OpenaiEndpoint, "openai_endpoint", "OpenAI-compatible endpoint", "openai", "", "authorization"),
119 other(Provider::Sentry, "sentry", "Sentry", ProviderKind::Alerts),
120 other(Provider::Datadog, "datadog", "Datadog", ProviderKind::Alerts),
121 other(Provider::Webhook, "webhook", "Webhook", ProviderKind::Alerts),
122 other(Provider::Jira, "jira", "Jira", ProviderKind::Tracker),
123 other(Provider::Linear, "linear", "Linear", ProviderKind::Tracker),
124];
125
126impl Provider {
127 pub fn spec(self) -> &'static Spec {
128 PROVIDERS
129 .iter()
130 .find(|spec| spec.provider == self)
131 .expect("every provider is in the catalogue")
132 }
133
134 pub fn all() -> impl Iterator<Item = Provider> {
135 PROVIDERS.iter().map(|spec| spec.provider)
136 }
137
138 pub fn name(self) -> &'static str {
139 self.spec().name
140 }
141
142 pub fn parse(name: &str) -> Option<Provider> {
143 PROVIDERS.iter().find(|spec| spec.name == name).map(|spec| spec.provider)
144 }
145
146 /// What people call it.
147 pub fn label(self) -> &'static str {
148 self.spec().label
149 }
150
151 pub fn kind(self) -> ProviderKind {
152 self.spec().kind
153 }
154
155 /// Whether it sends g1t requests, at the connection's own address.
156 pub fn receives(self) -> bool {
157 self.kind() == ProviderKind::Alerts
158 }
159
160 /// For a model provider, the API it speaks: `anthropic` or `openai`.
161 pub fn api(self) -> &'static str {
162 self.spec().api
163 }
164
165 /// Whether the connection gives the address, rather than g1t knowing it.
166 pub fn own_address(self) -> bool {
167 self.kind() == ProviderKind::Models && self.spec().base_url.is_empty()
168 }
169}
Merge branch 'model-routing'170
The Packages page names every registry it has171#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
172#[serde(rename_all = "snake_case")]
173pub enum ProviderKind {
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.174 /// An AI provider whose credentials the workspace keeps.
The Packages page names every registry it has175 Models,
176 /// Problems that become issues.
177 Alerts,
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.178 /// Tickets that people read and import.
The Packages page names every registry it has179 Tracker,
180}
181
182/// A connection's settings: everything about it except its secrets. Each
183/// provider uses the fields that apply to it.
184#[derive(Clone, Debug, Serialize, Deserialize)]
185#[serde(rename_all = "camelCase")]
186pub struct ConnectionConfig {
187 /// For alerts: the repository issues are opened in, `owner/name`. For a
188 /// tracker: where an imported ticket goes when no repository is named.
189 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub repo: Option<String>,
191 /// For alerts: the label put on each issue opened. `bug` when unset.
192 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub label: Option<String>,
194 /// Tell the outside system when the work lands: resolve the Sentry
195 /// issue, comment on the ticket.
196 #[serde(default = "yes")]
197 pub write_back: bool,
198 /// Sentry: the organization's slug.
199 #[serde(default, skip_serializing_if = "Option::is_none")]
200 pub organization: Option<String>,
201 /// The system's address, for Jira (`https://acme.atlassian.net`) or a
202 /// Sentry that is not sentry.io.
203 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub site: Option<String>,
205 /// Jira: the account the API token belongs to.
206 #[serde(default, skip_serializing_if = "Option::is_none")]
207 pub email: Option<String>,
208 /// Jira project keys or Linear team keys this connection answers for,
209 /// such as `TECH`. Empty answers for every key.
210 #[serde(default, skip_serializing_if = "Vec::is_empty")]
211 pub keys: Vec<String>,
212 /// Your own endpoint: its base URL, without `/v1`.
213 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub base_url: Option<String>,
215 /// Your own endpoint: send the key as `x-api-key` (the default) or as
216 /// `authorization: Bearer`.
217 #[serde(default, skip_serializing_if = "Option::is_none")]
218 pub auth_header: Option<String>,
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.219 /// For an AI provider: the model the team uses on it, when it names one.
The Packages page names every registry it has220 #[serde(default, skip_serializing_if = "Option::is_none")]
221 pub model: Option<String>,
222}
223
224fn yes() -> bool {
225 true
226}
227
228impl Default for ConnectionConfig {
229 fn default() -> Self {
230 ConnectionConfig {
231 repo: None,
232 label: None,
233 write_back: true,
234 organization: None,
235 site: None,
236 email: None,
237 keys: Vec::new(),
238 base_url: None,
239 auth_header: None,
240 model: None,
241 }
242 }
243}
244
245/// A connection, as anyone in the workspace sees it. Secrets are never
246/// shown after they are saved; `secretHint` is enough to tell keys apart.
247#[derive(Clone, Debug, Serialize, Deserialize)]
248#[serde(rename_all = "camelCase")]
249pub struct Connection {
250 pub id: String,
251 pub workspace: String,
252 pub provider: Provider,
253 pub kind: ProviderKind,
254 pub name: String,
255 pub config: ConnectionConfig,
256 /// The last four characters of the saved key, such as `…3f9a`.
257 pub secret_hint: Option<String>,
258 /// For a provider that sends g1t requests: where it sends them.
259 pub webhook_url: Option<String>,
260 pub created_by: String,
261 /// RFC 3339.
262 pub created_at: String,
263 pub last_used_at: Option<String>,
264 /// The last thing that went wrong talking to it, until it next works.
265 pub last_error: Option<String>,
266 /// For a model provider: the models it offered when last checked.
267 #[serde(default)]
268 pub models: Vec<String>,
269}
270
271/// One request an outside system sent, and what g1t did with it.
272#[derive(Clone, Debug, Serialize, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct Delivery {
275 pub id: String,
276 /// RFC 3339.
277 pub received_at: String,
278 /// What it was about, in the sender's terms: `issue.created`.
279 pub event: String,
280 /// `opened`, `updated`, `reopened`, `ignored` or `refused`.
281 pub outcome: String,
282 pub detail: String,
283 /// The issue it opened or updated, `owner/name#number`.
284 pub issue: Option<String>,
285}
286
287/// Something outside g1t, fetched as it is now. Its text was written outside
288/// g1t, so it is reference material and never instructions.
289#[derive(Clone, Debug, Serialize, Deserialize)]
290#[serde(rename_all = "camelCase")]
291pub struct ContextItem {
292 pub provider: Provider,
293 /// `TECH-1234`, or the Sentry issue's short id.
294 pub key: String,
295 pub title: String,
296 pub url: String,
297 /// Its status in that system: `In Progress`, `unresolved`.
298 pub status: Option<String>,
299 /// Its description, as plain text, shortened if long.
300 pub body: String,
301 /// RFC 3339: when g1t fetched it.
302 pub fetched_at: String,
303}
304
305/// An issue's tie to something outside g1t.
306#[derive(Clone, Debug, Serialize, Deserialize)]
307#[serde(rename_all = "camelCase")]
308pub struct Link {
309 pub provider: Provider,
310 pub connection_id: String,
311 pub key: String,
312 pub title: String,
313 pub url: String,
314 /// How many times an alert has fired for it.
315 pub count: u32,
316 /// RFC 3339.
317 pub first_seen: String,
318 pub last_seen: String,
319}
320
321// --- Methods -----------------------------------------------------------------
322
323/// `list`. Returns `Outcome<Vec<Connection>>`. Members only.
324#[derive(Debug, Serialize, Deserialize)]
325pub struct ListArgs {
326 pub workspace: String,
327 pub viewer: Viewer,
328}
329
330/// `connect`. Returns `Outcome<Connected>`. Owners only.
331#[derive(Debug, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase")]
333pub struct ConnectArgs {
334 pub actor: User,
335 pub workspace: String,
336 pub provider: Provider,
337 #[serde(default)]
338 pub name: Option<String>,
339 #[serde(default)]
340 pub config: ConnectionConfig,
341 /// The API key or token g1t uses to call it.
342 #[serde(default)]
343 pub secret: Option<String>,
344 /// What it signs its requests to g1t with: Sentry's client secret.
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.345 /// Made by g1t for Datadog and webhooks, and shown once.
The Packages page names every registry it has346 #[serde(default)]
347 pub signing_secret: Option<String>,
348}
349
350#[derive(Clone, Debug, Serialize, Deserialize)]
351#[serde(rename_all = "camelCase")]
352pub struct Connected {
353 pub connection: Connection,
354 /// A signing secret g1t made, shown this once.
355 pub signing_secret: Option<String>,
356}
357
358/// `update`: only the fields given change. Returns `Outcome<Connection>`.
359/// Owners only.
360#[derive(Debug, Serialize, Deserialize)]
361#[serde(rename_all = "camelCase")]
362pub struct UpdateArgs {
363 pub actor: User,
364 pub workspace: String,
365 pub id: String,
366 #[serde(default)]
367 pub name: Option<String>,
368 #[serde(default)]
369 pub config: Option<ConnectionConfig>,
370 #[serde(default)]
371 pub secret: Option<String>,
372 #[serde(default)]
373 pub signing_secret: Option<String>,
374}
375
376/// `disconnect` and `test`. `disconnect` returns `Outcome<bool>`; `test`
377/// returns `Outcome<Tested>`. Owners only.
378#[derive(Debug, Serialize, Deserialize)]
379pub struct ConnectionArgs {
380 pub actor: User,
381 pub workspace: String,
382 pub id: String,
383}
384
385#[derive(Clone, Debug, Serialize, Deserialize)]
386pub struct Tested {
387 pub ok: bool,
388 pub message: String,
389}
390
391/// `deliveries`: the latest requests a connection received, newest first.
392/// Returns `Outcome<Vec<Delivery>>`. Members only.
393#[derive(Debug, Serialize, Deserialize)]
394pub struct DeliveriesArgs {
395 pub workspace: String,
396 pub viewer: Viewer,
397 pub id: String,
398}
399
400/// `receive`: a request an outside system sent to a connection's address.
401/// Returns `Received`. Anyone can send one; only a signed one is acted on.
402#[derive(Debug, Serialize, Deserialize)]
403pub struct ReceiveArgs {
404 pub id: String,
405 /// Header names in lowercase.
406 pub headers: std::collections::HashMap<String, String>,
407 pub body: String,
408}
409
410#[derive(Clone, Debug, Serialize, Deserialize)]
411pub struct Received {
412 /// The HTTP status to answer with.
413 pub status: u16,
414 pub message: String,
415}
416
417/// `resolve`: fetches one outside reference. Returns `Outcome<ContextItem>`.
418/// Members of the workspace only.
419#[derive(Debug, Serialize, Deserialize)]
420pub struct ResolveArgs {
421 pub workspace: String,
422 pub viewer: Viewer,
423 /// `TECH-1234`, or a Jira, Linear or Sentry address.
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.424 pub reference: String,
425}
426
The Packages page names every registry it has427/// `import`: opens an issue from a ticket. Returns `Outcome<Imported>`.
428#[derive(Debug, Serialize, Deserialize)]
429pub struct ImportArgs {
430 pub actor: User,
431 pub repo: RepoPath,
432 pub reference: String,
433}
434
435#[derive(Clone, Debug, Serialize, Deserialize)]
436pub struct Imported {
437 pub number: u32,
438 pub item: ContextItem,
439 /// False when the ticket had been imported already, and `number` is
440 /// that issue.
441 pub created: bool,
442}
443
444/// `links`: what an issue is tied to outside g1t. Returns `Vec<Link>`.
445/// Callers must have checked the viewer may see the issue.
446#[derive(Debug, Serialize, Deserialize)]
447#[serde(rename_all = "camelCase")]
448pub struct LinksArgs {
449 pub repo: RepoPath,
450 pub number: u32,
451}
452
Merge branch 'ask-g1t'453// --- Ask g1t ---------------------------------------------------------------
454
455/// The most messages one ask sends: the conversation's latest turns.
456pub const ASK_MAX_MESSAGES: usize = 40;
457/// The longest one message may be, in characters.
458pub const ASK_MAX_MESSAGE_CHARS: usize = 16_000;
459/// The longest a whole conversation may be, in characters.
460pub const ASK_MAX_TOTAL_CHARS: usize = 64_000;
461/// The most tokens one answer may run to.
462pub const ASK_MAX_OUTPUT_TOKENS: u32 = 4_096;
463
464/// What Ask g1t answers with in a workspace: the connection and model an
465/// owner chose, or why it cannot answer yet.
466#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
467pub struct AskSetup {
468 pub connection_id: Option<String>,
469 /// The connection's name, as its owner named it.
470 pub connection_name: Option<String>,
471 pub provider: Option<Provider>,
472 pub model: Option<String>,
473 /// Why Ask cannot answer, in words for the person asking. `None` when
474 /// it can.
475 pub problem: Option<String>,
476}
477
478/// `ask_settings`: what Ask answers with. Returns `Outcome<AskSetup>`.
479/// Members only.
480#[derive(Debug, Serialize, Deserialize)]
481pub struct AskSettingsArgs {
482 pub workspace: String,
483 pub viewer: Viewer,
484}
485
486/// `set_ask_settings`: chooses the connection and model Ask answers with;
487/// no `connection_id` turns Ask off. Returns `Outcome<AskSetup>`. Owners
488/// only.
489#[derive(Debug, Serialize, Deserialize)]
490pub struct SetAskSettingsArgs {
491 pub actor: User,
492 pub workspace: String,
493 #[serde(default)]
494 pub connection_id: Option<String>,
495 /// One of the models the connection offers; its own model (an Azure
496 /// deployment) when not given.
497 #[serde(default)]
498 pub model: Option<String>,
499}
500
501#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
502#[serde(rename_all = "snake_case")]
503pub enum AskRole {
504 User,
505 Assistant,
506}
507
508/// One turn of an Ask conversation.
509#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
510pub struct AskMessage {
511 pub role: AskRole,
512 pub content: String,
513}
514
515/// Where the person asking is in g1t, so the answer is about their work.
516#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
517pub struct AskPage {
518 /// The page's path, such as `/acme/web/issues/12`.
519 #[serde(default)]
520 pub path: Option<String>,
521 #[serde(default)]
522 pub repo: Option<RepoPath>,
523 #[serde(default)]
524 pub issue: Option<u32>,
525 #[serde(default)]
526 pub pull: Option<u32>,
527}
528
529/// `ask`: one answer, streamed as newline-delimited JSON [`AskEvent`]s:
530/// `meta` first, then `text` as it comes, then `done`, or `error` at any
531/// point. Members only. The conversation alternates, starting and ending
532/// with the person.
533#[derive(Debug, Serialize, Deserialize)]
534pub struct AskArgs {
535 pub actor: User,
536 pub workspace: String,
537 pub messages: Vec<AskMessage>,
538 #[serde(default)]
539 pub page: AskPage,
540}
541
542/// One line of an `ask` answer.
543#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
544#[serde(tag = "type", rename_all = "snake_case")]
545pub enum AskEvent {
546 /// Who is answering.
547 Meta { provider: Provider, provider_label: String, model: String },
548 /// The next piece of the answer.
549 Text { text: String },
550 /// The answer is complete. `stop_reason` as the provider said it, such
551 /// as `end_turn`, `stop` or `max_tokens`.
552 Done { stop_reason: Option<String> },
553 /// The answer stopped: what went wrong, for the person asking.
554 Error { message: String },
555}
556
The Packages page names every registry it has557#[cfg(test)]
558mod tests {
559 use super::*;
560
561 #[test]
562 fn every_provider_is_named_once_and_found_again() {
563 let mut names: Vec<&str> = PROVIDERS.iter().map(|spec| spec.name).collect();
564 for provider in Provider::all() {
565 assert_eq!(Provider::parse(provider.name()), Some(provider));
566 }
567 names.sort();
568 names.dedup();
569 assert_eq!(names.len(), PROVIDERS.len());
570 }
571
572 #[test]
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.573 fn ai_providers_are_connections_like_any_other() {
574 assert_eq!(Provider::parse("anthropic"), Some(Provider::Anthropic));
575 let stored: Provider = serde_json::from_str("\"openai_endpoint\"").unwrap();
576 assert_eq!(stored.kind(), ProviderKind::Models);
577 assert!(stored.own_address());
578 assert!(!Provider::Anthropic.own_address());
579 assert!(Provider::all().any(|provider| provider == Provider::Groq));
580 assert!(Provider::all().any(|provider| provider == Provider::Sentry));
AI Gateway: OpenAI's format, open models, and your own providers581 }
582
583 #[test]
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.584 fn stored_configs_with_old_fields_parse() {
585 let config: ConnectionConfig =
586 serde_json::from_str(r#"{"repo":"acme/web","assign":true,"gatewayModels":["*"],"writeBack":false}"#).unwrap();
587 assert_eq!(config.repo.as_deref(), Some("acme/web"));
588 assert!(!config.write_back);
AI Gateway: OpenAI's format, open models, and your own providers589 }
590
591 #[test]
The Packages page names every registry it has592 fn model_providers_say_how_to_reach_them() {
593 for spec in PROVIDERS.iter().filter(|spec| spec.kind == ProviderKind::Models) {
594 assert!(spec.api == "anthropic" || spec.api == "openai", "{}", spec.name);
595 assert!(!spec.auth_header.is_empty(), "{}", spec.name);
596 assert!(spec.base_url.is_empty() || spec.base_url.starts_with("https://"), "{}", spec.name);
597 }
598 }
Merge branch 'ask-g1t'599
600 #[test]
601 fn ask_events_are_tagged_lines() {
602 let line = serde_json::to_string(&AskEvent::Text { text: "Hi".into() }).unwrap();
603 assert_eq!(line, r#"{"type":"text","text":"Hi"}"#);
604 let meta: AskEvent =
605 serde_json::from_str(r#"{"type":"meta","provider":"anthropic","provider_label":"Anthropic","model":"claude"}"#).unwrap();
606 assert_eq!(
607 meta,
608 AskEvent::Meta { provider: Provider::Anthropic, provider_label: "Anthropic".into(), model: "claude".into() }
609 );
610 let page: AskPage = serde_json::from_str("{}").unwrap();
611 assert_eq!(page, AskPage::default());
612 }
The Packages page names every registry it has613}

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