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.

Commit

A session's page and Sessions say what it cost at list price

With sessions reporting their usage, the people looking at one want to know what it cost without opening a bill: its page and the sessions list say "≈ $4.12 at list price", quietly beside when it ran, with "some usage priced as unknown" when part of it was for a model g1t has no price for. WorkSession carries costUsd and costPartial, as the work service sends them. Part of #157

G1t-Session:
ses_01m573jm0ze1q9tram0b4dcva1
syntaqxcommitted Parent4e081b7Browse files
5 files+56−30/5 viewed
+14−1
2424 import { ItemIcon, PullIcon } from "./work-icons";
2525 import { cn } from "../lib/cn";
2626 import { HARNESS_NAMES, SESSION_STATES } from "../lib/coordination";
27−import { KIND_NAMES, type SessionsFilters, narrowed, repoName, runsIn, sessionHref, sessionsQuery, workspaceSessionTitle } from "../lib/sessions";
27+import { KIND_NAMES, type SessionsFilters, narrowed, repoName, runsIn, sessionCost, sessionHref, sessionsQuery, workspaceSessionTitle } from "../lib/sessions";
2828 import { itemPath, sessionsPath } from "../lib/work-items";
2929
3030 const BRANDS: Partial<Record<Harness, { path: string; title: string }>> = {
121121 );
122122 }
123123
124+/** What a harness session cost, quietly, when it reported usage. */
125+function Cost({ session }: { session: WorkspaceSession }) {
126+ const cost = sessionCost(session.session);
127+ if (!cost) return null;
128+ return (
129+ <Hint label="Estimated from the tokens its transcript recorded, at Anthropic's list price. Its subagents count on their own sessions.">
130+ <span className="text-xs text-faint tabular-nums">{cost}</span>
131+ </Hint>
132+ );
133+}
134+
124135 /**
125136 * A live session: its state, what it is called, who runs it and in what,
126137 * where, the work item and pull request it is on, its subagents and when
153164 <Where session={session} workspace={workspace} />
154165 </div>
155166 {session.step && <p className="line-clamp-1 text-xs text-muted">{session.step}</p>}
167+ <Cost session={session} />
156168 {(session.item != null || session.pull != null || session.subagents > 0) && (
157169 <div className="flex flex-wrap items-center gap-2">
158170 {session.item != null && <ItemChip workspace={workspace} number={session.item} title={session.itemTitle} />}
194206 </span>
195207 <Where session={session} workspace={workspace} />
196208 {session.subagents > 0 && <span>{session.subagents} subagents</span>}
209+ <Cost session={session} />
197210 </div>
198211 </div>
199212 <span className="shrink-0 text-xs text-faint">
+12−1
33
44 import type { WorkSession, WorkspaceSession } from "@g1t/contracts";
55
6−import { narrowed, peopleIn, repoName, runsIn, sessionHref, sessionsFilters, sessionsQuery, splitLive, workspaceSessionTitle } from "./sessions.ts";
6+import { narrowed, peopleIn, repoName, runsIn, sessionCost, sessionHref, sessionsFilters, sessionsQuery, splitLive, workspaceSessionTitle } from "./sessions.ts";
77
88 function listed(id: string, more: Partial<WorkspaceSession> = {}): WorkspaceSession {
99 return {
8686 assert.equal(repoName("acme/web", "Acme"), "web");
8787 assert.equal(repoName("other/web", "acme"), "other/web");
8888 });
89+
90+test("a session's cost reads as an estimate at list price, and says when some usage is priced as unknown", () => {
91+ assert.equal(sessionCost({ costUsd: 4.1234 }), "≈ $4.12 at list price");
92+ assert.equal(sessionCost({ costUsd: 1234.5, costPartial: true }), "≈ $1,234.50 at list price, some usage priced as unknown");
93+ assert.equal(sessionCost({ costUsd: 0.004 }), "≈ under $0.01 at list price");
94+ assert.equal(sessionCost({ costUsd: 0 }), "≈ $0.00 at list price");
95+ assert.equal(sessionCost({ costUsd: null, costPartial: true }), "Usage priced as unknown");
96+ assert.equal(sessionCost({ costUsd: null }), null);
97+ assert.equal(sessionCost({}), null);
98+ assert.equal(sessionCost(null), null);
99+});
+14−1
33 * and where it opens, the filters Sessions keeps in its address, and live
44 * ones apart from the rest. Pure, so it is tested under Node.
55 */
6−import type { Harness, SessionKind, WorkspaceSession } from "@g1t/contracts";
6+import type { Harness, SessionKind, WorkSession, WorkspaceSession } from "@g1t/contracts";
77
88 import { HARNESS_NAMES, sessionTitle } from "./coordination.ts";
99 import { sessionPath } from "./work-items.ts";
106106 return [...counts].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([name]) => name);
107107 }
108108
109+/**
110+ * What a session cost, in a few words: "≈ $4.12 at list price", with
111+ * "some usage priced as unknown" when some of it was for models g1t has no
112+ * price for. Null when it reported no usage.
113+ */
114+export function sessionCost(session: Pick<WorkSession, "costUsd" | "costPartial"> | null | undefined): string | null {
115+ if (!session) return null;
116+ const cost = session.costUsd;
117+ if (cost == null || !Number.isFinite(cost)) return session.costPartial ? "Usage priced as unknown" : null;
118+ const amount = cost > 0 && cost < 0.01 ? "under $0.01" : `$${cost.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
119+ return `≈ ${amount} at list price${session.costPartial ? ", some usage priced as unknown" : ""}`;
120+}
121+
109122 /** `acme/web` as the workspace `acme` names it: `web`. */
110123 export function repoName(path: string, workspace: string): string {
111124 const [owner, name] = path.split("/");
+8−0
3535 viaLabel,
3636 } from "../../../../lib/coordination";
3737 import { notFound } from "../../../../lib/not-found.server";
38+import { sessionCost } from "../../../../lib/sessions";
3839 import { repoPages } from "../../../../lib/repo-live";
3940 import { agentSessions, work } from "../../../../lib/services.server";
4041 import { assertSameOrigin, getViewer, requireUser } from "../../../../lib/session.server";
341342 <SessionStateBadge state={session.state} />
342343 <TimeAgo at={session.startedAt} />
343344 <span suppressHydrationWarning>· {duration(session, now)}</span>
345+ {sessionCost(session) && <span className="tabular-nums">· {sessionCost(session)}</span>}
344346 </span>
345347 </div>
346348 <CollapsibleContent className="mt-3 space-y-4 pl-6">
390392 // Its own subagents: those whose parent is in this tree (children lists every descendant).
391393 const tree = sessionTree([session, ...children]).find((node) => node.session.id === session.id)?.children ?? [];
392394 const managed = session.harness === MANAGED || root?.harness === MANAGED;
395+ const cost = sessionCost(session);
393396
394397 // The skills, summed by name across the tree.
395398 const skillUses = new Map<string, number>();
479482 · {session.state === "ended" ? "ended" : "last heard from"} <TimeAgo at={session.endedAt ?? session.lastSeenAt} />
480483 {session.endReason && ` (${endReasonText(session.endReason)})`}
481484 </span>
485+ {cost && (
486+ <Hint label="Estimated from the tokens its transcript recorded, at Anthropic's list price. Its subagents count on their own sessions.">
487+ <span className="tabular-nums">· {cost}</span>
488+ </Hint>
489+ )}
482490 {session.branch && (
483491 <Link to={`${base}/tree/${session.branch}`} className="inline-flex items-center gap-1 font-mono text-xs hover:text-fg">
484492 · <GitBranch size={12} />
+8−0
14491449 titleSource?: TitleSource | null;
14501450 /** When its title was last settled. */
14511451 titleSettledAt?: string | null;
1452+ /**
1453+ * What it cost, estimated at Anthropic's list price from the token usage
1454+ * its harness reported, in US dollars; none until it reports usage for a
1455+ * model with a known price. Only the workspace's members see it.
1456+ */
1457+ costUsd?: number | null;
1458+ /** Some of its usage was for models priced as unknown, which `costUsd` leaves out. */
1459+ costPartial?: boolean;
14521460 };
14531461
14541462 /**

Comments

Sign in to comment.