Commit

New workspaces are told to connect a model, before anything fails

Walking g1t as a second account, the way a new user would: g1t's hosted models are open only to selected workspaces, so a new workspace's agents and workflow runners need a model provider of its own, and nothing said so until something failed. - Mission control's "Get started" said "Add agent credit" and ticked it off, since nothing is charged. A workspace that cannot reach a model now gets "Connect a model" there instead, linked to Integrations. - The Actions page says up front that workflows run once the workspace connects a model provider, with a link; a job that was refused for it links "Integrations" in its reason. - An issue showed no way to put g1t's agent on it at all, while its empty state said "Put g1t agents on it". It now shows where the agent would be and how to make it appear. - Integrations, Billing and the usage guide said g1t charges $0.10 a run on your own provider. Not while it is being built out; they now say so, as the models guide already did. - The quickstart gains a "Connect a model" step before putting an agent on an issue, and says git takes any username with a token.

syntaqxcommitted Parent244926aBrowse files
8 files+116−260/8 viewed
+5−3
3232 it, plus 20%. A small change costs a few cents.
3333
3434 Work a workspace routes to [its own model providers](/guides/models/) is
35−paid for at those providers instead, and each such run here is a flat $0.10
36−for the sandbox and orchestration.
35+paid for at those providers instead, and each such run here will be a flat
36+$0.10 for the sandbox and orchestration once pricing starts (nothing while
37+g1t is being built out).
3738
3839 The charge goes to the workspace that owns the repository, whoever
3940 assigned the issue. That is why only members of a workspace can put g1t
121122 - **g1t's agents, for any workspace with its own model provider:** connect
122123 an Anthropic key or endpoint under [Integrations](/guides/models/) and the
123124 workspace's agents, acceptance checks and merge queue work at once. Your
124− provider bills you for the models; g1t charges $0.10 a run.
125+ provider bills you for the models; g1t charges nothing for now, and $0.10
126+ a run once pricing starts.
125127 - **g1t's hosted models, for selected workspaces:** while payments are in
126128 test mode, g1t's own models are open only to workspaces it has opened
127129 them to. When payments go live, every workspace can use them, paid from
+14−5
3434
3535 3. **Push a repository**
3636
37− Pushing to a repository that does not exist creates it. When git asks for
38− a password, give it your token.
37+ Pushing to a repository that does not exist creates it. When git asks,
38+ give it your username and, as the password, your token.
3939
4040 ```sh
4141 git remote add g1t https://g1t.sh/<workspace>/my-project.git
5757 Add **acceptance checks**, commands that must pass, to hold the work to
5858 them.
5959
60−5. **Put an agent on it**
60+5. **Connect a model**
6161
62+ g1t's agents, and the runners your workflows use, need a model to work
63+ with. In your workspace's **Settings → Integrations**, connect your
64+ Anthropic or OpenAI key, or any compatible endpoint. Your provider bills
65+ you for the model; g1t itself costs nothing while it is being built out.
66+ Mission control and the issue page link there until it is done. See
67+ [model providers](/guides/models/).
68+
69+6. **Put an agent on it**
70+
6271 <Tabs>
6372 <TabItem label="g1t's agent">
64− On the issue, choose **Assign** and pick **g1t-agent**. It opens a pull
73+ On the issue, choose **Assign to g1t agent**. It opens a pull
6574 request, makes the change in a sandbox, and sees it through: checks and
6675 workflows, a review by a second agent, revisions until both pass.
6776 </TabItem>
7988 </TabItem>
8089 </Tabs>
8190
82−6. **Merge**
91+7. **Merge**
8392
8493 Open the pull request, read **Changes** and **Session**, and choose
8594 **Merge**, or turn on auto-merge in the repository's settings and let g1t
+25−10
11 import { ArrowRight, Hand, Plus, Settings, Users } from "lucide-react";
2+import { env } from "cloudflare:workers";
23 import { type ReactNode, useEffect } from "react";
34 import { Link, data, useRevalidator, useRouteLoaderData } from "react-router";
45
4344 const times: Record<string, number> = {};
4445 const timed = <T,>(name: string, promise: Promise<T>) =>
4546 promise.then((value) => ((times[name] = Date.now() - started), value));
46− const [repos, pulls, assigned] = await Promise.all([
47+ // The workspace the sidebar shows, as the shell picks it.
48+ const workspace = viewer?.workspaces?.[0]?.slug ?? null;
49+ const [repos, pulls, assigned, models] = await Promise.all([
4750 timed("repos", reposApi.list(viewer, { memberOnly: Boolean(viewer) })),
4851 timed("pulls", work.listActivePulls(viewer)),
4952 timed("assigned", work.listAssignedIssues(viewer)),
53+ // Whether its agents can reach a model: g1t's, or one it connected.
54+ workspace ? timed("models", env.RUNNER.modelAccess(workspace).catch(() => null)) : Promise.resolve(null),
5055 ]);
5156 // Each issue and pull request is shown under its repository. Most are in
5257 // the viewer's own, already listed; the rest are looked up once each, all
6469 return data({
6570 viewer,
6671 repos,
72+ canRunAgents: models == null || models.hosted || models.own != null,
6773 assigned: assigned.flatMap((issue) => {
6874 const repo = known.get(issue.repoId);
6975 return repo ? [{ issue, repo }] : [];
241247 }
242248
243249 export default function Home({ loaderData }: Route.ComponentProps) {
244− const { viewer, repos, active, assigned } = loaderData;
250+ const { viewer, repos, active, assigned, canRunAgents } = loaderData;
245251
246252 const needsYou = active.filter((item) => item.lifecycle?.stage === "needs_you");
247253 const ready = active.filter((item) => item.lifecycle?.stage === "ready");
279285 about: "Create one, or import one from GitHub by its address. Push to it with git as usual.",
280286 to: workspace ? `/new?workspace=${workspace}` : "/new",
281287 action: "Add",
282− },
283− {
284− // Nothing to pay while g1t is being built out.
285− done: Boolean(shell?.free) || shell?.creditMicros == null || shell.creditMicros > 0,
286− title: "Add agent credit",
287− about: "g1t's agents are paid for from the workspace's credit, at what the model costs plus 20%.",
288− to: workspace ? `/${workspace}/-/billing` : null,
289− action: "Add credit",
290288 },
289+ canRunAgents
290+ ? {
291+ // Nothing to pay while g1t is being built out.
292+ done: Boolean(shell?.free) || shell?.creditMicros == null || shell.creditMicros > 0,
293+ title: "Add agent credit",
294+ about: "g1t's agents are paid for from the workspace's credit, at what the model costs plus 20%.",
295+ to: workspace ? `/${workspace}/-/billing` : null,
296+ action: "Add credit",
297+ }
298+ : {
299+ done: false,
300+ title: "Connect a model",
301+ about:
302+ "Agents need a model to think with. Connect your Anthropic or OpenAI key, or any compatible endpoint; g1t itself costs nothing while it is being built out.",
303+ to: workspace ? `/${workspace}/-/integrations` : null,
304+ action: "Connect",
305+ },
291306 {
292307 done: handedOff,
293308 title: "Hand off an outcome",
+21−1
6161 );
6262 }
6363
64+/** Why a job did not run as asked, with "Integrations" linked where it says so. */
65+function Reason({ text, workspace }: { text: string; workspace: string }) {
66+ const [before, after] = text.split("Integrations");
67+ return (
68+ <p className="rounded-lg bg-raised px-3 py-2 text-sm text-muted">
69+ {after === undefined ? (
70+ text
71+ ) : (
72+ <>
73+ {before}
74+ <Link to={`/${workspace}/-/integrations`} className="text-accent hover:underline">
75+ Integrations
76+ </Link>
77+ {after}
78+ </>
79+ )}
80+ </p>
81+ );
82+}
83+
6484 function JobView({ job, base }: { job: Job; base: string }) {
6585 const live = job.status !== "completed";
6686 const log = useJobLog(`${base}/actions/jobs/${job.id}/log`, live);
7696 {job.startedAt && ` · ${duration(job.startedAt, job.finishedAt)}`}
7797 </span>
7898 </div>
79− {job.reason && <p className="rounded-lg bg-raised px-3 py-2 text-sm text-muted">{job.reason}</p>}
99+ {job.reason && <Reason text={job.reason} workspace={base.split("/")[1]} />}
80100 {job.annotations.length > 0 && (
81101 <ul className="space-y-2 rounded-xl border border-line bg-surface p-4 text-sm">
82102 {job.annotations.map((note, index) => (
+22−3
11 import { AlertTriangle, FileCode2, GitBranch, Play, PlayCircle } from "lucide-react";
2+import { env } from "cloudflare:workers";
23 import { useEffect, useState } from "react";
34 import { Form, Link, useNavigation, useRevalidator, useSearchParams } from "react-router";
45
1819 const viewer = getViewer(context);
1920 const repo = { namespace: params.owner, name: params.repo };
2021 const selected = new URL(request.url).searchParams.get("workflow") ?? undefined;
21− const [workflows, runs] = await Promise.all([
22+ const member = roleIn(viewer, params.owner) != null;
23+ const [workflows, runs, models] = await Promise.all([
2224 actions.workflows(repo, viewer),
2325 actions.runs(repo, viewer, { workflow: selected, limit: 50 }),
26+ // Jobs run on g1t's machines for workspaces whose agents can reach a
27+ // model; members are told before a run fails for it.
28+ member ? env.RUNNER.modelAccess(params.owner).catch(() => null) : Promise.resolve(null),
2429 ]);
2530 return {
2631 workflows: unwrap(workflows),
2732 runs: runs.ok ? runs.value : [],
28− member: roleIn(viewer, params.owner) != null,
33+ member,
34+ runnable: models == null || models.hosted || models.own != null,
2935 selected: selected ?? null,
3036 };
3137 }
246252 - run: npm test`;
247253
248254 export default function Actions({ loaderData, actionData, params }: Route.ComponentProps) {
249− const { workflows, runs, member, selected } = loaderData;
255+ const { workflows, runs, member, runnable, selected } = loaderData;
250256 const [search] = useSearchParams();
251257 const base = `/${params.owner}/${params.repo}`;
252258 const workflow = workflows.find((w) => w.id === selected || w.path.endsWith(`/${selected}`)) ?? null;
270276 </a>
271277 </header>
272278
279+ {member && !runnable && (
280+ <div className="mb-6 flex flex-wrap items-center justify-between gap-3 rounded-xl bg-surface px-4 py-3 text-sm ring-1 ring-line">
281+ <span className="flex items-center gap-2 text-muted">
282+ <AlertTriangle size={15} className="shrink-0 text-warn" />
283+ Workflows run on g1t's runners once this workspace connects a model provider for its agents. Free while g1t is
284+ being built out.
285+ </span>
286+ <Link to={`/${params.owner}/-/integrations`} className="text-accent hover:underline">
287+ Connect a model
288+ </Link>
289+ </div>
290+ )}
291+
273292 {workflows.length === 0 ? (
274293 <div className="grid gap-6 lg:grid-cols-2">
275294 <EmptyState title="No workflows yet">
+20−0
478478 </Form>
479479 )}
480480
481+ {open && !agentsEnabled && canManage && !assigned && !issue.queued && (
482+ // Where g1t's agent would be, and what makes it appear.
483+ <div className="mt-3 rounded-lg border border-dashed border-line p-3 text-sm">
484+ <p className="flex items-center gap-1.5 font-medium">
485+ <Sparkles size={14} className="text-accent" />
486+ g1t agent
487+ </p>
488+ <p className="mt-1 text-xs text-muted">
489+ Connect a model provider and g1t's agent can take this issue: it opens a pull request and sees it
490+ through checks, review and fixes. g1t costs nothing while it is being built out.
491+ </p>
492+ <Link
493+ to={`/${params.owner}/-/integrations`}
494+ className="mt-2 inline-block text-xs text-accent hover:underline"
495+ >
496+ Connect a model
497+ </Link>
498+ </div>
499+ )}
500+
481501 {viewer && canManage && open && (
482502 <div className="mt-3 space-y-2">
483503 {!issue.assignees.includes(viewer.username) && (
+2−1
214214 Integrations
215215 </Link>
216216 , the provider bills you for the model and each run here is a flat{" "}
217− {dollars(account.orchestrationFeeMicros)}.
217+ {dollars(account.orchestrationFeeMicros)}
218+ {status.free && " once pricing starts; nothing while g1t is being built out"}.
218219 </li>
219220 <li>
220221 Only members of <span className="font-mono text-fg">{slug}</span> can
+7−3
6868 hostedOpen: access.hosted,
6969 routes: routes.ok ? routes.value : [],
7070 feeMicros: account.ok ? account.value.orchestrationFeeMicros : 100_000,
71+ free: account.ok ? Boolean(account.value.status.free) : false,
7172 marginPercent: account.ok ? account.value.marginPercent : 20,
7273 };
7374 }
174175 }
175176
176177 export default function WorkspaceIntegrations({ loaderData, actionData }: Route.ComponentProps) {
177− const { slug, role, connections, deliveries, repos: repoNames, adding, feeMicros, marginPercent, hostedOpen, routes } =
178+ const { slug, role, connections, deliveries, repos: repoNames, adding, feeMicros, free, marginPercent, hostedOpen, routes } =
178179 loaderData;
179180 const owner = role === "owner";
180181 const busy = useNavigation().state === "submitting";
224225 />
225226 )}
226227 <p className="mt-3 text-xs text-faint">
227− On your own providers, their bills are yours and g1t charges {dollars(feeMicros)} a run for the
228− sandbox and orchestration. Keys go only from g1t's model proxy to the provider: the agent's
228+ On your own providers, their bills are yours.{" "}
229+ {free
230+ ? `g1t charges nothing while it is being built out; once pricing starts, ${dollars(feeMicros)} a run for the sandbox and orchestration.`
231+ : `g1t charges ${dollars(feeMicros)} a run for the sandbox and orchestration.`}{" "}
232+ Keys go only from g1t's model proxy to the provider: the agent's
229233 sandbox holds a token that dies with the run.
230234 </p>
231235 {owner && <ModelCatalog slug={slug} adding={adding} />}