Skip to content

Compare changes

Choose two branches to see what one has that the other does not, then open a pull request for it.

Open a pull request

1 commit

17 files+114−330/17 viewed
+1−1
537537 "A workspace's integrations: its own model provider, 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."
538538 }
539539 Op::ConnectIntegration => {
540− "Connect a workspace to an outside system. provider is a model provider (anthropic, openai, gemini, xai, mistral, deepseek, azure_openai, openrouter, groq, together, fireworks, cerebras, anthropic_endpoint or openai_endpoint: your own key, billed by that provider, with g1t charging a flat orchestration fee per run; a workspace can connect several and route each kind of work with set_model_routes), or sentry, datadog, webhook, jira or linear. config holds the settings each needs; secret is the API key or token. For datadog and webhook, g1t makes the signing secret and returns it once. Owners only."
540+ "Connect a workspace to an outside system. provider is a model provider (anthropic, openai, gemini, xai, mistral, deepseek, azure_openai, openrouter, groq, together, fireworks, cerebras, anthropic_endpoint or openai_endpoint: your own key, billed by that provider, and free on g1t while it is being built out; a workspace can connect several and route each kind of work with set_model_routes), or sentry, datadog, webhook, jira or linear. config holds the settings each needs; secret is the API key or token. For datadog and webhook, g1t makes the signing secret and returns it once. Owners only."
541541 }
542542 Op::DisconnectIntegration => {
543543 "Remove an integration and its secrets. Agents already running on a model provider being removed stop reaching it. Owners only."
+4−2
114114
115115 ## Who may run workflows
116116
117−While g1t is in preview, workflows run in the workspaces g1t has opened
118−them to. Elsewhere a run is recorded with its jobs failed and the reason.
117+Workflows run in every workspace that uses g1t's agents: one with its own
118+[model provider](/guides/models/) connected, or one g1t has opened its
119+hosted models to. They are free while g1t is being built out. Elsewhere a
120+run is recorded with its jobs failed and the reason.
119121
120122 ## From the API
121123
+5−3
205205
206206 ## What it costs
207207
208−A workspace pays for the g1t agents that work on its repositories, from
209−credit an owner buys in advance: each run is charged what the model cost,
210−plus 20%. Acceptance checks are free. With no credit, agents do not start.
208+While g1t is being built out, its agents cost nothing: runs are recorded
209+with what they cost, and nothing is charged. Once pricing starts, a
210+workspace will pay for the g1t agents that work on its repositories from
211+credit an owner buys in advance: each run charged what the model cost, plus
212+20%, and acceptance checks free.
211213 The workspace's **Usage** page shows what its agents have cost, by day,
212214 kind of work, repository, model and pull request. See
213215 [usage and billing](/guides/usage-and-billing/).
+4−2
8383
8484 ## What it costs
8585
86−On your own providers, they bill you for the models, and g1t charges your
87−credit a flat **$0.10 per run** for the sandbox and orchestration. A
86+While g1t is being built out, g1t charges nothing for runs on your own
87+providers: they bill you for the models, and that is all. Once pricing
88+starts, g1t will charge your credit a flat **$0.10 per run** for the
89+sandbox and orchestration. A
8890 change, a review, a revision, a catch-up and a plan are each a run. The
8991 statement marks these runs "on your own model provider" and names the
9092 model and provider; the Usage page shows what they cost at the provider,
+7−0
33 description: What g1t agents cost, how a workspace pays for them, and what is free.
44 ---
55
6+> **Free while g1t is being built out.** For now, using g1t costs nothing:
7+> agents, reviews, checks and workflows. Bring your own model provider and
8+> its usage is billed by that provider, not by g1t. Runs are still recorded
9+> with what they cost, so the Usage page shows what you are using. This is
10+> for now, not forever: the pricing below is how g1t will charge once it
11+> starts, and we will say so well before anything is charged.
12+
613 Hosting repositories, issues, pull requests, review and your own agent cost
714 nothing on g1t. What costs money is g1t's own agents: each run uses a
815 model, and a workspace pays for the runs on its repositories from credit it
+10−4
6363 } | null;
6464 /** The workspace's agent credit, if billing is on and they may see it. */
6565 creditMicros: number | null;
66+ /** Whether g1t charges nothing for now, while it is being built out. */
67+ free?: boolean;
6668 /** What its agents have cost since the start of the month. */
6769 monthSpentMicros: number | null;
6870 };
190192 </span>
191193 <span className="mt-2 flex items-baseline justify-between">
192194 <span className="font-mono text-sm tabular-nums">${(spent / MICROS_PER_DOLLAR).toFixed(2)}</span>
193− {left != null && (
194− <span className={`text-xs ${left <= 0 ? "text-warn" : "text-faint"}`}>
195− ${(left / MICROS_PER_DOLLAR).toFixed(2)} left
196− </span>
195+ {shell.free ? (
196+ <span className="text-xs text-accent">Free for now</span>
197+ ) : (
198+ left != null && (
199+ <span className={`text-xs ${left <= 0 ? "text-warn" : "text-faint"}`}>
200+ ${(left / MICROS_PER_DOLLAR).toFixed(2)} left
201+ </span>
202+ )
197203 )}
198204 </span>
199205 <span className="mt-2 block h-1 overflow-hidden rounded-full bg-raised">
+3−1
109109 pulls: counts.value.pulls,
110110 }
111111 : null,
112+ // While g1t is being built out nothing is charged, so no credit is shown.
112113 creditMicros:
113− account?.ok && account.value.status.enabled ? account.value.balanceMicros : null,
114+ account?.ok && account.value.status.enabled && !account.value.status.free ? account.value.balanceMicros : null,
115+ free: account?.ok ? Boolean(account.value.status.free) : false,
114116 monthSpentMicros: usage?.ok ? usage.value.spentMicros : null,
115117 };
116118 }
+2−1
281281 action: "Add",
282282 },
283283 {
284− done: shell?.creditMicros == null || shell.creditMicros > 0,
284+ // Nothing to pay while g1t is being built out.
285+ done: Boolean(shell?.free) || shell?.creditMicros == null || shell.creditMicros > 0,
285286 title: "Add agent credit",
286287 about: "g1t's agents are paid for from the workspace's credit, at what the model costs plus 20%.",
287288 to: workspace ? `/${workspace}/-/billing` : null,
+11−0
7878 return (
7979 <div className="grid gap-10 lg:grid-cols-[1fr_20rem]">
8080 <div className="min-w-0">
81+ {status.free && (
82+ <div className="mb-8 rounded-xl border border-accent/30 bg-accent/5 p-5">
83+ <h2 className="font-medium">Free while g1t is being built out</h2>
84+ <p className="mt-1.5 max-w-2xl text-sm text-muted">
85+ While we build g1t out, using it costs nothing: agents, reviews, checks and workflows. Bring your own
86+ model provider under Integrations and its usage is billed by that provider, not by g1t. Runs are still
87+ recorded with what they cost, so Usage shows what you are using. This is for now, not forever: pricing
88+ will come later, and we will say so well before anything is charged.
89+ </p>
90+ </div>
91+ )}
8192 <h2 className="font-medium">Agent credit</h2>
8293 <p className="mt-1 max-w-2xl text-sm text-muted">
8394 g1t agents that work on this workspace's repositories are paid for from
+2−1
215215 as it uses, with each
216216 kind of work routed to one of them or to g1t's hosted models
217217 (`PUT /workspaces/{workspace}/model-routes`). Those providers bill the
218− workspace and g1t charges $0.10 a run. Sandboxes never hold a key.
218+ workspace; g1t charges nothing while it is being built out (later, $0.10
219+ a run). Sandboxes never hold a key.
219220 - **Alerts** (`sentry`, `datadog`, `webhook`): each problem opens one issue
220221 in a chosen repository, optionally with an agent put on it at once.
221222 Senders sign requests to `https://api.g1t.sh/hooks/{integration}`.
+5−0
2727 /// False while the payment provider is in its test mode, where cards
2828 /// are not real.
2929 pub live: bool,
30+ /// True while g1t is being built out: runs are recorded, with what
31+ /// they cost, but nothing is charged and no credit is needed. Not a
32+ /// promise that it stays free.
33+ #[serde(default)]
34+ pub free: bool,
3035 }
3136
3237 /// A workspace's standing.
+5−0
1414 enabled: boolean;
1515 /** False while the card processor is in its test mode, where cards are not real. */
1616 live: boolean;
17+ /**
18+ * True while g1t is being built out: runs are recorded with what they
19+ * cost, but nothing is charged and no credit is needed. Not forever.
20+ */
21+ free?: boolean;
1722 };
1823
1924 /** A workspace's standing. */
+17−9
66 use g1t_actions::workflow::{self, Trigger, Workflow};
77 use g1t_contracts::actions::{DispatchArgs, WorkflowRun};
88 use g1t_contracts::events::Event;
9−use g1t_contracts::identity::{AGENT_ID, AGENT_NAME, UsernameArgs, UsernamesArgs};
9+use g1t_contracts::identity::{AGENT_ID, AGENT_NAME, UsernamesArgs};
1010 use g1t_contracts::repos::{Commit, CompareArgs, Comparison, LogArgs, Repo, RepoPath};
1111 use g1t_contracts::work::{IssueDetail, PullDetail, ViewArgs};
12−use g1t_contracts::{FailureCode, Outcome, User, Viewer, new_id};
12+use g1t_contracts::{FailureCode, Outcome, User, new_id};
1313 use g1t_kit::now_ms;
1414 use serde_json::{Map, Value, json};
1515 use worker::Result;
5151 Ok(names.get(id).cloned())
5252 }
5353
54− /// Whether someone belongs to the workspace, so their pull requests'
55− /// runs get the secrets.
56− async fn insider(&self, author: &User, namespace: &str) -> Result<bool> {
57− if author.id == AGENT_ID || author.is_member(&namespace.to_lowercase()) {
54+ /// Whether a pull request's author belongs to the workspace, so its
55+ /// runs get the secrets and a token. On a private repository only
56+ /// members can open one at all.
57+ async fn insider(&self, author: &User, repo: &Repo, ws: &User) -> Result<bool> {
58+ let slug = repo.namespace.to_lowercase();
59+ if repo.is_private || author.id == AGENT_ID || author.is_member(&slug) {
5860 return Ok(true);
5961 }
60− let found: Viewer = g1t_kit::call(&self.identity, "user_by_username", &UsernameArgs { username: author.username.clone() }).await?;
61− Ok(found.is_some_and(|user| user.is_member(&namespace.to_lowercase())))
62+ // Stored authors carry no memberships: ask the workspace.
63+ let members: Outcome<Vec<g1t_contracts::identity::Member>> = g1t_kit::call(
64+ &self.identity,
65+ "list_members",
66+ &g1t_contracts::identity::ListMembersArgs { slug, viewer: Some(ws.clone()) },
67+ )
68+ .await?;
69+ Ok(members.into_result().unwrap_or_default().iter().any(|m| m.username.eq_ignore_ascii_case(&author.username)))
6270 }
6371
6472 async fn commits(&self, repo: &Repo, actor: &User, after: &str, before: Option<&str>) -> Result<Vec<Commit>> {
195203 "user": review.map(|r| payload::user(&r.author.username)),
196204 });
197205 }
198− let trusted = self.insider(&pull.author, &repo.namespace).await?;
206+ let trusted = self.insider(&pull.author, repo, ws).await?;
199207 let head_ref = payload::head_ref(pull);
200208 if event_name == "pull_request_target" {
201209 // In the base's context: its workflows, its head.
+15−1
124124 margin_percent: u32,
125125 /// Charged for a run on the workspace's own model provider.
126126 orchestration_fee_micros: i64,
127+ /// While g1t is being built out, nothing is charged (`FREE_WHILE_BUILDING`).
128+ free: bool,
127129 }
128130
129131 impl Billing {
131133 Status {
132134 enabled: self.stripe.is_some(),
133135 live: self.stripe.as_ref().is_some_and(Stripe::live),
136+ free: self.free,
134137 }
135138 }
136139
443446
444447 /// A refusal if the workspace has no credit to start an agent with.
445448 async fn out_of_credit<T>(&self, workspace: &str) -> Result<Option<Outcome<T>>> {
449+ // While g1t is being built out, no one needs credit.
450+ if self.free {
451+ return Ok(None);
452+ }
446453 let balance = self
447454 .row(workspace)
448455 .await?
531538 }
532539 // On the workspace's own provider, the model was paid for there:
533540 // g1t charges its fee, and keeps the provider's cost to show.
534− let charge = if run.own_provider() {
541+ let charge = if self.free {
542+ // Recorded, with what it cost, but not charged.
543+ 0
544+ } else if run.own_provider() {
535545 self.orchestration_fee_micros
536546 } else {
537547 charge_micros(a.cost_usd, self.margin_percent)
545555 if run.own_provider() {
546556 description.push_str(", on your own model provider");
547557 }
558+ if self.free {
559+ description.push_str(" (free while g1t is being built out)");
560+ }
548561 self.enter(
549562 &run.workspace,
550563 EntryKind::Usage,
592605 .ok()
593606 .and_then(|fee| fee.to_string().parse().ok())
594607 .unwrap_or(100_000),
608+ free: env.var("FREE_WHILE_BUILDING").is_ok_and(|v| v.to_string() == "true"),
595609 };
596610 match method.as_str() {
597611 "status" => reply(&billing.status()),
+5−1
2525 // What a run on a workspace's own model provider is charged, in
2626 // millionths of a dollar: the sandbox and the orchestration around
2727 // it, with the model paid for at the provider. $0.10.
28− "ORCHESTRATION_FEE_MICROS": "100000"
28+ "ORCHESTRATION_FEE_MICROS": "100000",
29+ // While g1t is being built out, workspaces pay nothing: runs are
30+ // recorded with what they cost, and nothing is charged. Set to
31+ // "false" when pricing starts.
32+ "FREE_WHILE_BUILDING": "true"
2933 },
3034 // Secret: STRIPE_SECRET_KEY. Without it nothing is charged and the
3135 // runner decides who may start agents some other way.
+4−3
555555 repo: RepoPath;
556556 timeoutMinutes: number;
557557 }): Promise<Result<true>> {
558− const status = await billingClient(this.env.BILLING).status();
559− if (!(this.previewListed(args.repo.namespace) || (status.enabled && status.live))) {
558+ // The same workspaces that may use g1t's sandboxes for agents.
559+ if (!(await this.workspaceAllowed(args.repo.namespace))) {
560560 return {
561561 ok: false,
562562 error: {
563563 code: "forbidden",
564− message: "Workflows run on g1t's hosted runners, which are not open to this workspace yet.",
564+ message:
565+ "Workflows run on g1t's runners for workspaces that use g1t's agents: connect your own model provider under Integrations, free while g1t is being built out.",
565566 },
566567 };
567568 }
+14−4
627627 .map(|s| {
628628 let run = s.target_url.as_deref().and_then(|url| url.rsplit('/').next()).unwrap_or_default();
629629 format!(
630− "- {} ({}): run `{run}`",
630+ "- {} ({}): run `{run}`, {}",
631631 s.context,
632− s.description.as_deref().unwrap_or("failed")
632+ s.description.as_deref().unwrap_or("failed"),
633+ s.target_url.as_deref().unwrap_or_default()
633634 )
634635 })
635636 .collect();
637+ // Named outright: the agent cannot guess it from its fork.
638+ let repo = g1t_kit::call::<_, Option<RepoPath>>(
639+ &self.repos,
640+ "path_by_id",
641+ &g1t_contracts::repos::PathByIdArgs { id: pull.repo_id.clone() },
642+ )
643+ .await?
644+ .map(|path| format!("{}/{}", path.namespace, path.name))
645+ .unwrap_or_default();
636646 Ok(format!(
637− "These GitHub Actions workflows failed on your latest commit:\n\n{}\n\n\
638− Read why with the `get_workflow_run` tool (this repository, and the run's id), \
647+ "These GitHub Actions workflows failed on your latest commit to {repo}:\n\n{}\n\n\
648+ Read why with the `get_workflow_run` tool (repo `{repo}` and the run's id), \
639649 then `get_job_logs` for the job that failed. Fix the cause in the code, not the workflow, \
640650 unless the workflow itself is wrong.",
641651 failed.join("\n")