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.
A workspace's owners can still connect, test, change and remove its AI providers (Anthropic, OpenAI, Google, Azure, an endpoint of your own and the rest), on Integrations under "Your AI providers": keys are sealed when saved, and testing one asks the provider for its models with the workspace's key. They are kept so g1t can hand work to them later; nothing routes through g1t. The top bar has an Ask AI button whose composer says it is coming soon and sends nothing. The Sessions pages for what coding agents recorded keep working.
131 files+297−211115 files not counted0/131 viewed
| 410 | 410 | { "day": "2026-10-01", "product": "other", "micros": 8_000_000 }, | |
| 411 | 411 | ], | |
| 412 | 412 | "products": [ | |
| 413 | − | { "key": "other", "label": "Other", "micros": 12_000_000, "features": [], | |
| 413 | + | { "key": "other", "label": "Other", "micros": 12_000_000, | |
| 414 | 414 | "meters": [{ "key": "other", "label": "Other", "product": "other", "unit": "entries", "quantity": 3.0, | |
| 415 | 415 | "micros": 12_000_000, "pendingMicros": 0, "daily": [8_000_000, 4_000_000], "allowance": null, | |
| 416 | 416 | "byProject": [{ "project": "acme/web", "micros": 9_000_000, "quantity": 2.0 }, | |
| 417 | 417 | { "project": "", "micros": 3_000_000, "quantity": 1.0 }] }] }, | |
| 418 | − | { "key": "sandboxes", "label": "Sandboxes", "micros": 500_000, "features": [], | |
| 418 | + | { "key": "sandboxes", "label": "Sandboxes", "micros": 500_000, | |
| 419 | 419 | "meters": [{ "key": "sandbox", "label": "Sandbox time", "product": "sandboxes", "unit": "seconds", "quantity": 600.0, | |
| 420 | 420 | "micros": 500_000, "pendingMicros": 0, "daily": [0, 500_000], | |
| 421 | 421 | "allowance": { "used": 600.0, "of": 3600.0, "unit": "seconds" }, |
| 5 | 5 | //! thing, brought back to the top as things happen to it. | |
| 6 | 6 | //! | |
| 7 | 7 | //! Every operation here is the person's own: a personal access token or a | |
| 8 | − | //! session, never a workspace's token or g1t's agents (which act as g1t, | |
| 9 | − | //! and g1t is never told anything). | |
| 8 | + | //! session, never a workspace's token or a run's token (which acts as | |
| 9 | + | //! g1t, and g1t is never told anything). | |
| 10 | 10 | ||
| 11 | 11 | use g1t_contracts::inbox::*; | |
| 12 | 12 | use g1t_contracts::repos::{GetArgs, Repo, RepoPath}; |
| 536 | 536 | ), | |
| 537 | 537 | ( | |
| 538 | 538 | "Integrations", | |
| 539 | − | "A workspace's connections to outside systems: alert sources and issue trackers.", | |
| 539 | + | "A workspace's connections to outside systems: AI providers, alert sources and issue trackers.", | |
| 540 | 540 | &[ | |
| 541 | 541 | Op::ListIntegrations, | |
| 542 | 542 | Op::ConnectIntegration, |
| 1467 | 1467 | "The timeline of a repository: pushes, issues, pull requests, comments and session activity, newest first." | |
| 1468 | 1468 | } | |
| 1469 | 1469 | Op::ListIntegrations => { | |
| 1470 | − | "A workspace's integrations: the alert sources that open issues (Sentry, Datadog, webhooks), and the trackers whose tickets agents can read (Jira, Linear). Secrets are never returned. Members only." | |
| 1470 | + | "A workspace's integrations: its AI providers, the alert sources that open issues (Sentry, Datadog, webhooks), and the trackers whose tickets agents can read (Jira, Linear). Secrets are never returned. Members only." | |
| 1471 | 1471 | } | |
| 1472 | 1472 | Op::ConnectIntegration => { | |
| 1473 | − | "Connect a workspace to an outside system. provider is sentry, datadog, webhook, jira or linear. config holds the settings each needs; secret is the API key or token, kept encrypted and never returned (secret_hint shows its last four characters). For datadog and webhook, g1t makes the signing secret and returns it once. Owners only." | |
| 1473 | + | "Connect a workspace to an outside system. provider is an AI provider (anthropic, openai, gemini, xai, mistral, deepseek, azure_openai, openrouter, groq, together, fireworks, cerebras, anthropic_endpoint or openai_endpoint), which stores your AI provider credentials for the workspace, or sentry, datadog, webhook, jira or linear. config holds the settings each needs; secret is the API key or token, kept encrypted and never returned (secret_hint shows its last four characters). An AI provider's key is checked as it is saved. For datadog and webhook, g1t makes the signing secret and returns it once. Owners only." | |
| 1474 | 1474 | } | |
| 1475 | 1475 | Op::UpdateIntegration => { | |
| 1476 | − | "Change an integration: its name, its config (replaced whole when given) or its secret (a new key replaces the old one, write-only). Use it to rotate a key. Fields left out are kept. Secrets are never returned. Owners only." | |
| 1476 | + | "Change an integration: its name, its config (replaced whole when given) or its secret (a new key replaces the old one, write-only). Use it to rotate a key, such as an AI provider's. Fields left out are kept. Secrets are never returned. Owners only." | |
| 1477 | 1477 | } | |
| 1478 | 1478 | Op::DisconnectIntegration => { | |
| 1479 | 1479 | "Remove an integration and its secrets. Owners only." | |
| ⋯ | |||
| 1707 | 1707 | "Choose what happens when a team is asked to review a pull request. Off, everyone in it is asked. On (`enabled`), g1t picks `count` people from it (1 to 10, never the pull request's author) and asks them, and the team stays shown as asked beside them: `round_robin` picks whoever this team asked least recently, `load_balance` whoever has the fewest pull requests waiting on their review. `skip_busy` leaves out anyone with `busy_at` or more waiting; `include_child_teams` also picks from its child teams' people; `excluded` lists usernames never picked; `notify_team` also tells the rest of the team. Fields left out keep their current value. Owners of the workspace and the team's maintainers. People only. Returns the team." | |
| 1708 | 1708 | } | |
| 1709 | 1709 | Op::GetUsage => { | |
| 1710 | − | "A workspace's usage over a range of days, at price, and what paid for it. `from` and `until` are UTC days, `YYYY-MM-DD`, with `until` included and at most 400 days in all; left out, the current month so far. `products` narrows it to product families (sandboxes, deployments, git_storage, packages, security, search, other) and `projects` to repositories (\"owner/name\"). Returns `totals`: `price_micros` less `discount_micros`, `included_micros` and `credits_micros` is `charged_micros`, what is left for the workspace to pay; `pending_micros` is metered this month and charged when it closes, counted in any range that reaches into the current month, and `pending_charged_micros` is what of it will be charged then, after the discount and what g1t covers; `cost_micros` is what it cost g1t. Then `days` (each day and product with usage), `products` (every family, with its meters: quantity, unit, amount, a `daily` amount for each day of the range, any `allowance`, and the split `by_project`), `projects` (every repository with usage in the range), and the credit left now: `credit_micros`, and `ai_credit_micros` for AI credit bought earlier, which still pays toward what is owed. Usage on a meter g1t no longer has, such as agent runs and model requests from before they were retired, is kept on one meter in a last product, `other`, shown only when it has something, so nothing charged is left out. `models`, `by_agent` and `by_person` are always empty, and each product's `features` too. With `group_by` (`product`, `project` or `day`), `groups` adds up the range that way. Amounts are whole millionths of a dollar. Members of the workspace only." | |
| 1710 | + | "A workspace's usage over a range of days, at price, and what paid for it. `from` and `until` are UTC days, `YYYY-MM-DD`, with `until` included and at most 400 days in all; left out, the current month so far. `products` narrows it to product families (sandboxes, deployments, git_storage, packages, security, search, other) and `projects` to repositories (\"owner/name\"). Returns `totals`: `price_micros` less `discount_micros`, `included_micros` and `credits_micros` is `charged_micros`, what is left for the workspace to pay; `pending_micros` is metered this month and charged when it closes, counted in any range that reaches into the current month, and `pending_charged_micros` is what of it will be charged then, after the discount and what g1t covers; `cost_micros` is what it cost g1t. Then `days` (each day and product with usage), `products` (every family, with its meters: quantity, unit, amount, a `daily` amount for each day of the range, any `allowance`, and the split `by_project`), `projects` (every repository with usage in the range), and the credit left now: `credit_micros`, and `ai_credit_micros` for AI credit bought earlier, which still pays toward what is owed. Usage on a meter g1t no longer has, such as agent runs and model requests from before they were retired, is kept on one meter in a last product, `other`, shown only when it has something, so nothing charged is left out. With `group_by` (`product`, `project` or `day`), `groups` adds up the range that way. Amounts are whole millionths of a dollar. Members of the workspace only." | |
| 1711 | 1711 | } | |
| 1712 | 1712 | Op::GetBudget => { | |
| 1713 | 1713 | "A workspace's budget: its monthly spend limit (`amount_micros`; `automatic` is true while the owners have not set one, and it is then $200 or twice last month's spend), what was charged this month (`spent_micros`), the most the owners may set it to themselves (`max_amount_micros`), its `alerts` (percent of the limit, each emailed to the owners once a month), whether usage pauses at the limit (`pause_at_limit`), the `webhook` told of each alert, and `state`: `ok`, `warning` or `stopped`, with a `message` when work is stopped or close to it. Members of the workspace only." | |
| 1714 | 1714 | } | |
| 1715 | 1715 | Op::SetBudget => { | |
| 1716 | − | "Change a workspace's budget. Give only what you change; the rest stays as it is. `amount_micros` is the monthly spend limit, up to `max_amount_micros`, or null for the automatic one. `alerts` is some of 50, 75, 90 and 100, in percent of the limit. `pause_at_limit` false makes the limit alert only, without pausing usage; g1t's own ceiling still applies. `webhook` is an https:// address sent a JSON POST for each alert, or null for none. Owners only, as a person: signed in or with a personal access token. A workspace's own token and g1t's agents can read the budget but never change it. Returns the budget." | |
| 1716 | + | "Change a workspace's budget. Give only what you change; the rest stays as it is. `amount_micros` is the monthly spend limit, up to `max_amount_micros`, or null for the automatic one. `alerts` is some of 50, 75, 90 and 100, in percent of the limit. `pause_at_limit` false makes the limit alert only, without pausing usage; g1t's own ceiling still applies. `webhook` is an https:// address sent a JSON POST for each alert, or null for none. Owners only, as a person: signed in or with a personal access token. A workspace's own token and a run's token can read the budget but never change it. Returns the budget." | |
| 1717 | 1717 | } | |
| 1718 | 1718 | Op::ListInvoices => { | |
| 1719 | 1719 | "A workspace's invoices, newest first. `invoices` is every invoice billed to it (the plan, usage and any AI credit bought earlier), each with its `status`, `total_cents`, `currency` and links to view it and its PDF. `usage_invoices` are g1t's itemised invoices for usage, one when each month closes and one each time the card is charged near the limit, with their `lines` in millionths of a dollar; `amount_micros` is the usage, and the card processing fee (`fee_micros`) and tax (`tax_micros`) are on top. Prices exclude tax: Stripe adds it where it applies. `upcoming` is what the next invoice comes to so far. `unavailable` says why `invoices` could not be read just now, when it could not. Members of the workspace only." | |
| ⋯ | |||
| 2445 | 2445 | "name": { "type": "string", "description": "What to call it. The provider's name if left out." }, | |
| 2446 | 2446 | "config": { | |
| 2447 | 2447 | "type": "object", | |
| 2448 | − | "description": "Settings. repo (owner/name) is where alerts open issues; label names the label (bug). organization is the Sentry org's slug. site is Jira's address; email the account its token belongs to; keys the project or team keys it answers for. write_back (default true) tells the outside system when the work lands.", | |
| 2448 | + | "description": "Settings. repo (owner/name) is where alerts open issues; label names the label (bug). organization is the Sentry org's slug. site is Jira's address; email the account its token belongs to; keys the project or team keys it answers for. For an AI provider, base_url and auth_header (x-api-key or authorization) are for your own endpoint (and base_url for an Azure OpenAI resource), and model names the model or Azure deployment the team uses. write_back (default true) tells the outside system when the work lands.", | |
| 2449 | 2449 | }, | |
| 2450 | 2450 | "secret": { "type": "string", "description": "The API key or token g1t uses to call it. Write-only: kept encrypted, never returned." }, | |
| 2451 | 2451 | "signing_secret": { "type": "string", "description": "For sentry: the integration's client secret." }, | |
| ⋯ | |||
| 5061 | 5061 | lead: None, | |
| 5062 | 5062 | channel_id: None, | |
| 5063 | 5063 | channel_name: None, | |
| 5064 | − | budget_micros: None, | |
| 5065 | 5064 | surface: Some(services.audit.surface), | |
| 5066 | 5065 | }; | |
| 5067 | 5066 | if args.name.is_none() | |
| 3 | 3 | //! projects service keeps them; this is their public shape, in snake_case. | |
| 4 | 4 | //! | |
| 5 | 5 | //! A person's own: a personal access token or a session, never a | |
| 6 | − | //! workspace's token or g1t's agents. | |
| 6 | + | //! workspace's token or a run's token. | |
| 7 | 7 | ||
| 8 | 8 | use g1t_contracts::{FailureCode, Outcome, PrincipalKind, Viewer}; | |
| 9 | 9 | use serde_json::{Value, json}; |
| 825 | 825 | "required_approvals": 0, | |
| 826 | 826 | "count_agent_approvals": true, | |
| 827 | 827 | "allow_ignoring_checks": true, | |
| 828 | − | "agent_review": true, | |
| 829 | − | "max_revisions": 2, | |
| 830 | 828 | "merge_queue": false, | |
| 831 | − | "hold_low_confidence": true, | |
| 832 | 829 | "require_code_owner_review": false, | |
| 833 | 830 | "updated_by": null, | |
| 834 | 831 | "updated_at": null | |
| ⋯ | |||
| 855 | 852 | "required_approvals": 1, | |
| 856 | 853 | "count_agent_approvals": false, | |
| 857 | 854 | "allow_ignoring_checks": true, | |
| 858 | − | "agent_review": true, | |
| 859 | − | "max_revisions": 2, | |
| 860 | 855 | "merge_queue": true, | |
| 861 | − | "hold_low_confidence": true, | |
| 862 | 856 | "require_code_owner_review": true, | |
| 863 | 857 | "updated_by": "syntaqx", | |
| 864 | 858 | "updated_at": "2026-10-04T16:20:37.508Z" | |
| 865 | 859 | }, | |
| 866 | − | "notes": "`required_checks` replaces the whole list: at most 20 names, each a workflow's name or another status's context, as [`list_check_names`](/reference/api/repositories/list-check-names/) gives them. A pull request merges only once each passes on its head; one nothing has reported holds it too. With the merge queue on, each must also pass on the queued state, so the workflows behind them need `merge_group` in their `on:`. `required_approvals` is at most 6 and `max_revisions` at most 5. See [what a repository can ask for](/guides/working-with-g1t/#what-a-repository-can-ask-for). `hold_low_confidence` is on unless turned off: see [confidence](/guides/working-with-g1t/#how-sure-the-agent-is). With `require_code_owner_review`, a pull request into the default branch merges only once the code owners of every file it changes have approved it, as many as each section of its CODEOWNERS file asks: see [Code owners](/guides/codeowners/)." | |
| 860 | + | "notes": "`required_checks` replaces the whole list: at most 20 names, each a workflow's name or another status's context, as [`list_check_names`](/reference/api/repositories/list-check-names/) gives them. A pull request merges only once each passes on its head; one nothing has reported holds it too. With the merge queue on, each must also pass on the queued state, so the workflows behind them need `merge_group` in their `on:`. `required_approvals` is at most 6. See [what a repository can ask for](/guides/working-with-g1t/#what-a-repository-can-ask-for). With `require_code_owner_review`, a pull request into the default branch merges only once the code owners of every file it changes have approved it, as many as each section of its CODEOWNERS file asks: see [Code owners](/guides/codeowners/)." | |
| 867 | 861 | }, | |
| 868 | 862 | "list_check_names": { | |
| 869 | 863 | "response": [ | |
| ⋯ | |||
| 1555 | 1549 | "requested_by": null, | |
| 1556 | 1550 | "created_at": "2026-10-01T18:20:02.117Z", | |
| 1557 | 1551 | "updated_at": "2026-10-01T18:35:44.902Z", | |
| 1558 | − | "confidence": null, | |
| 1559 | 1552 | "labels": [], | |
| 1560 | 1553 | "milestone": null, | |
| 1561 | 1554 | "base": "main" | |
| ⋯ | |||
| 1644 | 1637 | "checks": null, | |
| 1645 | 1638 | "overlaps": [], | |
| 1646 | 1639 | "behind": false, | |
| 1647 | − | "review_pending": false, | |
| 1648 | 1640 | "lifecycle": null, | |
| 1649 | 1641 | "landing": false, | |
| 1650 | 1642 | "stalled": null, | |
| 1651 | − | "messages": [ | |
| 1652 | − | { | |
| 1653 | − | "id": "msg_01m43tx5egeh4t3f9zcnjenqvz", | |
| 1654 | − | "author": "syntaqx", | |
| 1655 | − | "body": "Keep \"world\" as the default when no name is given.", | |
| 1656 | − | "created_at": "2026-10-01T18:25:00.310Z", | |
| 1657 | − | "delivered_at": "2026-10-01T18:25:31.007Z", | |
| 1658 | − | "kind": "message", | |
| 1659 | − | "from_number": null, | |
| 1660 | − | "to_number": 14, | |
| 1661 | − | "answer": null, | |
| 1662 | − | "declined": false | |
| 1663 | − | } | |
| 1664 | − | ], | |
| 1665 | 1643 | "statuses": [ | |
| 1666 | 1644 | { | |
| 1667 | 1645 | "context": "CI / pull_request", | |
| ⋯ | |||
| 3659 | 3637 | "workspace": "flagon-io" | |
| 3660 | 3638 | }, | |
| 3661 | 3639 | "response": [ | |
| 3662 | − | { | |
| 3663 | − | "id": "con_01kpx3m7q2f8r9t4v6w0y1z2ab", | |
| 3664 | − | "workspace": "flagon-io", | |
| 3665 | − | "provider": "anthropic", | |
| 3666 | − | "kind": "models", | |
| 3667 | − | "name": "Anthropic", | |
| 3668 | − | "config": { | |
| 3669 | − | "assign": false, | |
| 3670 | − | "write_back": true | |
| 3671 | − | }, | |
| 3672 | − | "secret_hint": "…3f9a", | |
| 3673 | − | "webhook_url": null, | |
| 3674 | − | "created_by": "syntaqx", | |
| 3675 | − | "created_at": "2026-10-04T15:42:07.318Z", | |
| 3676 | − | "last_used_at": "2026-10-04T15:42:08.102Z", | |
| 3677 | − | "last_error": null, | |
| 3678 | − | "models": [ | |
| 3679 | − | "claude-haiku-4-5", | |
| 3680 | − | "claude-sonnet-4-5" | |
| 3681 | − | ] | |
| 3682 | − | }, | |
| 3683 | 3640 | { | |
| 3684 | 3641 | "id": "con_01kpx4n8r3g9s0v5w7x1z2a3bc", | |
| 3685 | 3642 | "workspace": "flagon-io", | |
| ⋯ | |||
| 3687 | 3644 | "kind": "tracker", | |
| 3688 | 3645 | "name": "Jira", | |
| 3689 | 3646 | "config": { | |
| 3690 | − | "assign": false, | |
| 3691 | 3647 | "write_back": true, | |
| 3692 | 3648 | "site": "https://acme.atlassian.net", | |
| 3693 | 3649 | "email": "syntaqx@example.com", | |
| ⋯ | |||
| 3704 | 3660 | "models": [] | |
| 3705 | 3661 | } | |
| 3706 | 3662 | ], | |
| 3707 | − | "notes": "`kind` is `models`, `alerts` or `tracker`. `secret_hint` shows the end of the secret; the secret itself is never returned. `webhook_url` is where an alert source sends its alerts." | |
| 3663 | + | "notes": "`kind` is `alerts` or `tracker`. `secret_hint` shows the end of the secret; the secret itself is never returned. `webhook_url` is where an alert source sends its alerts." | |
| 3708 | 3664 | }, | |
| 3709 | 3665 | "connect_integration": { | |
| 3710 | 3666 | "params": { | |
| ⋯ | |||
| 3727 | 3683 | "config": { | |
| 3728 | 3684 | "repo": "flagon-io/hello", | |
| 3729 | 3685 | "label": "bug", | |
| 3730 | − | "assign": false, | |
| 3731 | 3686 | "write_back": true | |
| 3732 | 3687 | }, | |
| 3733 | 3688 | "secret_hint": null, | |
| ⋯ | |||
| 3740 | 3695 | }, | |
| 3741 | 3696 | "signing_secret": "g1ts_9f2c4a7e1b0d3c6f8a2e5b7d9c1f4a6e8b0d2c4f6a8e1b3d" | |
| 3742 | 3697 | }, | |
| 3743 | − | "notes": "`signing_secret` is set only when g1t made it, for `datadog` and `webhook`, and is shown only this once. A model provider is tested as it is connected. See [integrations](/guides/integrations/) and [model providers](/guides/models/)." | |
| 3698 | + | "notes": "`signing_secret` is set only when g1t made it, for `datadog` and `webhook`, and is shown only this once. See [integrations](/guides/integrations/)." | |
| 3744 | 3699 | }, | |
| 3745 | 3700 | "update_integration": { | |
| 3746 | 3701 | "params": { | |
| 3747 | 3702 | "workspace": "flagon-io", | |
| 3748 | − | "id": "con_01kpx5c2d8e4f6g0h2j4k6m8n0" | |
| 3703 | + | "id": "con_01kpx4n8r3g9s0v5w7x1z2a3bc" | |
| 3749 | 3704 | }, | |
| 3750 | 3705 | "request": { | |
| 3751 | 3706 | "config": { | |
| 3752 | − | "base_url": "https://gpu.flagon.dev/v1", | |
| 3753 | − | "gateway_models": [ | |
| 3754 | − | "ollama/*" | |
| 3707 | + | "site": "https://acme.atlassian.net", | |
| 3708 | + | "email": "syntaqx@example.com", | |
| 3709 | + | "keys": [ | |
| 3710 | + | "TECH", | |
| 3711 | + | "OPS" | |
| 3755 | 3712 | ] | |
| 3756 | 3713 | }, | |
| 3757 | − | "secret": "sk-office-gpu-key" | |
| 3714 | + | "secret": "atlassian-api-token" | |
| 3758 | 3715 | }, | |
| 3759 | 3716 | "response": { | |
| 3760 | − | "id": "con_01kpx5c2d8e4f6g0h2j4k6m8n0", | |
| 3717 | + | "id": "con_01kpx4n8r3g9s0v5w7x1z2a3bc", | |
| 3761 | 3718 | "workspace": "flagon-io", | |
| 3762 | − | "provider": "openai_endpoint", | |
| 3763 | − | "kind": "models", | |
| 3764 | − | "name": "Office GPU", | |
| 3719 | + | "provider": "jira", | |
| 3720 | + | "kind": "tracker", | |
| 3721 | + | "name": "Jira", | |
| 3765 | 3722 | "config": { | |
| 3766 | 3723 | "write_back": true, | |
| 3767 | − | "base_url": "https://gpu.flagon.dev/v1", | |
| 3768 | − | "gateway_models": [ | |
| 3769 | − | "ollama/*" | |
| 3724 | + | "site": "https://acme.atlassian.net", | |
| 3725 | + | "email": "syntaqx@example.com", | |
| 3726 | + | "keys": [ | |
| 3727 | + | "TECH", | |
| 3728 | + | "OPS" | |
| 3770 | 3729 | ] | |
| 3771 | 3730 | }, | |
| 3772 | − | "secret_hint": "…-key", | |
| 3731 | + | "secret_hint": "…oken", | |
| 3773 | 3732 | "webhook_url": null, | |
| 3774 | 3733 | "created_by": "syntaqx", | |
| 3775 | − | "created_at": "2026-10-07T12:10:44.512Z", | |
| 3734 | + | "created_at": "2026-10-04T15:42:07.318Z", | |
| 3776 | 3735 | "last_used_at": "2026-10-07T14:00:02.000Z", | |
| 3777 | 3736 | "last_error": null, | |
| 3778 | − | "models": [ | |
| 3779 | − | "llama3.3:70b", | |
| 3780 | − | "qwen3-coder:30b" | |
| 3781 | − | ] | |
| 3737 | + | "models": [] | |
| 3782 | 3738 | }, | |
| 3783 | − | "notes": "The secret is write-only: it is kept encrypted and only its last four characters come back, as `secret_hint`. With `gateway_models` set to `ollama/*`, an AI Gateway request for `ollama/qwen3-coder:30b` reaches this endpoint as `qwen3-coder:30b`, on the workspace's own account. See [the AI Gateway guide](/guides/ai-gateway/#your-own-providers)." | |
| 3739 | + | "notes": "The secret is write-only: it is kept encrypted and only its last four characters come back, as `secret_hint`. Use it to rotate a key." | |
| 3784 | 3740 | }, | |
| 3785 | 3741 | "disconnect_integration": { | |
| 3786 | 3742 | "params": { | |
| 3787 | 3743 | "workspace": "flagon-io", | |
| 3788 | − | "id": "con_01kpx3m7q2f8r9t4v6w0y1z2ab" | |
| 3744 | + | "id": "con_01kpx4n8r3g9s0v5w7x1z2a3bd" | |
| 3789 | 3745 | }, | |
| 3790 | 3746 | "response": true | |
| 3791 | 3747 | }, | |
| 3792 | 3748 | "test_integration": { | |
| 3793 | 3749 | "params": { | |
| 3794 | 3750 | "workspace": "flagon-io", | |
| 3795 | − | "id": "con_01kpx3m7q2f8r9t4v6w0y1z2ab" | |
| 3751 | + | "id": "con_01kpx4n8r3g9s0v5w7x1z2a3bc" | |
| 3796 | 3752 | }, | |
| 3797 | 3753 | "response": { | |
| 3798 | 3754 | "ok": true, | |
| 3799 | − | "message": "Anthropic accepted the key and offers 9 models." | |
| 3755 | + | "message": "Connected to https://acme.atlassian.net as Chase Pierce." | |
| 3800 | 3756 | }, | |
| 3801 | 3757 | "notes": "Credentials that do not work still answer `200`, with `ok` false and `message` saying what went wrong." | |
| 3802 | 3758 | }, | |
| ⋯ | |||
| 5709 | 5665 | } | |
| 5710 | 5666 | ] | |
| 5711 | 5667 | } | |
| 5712 | − | ], | |
| 5713 | − | "features": [] | |
| 5668 | + | ] | |
| 5714 | 5669 | }, | |
| 5715 | 5670 | { | |
| 5716 | 5671 | "key": "other", | |
| ⋯ | |||
| 5748 | 5703 | } | |
| 5749 | 5704 | ] | |
| 5750 | 5705 | } | |
| 5751 | − | ], | |
| 5752 | − | "features": [] | |
| 5706 | + | ] | |
| 5753 | 5707 | } | |
| 5754 | 5708 | ], | |
| 5755 | 5709 | "projects": [ | |
| 5756 | 5710 | "flagon-io/g1t", | |
| 5757 | 5711 | "flagon-io/hello" | |
| 5758 | 5712 | ], | |
| 5759 | − | "models": [], | |
| 5760 | − | "by_agent": [], | |
| 5761 | − | "by_person": [], | |
| 5762 | 5713 | "included": { | |
| 5763 | 5714 | "used": 20, | |
| 5764 | 5715 | "of": 20, | |
| ⋯ | |||
| 5838 | 5789 | "state": "ok", | |
| 5839 | 5790 | "message": null | |
| 5840 | 5791 | }, | |
| 5841 | − | "notes": "Fields left out keep their value. A limit above `max_amount_micros` is refused with `invalid`. Called by anyone but an owner signed in as a person, or by a workspace's own token or one of g1t's agents, it is refused with `forbidden`. Each alert is sent once a month, to the owners by email and, with `webhook`, as a JSON POST." | |
| 5792 | + | "notes": "Fields left out keep their value. A limit above `max_amount_micros` is refused with `invalid`. Called by anyone but an owner signed in as a person, or by a workspace's own token or a run's token, it is refused with `forbidden`. Each alert is sent once a month, to the owners by email and, with `webhook`, as a JSON POST." | |
| 5842 | 5793 | }, | |
| 5843 | 5794 | "list_invoices": { | |
| 5844 | 5795 | "params": { | |
| ⋯ | |||
| 8336 | 8287 | }, | |
| 8337 | 8288 | "requested_by": null, | |
| 8338 | 8289 | "created_at": "2026-10-01T18:20:02.117Z", | |
| 8339 | − | "updated_at": "2026-10-01T18:35:44.902Z", | |
| 8340 | − | "confidence": null | |
| 8290 | + | "updated_at": "2026-10-01T18:35:44.902Z" | |
| 8341 | 8291 | } | |
| 8342 | 8292 | ] | |
| 8343 | 8293 | } | |
| ⋯ | |||
| 8397 | 8347 | }, | |
| 8398 | 8348 | "requested_by": null, | |
| 8399 | 8349 | "created_at": "2026-10-01T18:20:02.117Z", | |
| 8400 | − | "updated_at": "2026-10-01T18:35:44.902Z", | |
| 8401 | − | "confidence": null | |
| 8350 | + | "updated_at": "2026-10-01T18:35:44.902Z" | |
| 8402 | 8351 | }, | |
| 8403 | 8352 | "notes": "A new base takes it out of the merge queue, and whether it is behind, merges cleanly and has the checks it needs is worked out against the new base." | |
| 8404 | 8353 | }, | |
| ⋯ | |||
| 8561 | 8510 | "exceptions": [] | |
| 8562 | 8511 | }, | |
| 8563 | 8512 | "applies_to": "everyone" | |
| 8564 | − | }, | |
| 8565 | − | { | |
| 8566 | − | "type": "cost_cap", | |
| 8567 | − | "parameters": { | |
| 8568 | − | "max_usd": 25 | |
| 8569 | − | }, | |
| 8570 | − | "applies_to": "agents" | |
| 8571 | 8513 | } | |
| 8572 | 8514 | ], | |
| 8573 | 8515 | "created_by": "syntaqx", | |
| ⋯ | |||
| 9227 | 9169 | "exceptions": [] | |
| 9228 | 9170 | }, | |
| 9229 | 9171 | "applies_to": "everyone" | |
| 9230 | − | }, | |
| 9231 | − | { | |
| 9232 | − | "type": "cost_cap", | |
| 9233 | − | "parameters": { | |
| 9234 | − | "max_usd": 25 | |
| 9235 | − | }, | |
| 9236 | − | "applies_to": "agents" | |
| 9237 | 9172 | } | |
| 9238 | 9173 | ], | |
| 9239 | 9174 | "created_by": "syntaqx", | |
| ⋯ | |||
| 9308 | 9243 | "exceptions": [] | |
| 9309 | 9244 | }, | |
| 9310 | 9245 | "applies_to": "everyone" | |
| 9311 | − | }, | |
| 9312 | − | { | |
| 9313 | − | "type": "cost_cap", | |
| 9314 | − | "parameters": { | |
| 9315 | − | "max_usd": 25 | |
| 9316 | − | }, | |
| 9317 | − | "applies_to": "agents" | |
| 9318 | 9246 | } | |
| 9319 | 9247 | ], | |
| 9320 | 9248 | "created_by": "syntaqx", | |
| ⋯ | |||
| 9384 | 9312 | "exceptions": [] | |
| 9385 | 9313 | }, | |
| 9386 | 9314 | "applies_to": "everyone" | |
| 9387 | − | }, | |
| 9388 | − | { | |
| 9389 | − | "type": "cost_cap", | |
| 9390 | − | "parameters": { | |
| 9391 | − | "max_usd": 25 | |
| 9392 | − | }, | |
| 9393 | − | "applies_to": "agents" | |
| 9394 | 9315 | } | |
| 9395 | 9316 | ] | |
| 9396 | 9317 | }, | |
| ⋯ | |||
| 9454 | 9375 | "exceptions": [] | |
| 9455 | 9376 | }, | |
| 9456 | 9377 | "applies_to": "everyone" | |
| 9457 | − | }, | |
| 9458 | − | { | |
| 9459 | − | "type": "cost_cap", | |
| 9460 | − | "parameters": { | |
| 9461 | − | "max_usd": 25 | |
| 9462 | − | }, | |
| 9463 | − | "applies_to": "agents" | |
| 9464 | 9378 | } | |
| 9465 | 9379 | ], | |
| 9466 | 9380 | "created_by": "syntaqx", | |
| ⋯ | |||
| 9538 | 9452 | "exceptions": [] | |
| 9539 | 9453 | }, | |
| 9540 | 9454 | "applies_to": "everyone" | |
| 9541 | − | }, | |
| 9542 | − | { | |
| 9543 | − | "type": "cost_cap", | |
| 9544 | − | "parameters": { | |
| 9545 | − | "max_usd": 25 | |
| 9546 | − | }, | |
| 9547 | − | "applies_to": "agents" | |
| 9548 | 9455 | } | |
| 9549 | 9456 | ], | |
| 9550 | 9457 | "created_by": "syntaqx", | |
| 272 | 272 | route("PUT", "/workspaces/:workspace/budget", Op::SetBudget, &[]), | |
| 273 | 273 | route("GET", "/workspaces/:workspace/invoices", Op::ListInvoices, &[]), | |
| 274 | 274 | route("GET", "/workspaces/:workspace/billing_details", Op::GetBillingDetails, &[]), | |
| 275 | − | // The AI Gateway's log of a workspace's requests. | |
| 276 | 275 | // Code owners: the CODEOWNERS file, checked. | |
| 277 | 276 | route("GET", "/repos/:owner/:name/codeowners/errors", Op::GetCodeownersErrors, &[("ref", "ref")]), | |
| 278 | 277 | // Security alerts: secrets and vulnerable dependencies. |
| 99 | 99 | match self { | |
| 100 | 100 | RulesOp::ListRepoRulesets => "List a repository's rulesets: what may happen to its branches and tags, and what a pull request needs before it merges. With include_parents, also its workspace's rulesets that hold in it (level workspace). Each has its enforcement (active, evaluate: a dry run that records what it would have refused, or disabled), target (branch or tag), conditions (ref_name include and exclude patterns: fnmatch, ~DEFAULT_BRANCH, ~ALL), bypass_actors and rules. The one made from branch protection settings has source branch_protection.", | |
| 101 | 101 | RulesOp::GetRepoRuleset => "Get one of a repository's rulesets by id (rs_…), or one of its workspace's that holds in it.", | |
| 102 | − | RulesOp::CreateRepoRuleset => "Create a repository ruleset: name, enforcement (active, evaluate or disabled; active by default), target (branch or tag), conditions.ref_name (include and exclude patterns), bypass_actors (each a kind: role, team, user, token or g1t, a value, and a mode: always or pull_requests; nobody bypasses unless listed, g1t included) and rules (each a type, its parameters, and applies_to: everyone, agents or people). Rule types: creation, update, deletion, non_fast_forward, required_linear_history, required_signatures, pull_request, required_status_checks, merge_queue, required_deployments, commit_message_pattern, commit_author_email_pattern, committer_email_pattern, branch_name_pattern, tag_name_pattern, file_path_restriction, file_extension_restriction, max_file_size, max_file_path_length, max_files_changed, secret_scanning, confidence_threshold, cost_cap, path_review, merge_window and agent_auto_merge. Several rulesets stack: every rule of each holds. Takes the Admin role. Returns the ruleset as saved, tidied.", | |
| 102 | + | RulesOp::CreateRepoRuleset => "Create a repository ruleset: name, enforcement (active, evaluate or disabled; active by default), target (branch or tag), conditions.ref_name (include and exclude patterns), bypass_actors (each a kind: role, team, user, token or g1t, a value, and a mode: always or pull_requests; nobody bypasses unless listed, g1t included) and rules (each a type, its parameters, and applies_to: everyone, agents or people). Rule types: creation, update, deletion, non_fast_forward, required_linear_history, required_signatures, pull_request, required_status_checks, merge_queue, required_deployments, commit_message_pattern, commit_author_email_pattern, committer_email_pattern, branch_name_pattern, tag_name_pattern, file_path_restriction, file_extension_restriction, max_file_size, max_file_path_length, max_files_changed, secret_scanning, path_review, merge_window and agent_auto_merge. Several rulesets stack: every rule of each holds. Takes the Admin role. Returns the ruleset as saved, tidied.", | |
| 103 | 103 | RulesOp::UpdateRepoRuleset => "Change a repository ruleset. Fields left out stay as they are; rules and bypass_actors, when given, replace the whole list. Takes the Admin role.", | |
| 104 | 104 | RulesOp::DeleteRepoRuleset => "Delete a repository ruleset. Its evaluations stay in the log. Takes the Admin role.", | |
| 105 | 105 | RulesOp::GetBranchRules => "Every rule that holds for a branch (or a tag, with target tag) of a repository, from every ruleset that targets it, the repository's and its workspace's: each with its type, parameters and applies_to, and the ruleset_id, ruleset_name, level and enforcement it comes from. Active rules come first, then those of rulesets in evaluate. rulesets lists the rulesets with who may bypass each. A branch name with slashes is URL-encoded in the path.", |
| 2 | 2 | //! each secret is, push protection bypasses and their review, validity | |
| 3 | 3 | //! checks, custom patterns), code scanning (alerts, analyses, SARIF | |
| 4 | 4 | //! uploads), vulnerability alerts, the dependency graph with its SBOM and | |
| 5 | − | //! dependency review, "Fix with g1t", settings, and the workspace's | |
| 6 | − | //! overview. | |
| 5 | + | //! dependency review, settings, and the workspace's overview. | |
| 7 | 6 | //! | |
| 8 | 7 | //! The addresses follow the common ones (`/repos/{owner}/{name}/secret- | |
| 9 | 8 | //! scanning/alerts`, `/code-scanning/sarifs`, `/dependency-graph/sbom`), | |
| ⋯ | |||
| 1242 | 1241 | } | |
| 1243 | 1242 | assert_eq!(scope_for("get_sbom"), Some(Scope::SecurityRead)); | |
| 1244 | 1243 | assert_eq!(scope_for("upload_sarif"), Some(Scope::SecurityWrite)); | |
| 1245 | − | // Fixing an alert also opens an issue and spends agent time. | |
| 1246 | − | let needed = g1t_contracts::scopes::needed("fix_security_alert", &json!({})); | |
| 1247 | − | assert_eq!(needed, [Scope::SecurityWrite, Scope::IssuesWrite, Scope::AgentsRun]); | |
| 1248 | − | // No agent decides about security. | |
| 1249 | − | for name in ["bypass_push_protection", "review_bypass_request", "update_security_settings", "fix_security_alert"] { | |
| 1244 | + | // No run decides about security. | |
| 1245 | + | for name in ["bypass_push_protection", "review_bypass_request", "update_security_settings"] { | |
| 1250 | 1246 | assert!(g1t_contracts::credentials::NEVER.contains(&name), "{name}"); | |
| 1251 | 1247 | } | |
| 1252 | 1248 | } | |
| 390 | 390 | a("list_invites", Op::ListWorkspaceInvites, "Its invites"), | |
| 391 | 391 | a("invite_member", Op::InviteMember, "Invite someone by username or email address"), | |
| 392 | 392 | a("revoke_invite", Op::RevokeWorkspaceInvite, "Revoke a pending invite"), | |
| 393 | − | a("list_integrations", Op::ListIntegrations, "Alert sources and issue trackers"), | |
| 393 | + | a("list_integrations", Op::ListIntegrations, "AI providers, alert sources and issue trackers"), | |
| 394 | 394 | a("connect_integration", Op::ConnectIntegration, "Connect one"), | |
| 395 | 395 | a("update_integration", Op::UpdateIntegration, "Change one: rotate its key or its settings"), | |
| 396 | 396 | a("disconnect_integration", Op::DisconnectIntegration, "Remove one"), | |
| ⋯ | |||
| 812 | 812 | use super::*; | |
| 813 | 813 | use g1t_contracts::scopes::{Preset, Scope}; | |
| 814 | 814 | ||
| 815 | − | /// Operations g1t no longer has, which the scopes table in | |
| 816 | − | /// g1t-contracts still names until it is trimmed. | |
| 817 | − | const RETIRED: &[&str] = &[ | |
| 818 | − | "message_agent", | |
| 819 | − | "answer_message", | |
| 820 | − | "take_messages", | |
| 821 | − | "search_context", | |
| 822 | − | "get_entity", | |
| 823 | − | "assign_issue", | |
| 824 | − | "delegate", | |
| 825 | − | "plan_work", | |
| 826 | − | "get_plan", | |
| 827 | − | "apply_plan", | |
| 828 | − | "get_model_routes", | |
| 829 | − | "set_model_routes", | |
| 830 | − | "get_agent_computer", | |
| 831 | − | "wake_agent_computer", | |
| 832 | − | "sleep_agent_computer", | |
| 833 | − | "reset_agent_computer", | |
| 834 | − | "list_agent_computer_commands", | |
| 835 | − | "list_gateway_requests", | |
| 836 | − | "fix_security_alert", | |
| 837 | − | "list_marketplaces", | |
| 838 | − | "add_marketplace", | |
| 839 | − | "remove_marketplace", | |
| 840 | − | "refresh_marketplace", | |
| 841 | − | "discover", | |
| 842 | − | "list_plugins", | |
| 843 | − | "get_plugin", | |
| 844 | − | "install_plugin", | |
| 845 | − | "set_plugin_enabled", | |
| 846 | − | "uninstall_plugin", | |
| 847 | − | "update_plugin", | |
| 848 | − | "get_ai_credit", | |
| 849 | − | "buy_ai_credit", | |
| 850 | − | "list_plugin_agents", | |
| 851 | − | "list_connectors", | |
| 852 | − | "get_connector", | |
| 853 | − | "add_connector", | |
| 854 | − | "set_connector_key", | |
| 855 | − | "remove_connector", | |
| 856 | − | "refresh_connector", | |
| 857 | − | "grant_connector", | |
| 858 | − | "revoke_connector", | |
| 859 | − | "customize_bundle", | |
| 860 | − | ]; | |
| 861 | − | ||
| 862 | 815 | fn listed(gate: &Gate) -> Vec<Value> { | |
| 863 | 816 | TOOLS.iter().filter_map(|tool| tool.listed(gate)).collect() | |
| 864 | 817 | } | |
| ⋯ | |||
| 905 | 858 | assert_eq!(mapped + usize::from(free), 1, "{}", op.name()); | |
| 906 | 859 | } | |
| 907 | 860 | for (name, _) in OPERATIONS { | |
| 908 | − | assert!(Op::by_name(name).is_some() || RETIRED.contains(name), "{name} is not an operation"); | |
| 861 | + | assert!(Op::by_name(name).is_some(), "{name} is not an operation"); | |
| 909 | 862 | } | |
| 910 | 863 | } | |
| 911 | 864 | ||
| 18 | 18 | | Where | **Integrations**, at `g1t.sh/<workspace>/-/integrations`, with each integration's setup page under it | **Your settings → Integrations**, at [`g1t.sh/settings/integrations`](https://g1t.sh/settings/integrations) | | |
| 19 | 19 | | Who it is for | Everyone in that workspace | Just you, in every workspace you belong to | | |
| 20 | 20 | | Who connects | Workspace owners. Members see what is connected. | You | | |
| 21 | − | | Examples | Sentry, Jira, Linear, GitHub imports, webhooks | Signing in with GitHub, coding agents you connect through MCP, and soon your calendar | | |
| 21 | + | | Examples | Your AI providers, Sentry, Jira, Linear, GitHub imports, webhooks | Signing in with GitHub, coding agents you connect through MCP, and soon your calendar | | |
| 22 | 22 | ||
| 23 | 23 | Some tools have both sides. GitHub, for instance: a workspace owner | |
| 24 | 24 | installs g1t's GitHub App to import the team's repositories, and | |
| ⋯ | |||
| 31 | 31 | ||
| 32 | 32 | - **Categories** down the side (along the top on a phone): Code & CI, | |
| 33 | 33 | Issues & projects, Chat & meetings, Calendar & email, Docs & knowledge, | |
| 34 | − | Monitoring & incidents, Cloud & deploy, Data & analytics, CRM & support, | |
| 34 | + | Monitoring & incidents, Cloud & deploy, AI models, Data & analytics, CRM & support, | |
| 35 | 35 | Design, Security & identity, and Files & storage. Each shows how many | |
| 36 | 36 | integrations it has. A link can open on one, with `?category=code` and | |
| 37 | 37 | so on. | |
| ⋯ | |||
| 56 | 56 | | Code & CI | [GitHub](/guides/github/) import, [webhooks](/guides/webhooks/) | [Signing in with GitHub](/guides/github/#link-and-unlink-github); [MCP clients](/guides/authentication/#signing-in-with-oauth): your [coding agent](/guides/bring-your-own-agent/), connected to g1t as you | | |
| 57 | 57 | | Issues & projects | [Jira and Linear](#trackers) | | | |
| 58 | 58 | | Monitoring & incidents | [Sentry, Datadog, and any system that sends a webhook](#alerts) | | | |
| 59 | + | | AI models | [Your AI providers](#your-ai-providers): Anthropic, OpenAI, Google Gemini, xAI, Mistral, DeepSeek, Azure OpenAI, OpenRouter, Groq, Together AI, Fireworks AI, Cerebras, and any Anthropic- or OpenAI-compatible endpoint | | | |
| 59 | 60 | ||
| 60 | − | Alerts and trackers each have a setup page under the directory: | |
| 61 | − | `-/integrations/alerts` and `-/integrations/trackers`. GitHub and webhooks | |
| 61 | + | AI providers, alerts and trackers each have a setup page under the | |
| 62 | + | directory: `-/integrations/models`, `-/integrations/alerts` and | |
| 63 | + | `-/integrations/trackers`. GitHub and webhooks | |
| 62 | 64 | keep their own pages; the directory links to them. | |
| 63 | 65 | ||
| 64 | 66 | Secrets are sealed when they are saved and never shown again, to anyone. | |
| ⋯ | |||
| 86 | 88 | not listed offers the same, for whatever you searched for. Requests decide | |
| 87 | 89 | what is built next. | |
| 88 | 90 | ||
| 91 | + | ## Your AI providers | |
| 92 | + | ||
| 93 | + | **Your AI providers**, at `g1t.sh/<workspace>/-/integrations/models`, is | |
| 94 | + | where a workspace keeps the credentials for the AI providers its team | |
| 95 | + | uses, so g1t can hand work to them. Only workspace owners add, test, | |
| 96 | + | change and remove them; members see which are connected. | |
| 97 | + | ||
| 98 | + | - **Add one.** Choose a provider and paste its API key. Anthropic, OpenAI, | |
| 99 | + | Google Gemini, xAI, Mistral, DeepSeek, OpenRouter, Groq, Together AI, | |
| 100 | + | Fireworks AI and Cerebras need only the key. Azure OpenAI also needs | |
| 101 | + | your resource's endpoint and the deployment's name. An Anthropic- or | |
| 102 | + | OpenAI-compatible endpoint (your own gateway, LiteLLM, vLLM and the | |
| 103 | + | like) needs its base URL over `https://`, and takes a key if it asks for | |
| 104 | + | one, sent as `x-api-key` or `Authorization: Bearer`. You can name the | |
| 105 | + | model your team uses on each. | |
| 106 | + | - **Test.** g1t checks a key as it is saved, and again whenever you choose | |
| 107 | + | **Test**, by asking the provider for its models. A key that works shows | |
| 108 | + | how many models it offers; one that fails says why, on the connection, | |
| 109 | + | until it next works. An endpoint that does not list its models is | |
| 110 | + | reached, but its key cannot be checked this way. | |
| 111 | + | - **Change.** **Change its model, address or key** sets a new model or | |
| 112 | + | address, or replaces the key. The old key is gone once the new one is | |
| 113 | + | saved. | |
| 114 | + | - **Remove.** The **×** on a connection disconnects it and deletes its key. | |
| 115 | + | ||
| 116 | + | Keys are sealed when they are saved and never shown again, to anyone; the | |
| 117 | + | page shows only their last four characters. | |
| 118 | + | ||
| 119 | + | The **Ask AI** button in the top bar is where you will start work on one | |
| 120 | + | of these providers. It is not on yet: it shows the composer, marked | |
| 121 | + | coming soon. | |
| 122 | + | ||
| 89 | 123 | ## Alerts | |
| 90 | 124 | ||
| 91 | 125 | An alert source opens issues in one repository. Each problem it reports is | |
| ⋯ | |||
| 272 | 306 | -d '{"provider": "jira", "config": {"site": "https://acme.atlassian.net", "email": "dev@acme.com", "keys": ["TECH"]}, "secret": "<api token>"}' | |
| 273 | 307 | ``` | |
| 274 | 308 | ||
| 275 | − | `provider` is `sentry`, `datadog`, `webhook`, `jira` or `linear`. | |
| 276 | − | `config` takes `repo`, `label`, `write_back`, `organization`, `site`, | |
| 277 | − | `email` and `keys`; each provider uses the ones above. For `datadog` | |
| 309 | + | `provider` is an AI provider (`anthropic`, `openai`, `gemini`, `xai`, | |
| 310 | + | `mistral`, `deepseek`, `azure_openai`, `openrouter`, `groq`, `together`, | |
| 311 | + | `fireworks`, `cerebras`, `anthropic_endpoint` or `openai_endpoint`), or | |
| 312 | + | `sentry`, `datadog`, `webhook`, `jira` or `linear`. `config` takes `repo`, | |
| 313 | + | `label`, `write_back`, `organization`, `site`, `email` and `keys`, and for | |
| 314 | + | an AI provider `base_url`, `auth_header` and `model`; each provider uses | |
| 315 | + | the ones above. For `datadog` | |
| 278 | 316 | and `webhook`, the response's `signing_secret` is the only time the secret | |
| 279 | 317 | is shown. | |
Binary or large file; its contents are not shown.
| 1 | + | import { ArrowUp, AtSign, Plus, Sparkles } from "lucide-react"; | |
| 2 | + | import type { ReactNode } from "react"; | |
| 3 | + | import { Link } from "react-router"; | |
| 4 | + | ||
| 5 | + | import { MarkGlyph } from "./logo"; | |
| 6 | + | import { Badge } from "./ui/badge"; | |
| 7 | + | import { Button } from "./ui/button"; | |
| 8 | + | import { Card } from "./ui/card"; | |
| 9 | + | import { Hint } from "./ui/hint"; | |
| 10 | + | import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; | |
| 11 | + | ||
| 12 | + | /** | |
| 13 | + | * The place to ask AI for something and hand the work to one of the | |
| 14 | + | * workspace's AI providers (Workspace settings → Integrations → Your AI | |
| 15 | + | * providers). Shown so it is clear where this will be, and not usable yet: | |
| 16 | + | * nothing here sends, and every control says so. | |
| 17 | + | */ | |
| 18 | + | export function AskComposer({ slug, children }: { slug: string; children?: ReactNode }) { | |
| 19 | + | return ( | |
| 20 | + | <section aria-label="Ask AI" className="space-y-3"> | |
| 21 | + | <Card aria-disabled="true" className="opacity-80"> | |
| 22 | + | <p className="flex items-center gap-2 px-4 pt-3 text-sm font-medium text-fg"> | |
| 23 | + | <Sparkles size={14} className="text-accent" aria-hidden="true" /> | |
| 24 | + | Ask AI | |
| 25 | + | <Badge className="px-1.5 py-0 font-normal">Coming soon</Badge> | |
| 26 | + | </p> | |
| 27 | + | <label htmlFor="ask-ai" className="sr-only"> | |
| 28 | + | Ask AI (coming soon) | |
| 29 | + | </label> | |
| 30 | + | <textarea | |
| 31 | + | id="ask-ai" | |
| 32 | + | disabled | |
| 33 | + | rows={2} | |
| 34 | + | placeholder="Ask anything, or describe work to hand to your AI provider." | |
| 35 | + | className="block w-full cursor-not-allowed resize-none bg-transparent px-4 pt-3.5 pb-2 text-sm text-fg placeholder:text-muted focus:outline-none" | |
| 36 | + | /> | |
| 37 | + | <div className="flex flex-wrap items-center gap-2 px-3 pb-3"> | |
| 38 | + | <Soon label="Add context"> | |
| 39 | + | <Plus size={15} /> | |
| 40 | + | </Soon> | |
| 41 | + | <Soon label="Mention"> | |
| 42 | + | <AtSign size={14} /> | |
| 43 | + | </Soon> | |
| 44 | + | <span className="ml-auto" /> | |
| 45 | + | <Soon label="Send"> | |
| 46 | + | <ArrowUp size={15} /> | |
| 47 | + | </Soon> | |
| 48 | + | </div> | |
| 49 | + | </Card> | |
| 50 | + | <p className="text-xs text-muted"> | |
| 51 | + | It will hand off to{" "} | |
| 52 | + | <Link to={`/${slug}/-/integrations/models`} className="text-accent hover:underline"> | |
| 53 | + | your AI providers | |
| 54 | + | </Link> | |
| 55 | + | . | |
| 56 | + | </p> | |
| 57 | + | {children} | |
| 58 | + | </section> | |
| 59 | + | ); | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | /** One of the composer's controls: there, disabled, with a hint saying why. */ | |
| 63 | + | function Soon({ label, children }: { label: string; children: ReactNode }) { | |
| 64 | + | return ( | |
| 65 | + | <Hint label="Coming soon" disabled> | |
| 66 | + | <Button type="button" variant="outline" size="icon-sm" disabled aria-label={`${label} (coming soon)`}> | |
| 67 | + | {children} | |
| 68 | + | </Button> | |
| 69 | + | </Hint> | |
| 70 | + | ); | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | /** | |
| 74 | + | * The top bar's Ask AI: the lavender pixel 1 beside its words, on a wash of | |
| 75 | + | * the accent. Not on yet: it opens the composer, which says so. | |
| 76 | + | */ | |
| 77 | + | export function AskButton({ slug }: { slug: string }) { | |
| 78 | + | return ( | |
| 79 | + | <Popover> | |
| 80 | + | <Hint label="Ask AI · coming soon"> | |
| 81 | + | <PopoverTrigger asChild> | |
| 82 | + | <Button variant="soft" size="bar" aria-label="Ask AI (coming soon)" className="gap-2 max-md:ring-0 lg:px-3"> | |
| 83 | + | <MarkGlyph className="h-3.5" /> | |
| 84 | + | <span className="max-lg:hidden">Ask AI</span> | |
| 85 | + | </Button> | |
| 86 | + | </PopoverTrigger> | |
| 87 | + | </Hint> | |
| 88 | + | <PopoverContent align="end" className="w-[26rem] p-3"> | |
| 89 | + | <AskComposer slug={slug} /> | |
| 90 | + | </PopoverContent> | |
| 91 | + | </Popover> | |
| 92 | + | ); | |
| 93 | + | } |
| 24 | 24 | import { type Provider, PROVIDERS } from "@g1t/contracts"; | |
| 25 | 25 | import { type ConnectorScope, type ConnectorView, connectorPath } from "@g1t/contracts/connectors"; | |
| 26 | 26 | ||
| 27 | + | import { AI_PROVIDERS } from "../lib/ai-providers"; | |
| 27 | 28 | import { CONTACT } from "../lib/legal"; | |
| 28 | 29 | import { | |
| 29 | 30 | type CategoryFilter, | |
| ⋯ | |||
| 70 | 71 | linear: "#8b8ff0", | |
| 71 | 72 | }; | |
| 72 | 73 | ||
| 73 | − | /** A provider's mark: its initial in its colour, or an icon for the generic ones. */ | |
| 74 | + | /** A provider's mark: its initials in its colour, or an icon for the generic ones. */ | |
| 74 | 75 | export function ProviderMark({ provider, size = 32 }: { provider: Provider; size?: number }) { | |
| 75 | − | const color = PROVIDER_COLORS[provider] ?? "#a1a1aa"; | |
| 76 | + | const entry = AI_PROVIDERS[provider]; | |
| 77 | + | const color = entry?.color ?? PROVIDER_COLORS[provider] ?? "#a1a1aa"; | |
| 76 | 78 | const content = | |
| 77 | 79 | provider === "webhook" ? ( | |
| 78 | 80 | <Webhook size={size * 0.5} /> | |
| 79 | 81 | ) : provider.endsWith("_endpoint") ? ( | |
| 80 | 82 | <Bot size={size * 0.5} /> | |
| 81 | 83 | ) : ( | |
| 82 | − | PROVIDERS[provider].label[0] | |
| 84 | + | entry?.mark || PROVIDERS[provider].label[0] | |
| 83 | 85 | ); | |
| 84 | 86 | return ( | |
| 85 | 87 | <span | |
| ⋯ | |||
| 89 | 91 | width: size, | |
| 90 | 92 | height: size, | |
| 91 | 93 | borderRadius: size * 0.28, | |
| 92 | − | fontSize: size * 0.42, | |
| 94 | + | fontSize: size * (String(content).length > 1 ? 0.34 : 0.42), | |
| 93 | 95 | background: `color-mix(in srgb, ${color} 16%, transparent)`, | |
| 94 | − | // Toward the text colour, so the letter reads on the tile in both themes. | |
| 96 | + | // Toward the text colour, so the letters read on the tile in both themes. | |
| 95 | 97 | color: `color-mix(in srgb, ${color} 60%, var(--color-fg))`, | |
| 96 | 98 | boxShadow: `inset 0 0 0 1px color-mix(in srgb, ${color} 30%, transparent)`, | |
| 97 | 99 | }} | |
| 1 | + | /** | |
| 2 | + | * A workspace's AI providers: the catalogue people choose from, and the | |
| 3 | + | * fields each one asks for when it is added. The marks and tiles are the | |
| 4 | + | * integrations' own (components/connectors). | |
| 5 | + | */ | |
| 6 | + | import type { ReactNode } from "react"; | |
| 7 | + | ||
| 8 | + | import type { Provider } from "@g1t/contracts"; | |
| 9 | + | ||
| 10 | + | import { AI_PROVIDER_GROUPS, AI_PROVIDERS, aiProvidersIn } from "../lib/ai-providers"; | |
| 11 | + | import { ProviderTiles } from "./connectors"; | |
| 12 | + | import { Field, Input } from "./ui"; | |
| 13 | + | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; | |
| 14 | + | ||
| 15 | + | /** The AI providers to choose from, grouped. */ | |
| 16 | + | export function ModelCatalog({ slug, adding }: { slug: string; adding: Provider | null }) { | |
| 17 | + | return ( | |
| 18 | + | <div className="mt-4 space-y-4"> | |
| 19 | + | {AI_PROVIDER_GROUPS.map(({ group, title }) => ( | |
| 20 | + | <div key={group}> | |
| 21 | + | <p className="mb-2 text-xs font-medium tracking-wide text-faint uppercase">{title}</p> | |
| 22 | + | <ProviderTiles | |
| 23 | + | providers={aiProvidersIn(group)} | |
| 24 | + | slug={slug} | |
| 25 | + | adding={adding} | |
| 26 | + | blurb={(provider) => AI_PROVIDERS[provider]?.blurb ?? ""} | |
| 27 | + | /> | |
| 28 | + | </div> | |
| 29 | + | ))} | |
| 30 | + | </div> | |
| 31 | + | ); | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | /** What adding an AI provider asks for. */ | |
| 35 | + | export function ModelProviderFields({ provider }: { provider: Provider }): ReactNode { | |
| 36 | + | const entry = AI_PROVIDERS[provider]; | |
| 37 | + | if (provider === "azure_openai") { | |
| 38 | + | return ( | |
| 39 | + | <> | |
| 40 | + | <Field label="Endpoint" hint="Your resource's endpoint, from the Azure portal: Keys and Endpoint."> | |
| 41 | + | <Input name="baseUrl" type="url" required placeholder="https://acme.openai.azure.com" /> | |
| 42 | + | </Field> | |
| 43 | + | <Field label="Key" hint="KEY 1 or KEY 2 of the resource. Sealed when saved: nobody sees it again."> | |
| 44 | + | <Input name="secret" type="password" required autoComplete="new-password" /> | |
| 45 | + | </Field> | |
| 46 | + | <Field label="Deployment" hint="The name of the model deployment. Add one provider for each deployment your team uses."> | |
| 47 | + | <Input name="model" required placeholder="gpt-5" /> | |
| 48 | + | </Field> | |
| 49 | + | </> | |
| 50 | + | ); | |
| 51 | + | } | |
| 52 | + | if (provider === "anthropic_endpoint" || provider === "openai_endpoint") { | |
| 53 | + | const anthropic = provider === "anthropic_endpoint"; | |
| 54 | + | return ( | |
| 55 | + | <> | |
| 56 | + | <Field | |
| 57 | + | label="Base URL" | |
| 58 | + | hint={anthropic ? "Without /v1, such as https://llm.acme.dev." : "Up to and including the version, such as https://llm.acme.dev/v1."} | |
| 59 | + | > | |
| 60 | + | <Input name="baseUrl" type="url" required placeholder={anthropic ? "https://llm.acme.dev" : "https://llm.acme.dev/v1"} /> | |
| 61 | + | </Field> | |
| 62 | + | <Field label="Key" hint="Optional, if the endpoint needs one. Sealed when saved: nobody sees it again."> | |
| 63 | + | <Input name="secret" type="password" autoComplete="new-password" /> | |
| 64 | + | </Field> | |
| 65 | + | <Field label="Send the key as"> | |
| 66 | + | <Select name="authHeader" defaultValue={anthropic ? "x-api-key" : "authorization"}> | |
| 67 | + | <SelectTrigger aria-label="Send the key as" className="font-mono text-[0.8125rem]"> | |
| 68 | + | <SelectValue /> | |
| 69 | + | </SelectTrigger> | |
| 70 | + | <SelectContent> | |
| 71 | + | <SelectItem value="x-api-key" description="Anthropic's header" className="font-mono text-[0.8125rem]"> | |
| 72 | + | x-api-key | |
| 73 | + | </SelectItem> | |
| 74 | + | <SelectItem value="authorization" description="OpenAI's header, and most others" className="font-mono text-[0.8125rem]"> | |
| 75 | + | Authorization: Bearer | |
| 76 | + | </SelectItem> | |
| 77 | + | </SelectContent> | |
| 78 | + | </Select> | |
| 79 | + | </Field> | |
| 80 | + | <Field label="Model" hint="Optional. The model your team uses on it."> | |
| 81 | + | <Input name="model" placeholder={anthropic ? "claude-sonnet-5-5" : "qwen3-coder"} /> | |
| 82 | + | </Field> | |
| 83 | + | </> | |
| 84 | + | ); | |
| 85 | + | } | |
| 86 | + | return ( | |
| 87 | + | <> | |
| 88 | + | <Field | |
| 89 | + | label="API key" | |
| 90 | + | hint={`${entry?.keyUrl ? `Get one at ${new URL(entry.keyUrl).host}. ` : ""}Sealed when saved: nobody sees it again.`} | |
| 91 | + | > | |
| 92 | + | <Input name="secret" type="password" required placeholder={entry?.keyPlaceholder} autoComplete="new-password" /> | |
| 93 | + | </Field> | |
| 94 | + | <Field label="Model" hint="Optional. The model your team uses on it. g1t lists the key's models when it checks the key."> | |
| 95 | + | <Input name="model" placeholder={entry?.model} /> | |
| 96 | + | </Field> | |
| 97 | + | </> | |
| 98 | + | ); | |
| 99 | + | } |
| 14 | 14 | import { PAUSE_FOR, type PauseFor, dndOn, pauseUntil, untilLabel } from "../../lib/presence"; | |
| 15 | 15 | import { applySoundSettings, preview, saveSoundSettings, useSoundSettings } from "../../lib/sounds"; | |
| 16 | 16 | ||
| 17 | − | const CUE: Partial<Record<Exclude<SoundCue, "call">, { label: string; detail: string }>> = { | |
| 17 | + | const CUE: Record<Exclude<SoundCue, "call">, { label: string; detail: string }> = { | |
| 18 | 18 | message: { label: "New message", detail: "In a conversation you have open, while you're looking elsewhere." }, | |
| 19 | 19 | direct: { label: "Direct message", detail: "Someone messages you directly." }, | |
| 20 | 20 | mention: { label: "Mention", detail: "You were @mentioned." }, | |
| 21 | 21 | sent: { label: "Sent", detail: "A soft tick as your message goes." }, | |
| 22 | 22 | }; | |
| 23 | 23 | ||
| 24 | − | /** The cues offered: every one that still plays (an agent's `agent_done` no longer does). */ | |
| 25 | − | const CUES = SOUND_CUES.filter((cue) => CUE[cue] != null); | |
| 24 | + | /** The cues offered, in the settings' order. */ | |
| 25 | + | const CUES = SOUND_CUES; | |
| 26 | 26 | ||
| 27 | 27 | const SETS: { set: SoundSet; label: string; detail: string }[] = [ | |
| 28 | 28 | { set: "soft", label: "Soft", detail: "Warm and low." }, | |
| ⋯ | |||
| 190 | 190 | ||
| 191 | 191 | <ul className={off ? "opacity-60" : ""} aria-label="Sounds"> | |
| 192 | 192 | {CUES.map((cue) => { | |
| 193 | − | const { label, detail } = CUE[cue]!; | |
| 193 | + | const { label, detail } = CUE[cue]; | |
| 194 | 194 | return ( | |
| 195 | 195 | <li key={cue} className="flex items-center gap-3 px-4 py-2.5"> | |
| 196 | 196 | <Hint label={`Play ${label.toLowerCase()}`}> | |
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…