feat(tui): show Go allowance when a session costs nothing (#58565)

This commit is contained in:
Adam
2026-10-24 03:43:38 -05:00
committed by GitHub
Unverified
parent 4cba1b7f5d
commit 9109bcf136
4 changed files with 61 additions and 2 deletions
@@ -43,9 +43,12 @@ export function PromptFooter(props: {
session.revert?.messageID,
)
const cost = props.context.data.session.cost(props.sessionID)
// A subscription such as OpenCode Go covers its requests from an allowance, so they cost nothing.
const allowance = props.context.data.session.allowance(props.sessionID)
return [
usage ? formatContextUsage(usage.tokens, usage.percent) : undefined,
cost > 0 ? money.format(cost) : undefined,
allowance > 0 ? `${money.format(allowance)} allowance` : undefined,
].filter((item): item is string => Boolean(item))
})
const live = createMemo(() => Boolean(subagents() || shells()))
@@ -12,13 +12,14 @@ export function SidebarContext(props: { context: Plugin.Context; sessionID: stri
const msg = createMemo(() => props.context.data.session.message.list(props.sessionID))
const session = createMemo(() => props.context.data.session.get(props.sessionID))
const cost = createMemo(() => props.context.data.session.cost(props.sessionID))
const allowance = createMemo(() => props.context.data.session.allowance(props.sessionID))
const state = createMemo(() =>
contextUsage(msg(), props.context.data.location.model.list(session()?.location), session()?.revert?.messageID),
)
return (
<Show when={state() || cost() > 0}>
<Show when={state() || cost() > 0 || allowance() > 0}>
<box>
<text fg={theme.text.base}>
<b>Context</b>
@@ -36,6 +37,9 @@ export function SidebarContext(props: { context: Plugin.Context; sessionID: stri
<Show when={cost() > 0}>
<text fg={theme.text.muted}>{money.format(cost())} spent</text>
</Show>
<Show when={allowance() > 0}>
<text fg={theme.text.muted}>{money.format(allowance())} allowance used</text>
</Show>
</box>
</Show>
)
@@ -29,6 +29,7 @@ test("prompt footer separates simultaneous subagent, shell, and usage status", a
status: (id: string) => (id === "child" ? "running" : "idle"),
get: () => ({ id: "session", location: { directory: "/workspace" } }),
cost: () => 1,
allowance: () => 0,
message: { list: () => [] },
},
shell: {
@@ -87,6 +88,7 @@ test("prompt footer can hide details", async () => {
status: () => "running",
get: () => ({ id: "session", location: { directory: "/workspace" } }),
cost: () => 1,
allowance: () => 0,
message: {
list: () => [
{
@@ -142,3 +144,36 @@ test("prompt footer can hide details", async () => {
app.renderer.destroy()
}
})
test("prompt footer shows the allowance a subscription covered", async () => {
const color = RGBA.fromInts(200, 200, 200)
const context = {
location: { directory: "/workspace" },
theme: { text: { base: color, muted: color } },
keymap: { shortcuts: () => [] },
data: {
session: {
family: () => ["session"],
status: () => "idle",
get: () => ({ id: "session", location: { directory: "/workspace" } }),
cost: () => 0,
allowance: () => 0.42,
message: { list: () => [] },
},
shell: { list: () => [] },
location: { model: { list: () => [] } },
},
} as unknown as Context
const app = await testRender(
() => <PromptFooter context={context} sessionID="session" mode="normal" showDetails={true} />,
{ width: 80, height: 1 },
)
try {
await app.renderOnce()
expect(app.captureCharFrame()).toContain("$0.42 allowance")
expect(app.captureCharFrame()).not.toContain("$0.00")
} finally {
app.renderer.destroy()
}
})
@@ -5,7 +5,7 @@ import { testRender } from "@opentui/solid"
import type { Context } from "@opencode/plugin/tui/context"
import { SidebarContext } from "../../src/feature-plugins/sidebar/context"
function context(options?: { cost?: number; tokens?: number }) {
function context(options?: { cost?: number; allowance?: number; tokens?: number }) {
const color = RGBA.fromInts(200, 200, 200)
return {
theme: { text: { base: color, muted: color } },
@@ -13,6 +13,7 @@ function context(options?: { cost?: number; tokens?: number }) {
session: {
get: () => ({ location: { directory: "/workspace" } }),
cost: () => options?.cost ?? 0,
allowance: () => options?.allowance ?? 0,
message: {
list: () =>
options?.tokens
@@ -68,3 +69,19 @@ test("sidebar shows available context usage", async () => {
app.renderer.destroy()
}
})
test("sidebar shows the allowance a subscription covered", async () => {
const app = await testRender(() => <SidebarContext context={context({ allowance: 0.42 })} sessionID="session" />, {
width: 42,
height: 8,
})
try {
await app.renderOnce()
expect(app.captureCharFrame()).toContain("Context")
expect(app.captureCharFrame()).toContain("$0.42 allowance used")
expect(app.captureCharFrame()).not.toContain("spent")
} finally {
app.renderer.destroy()
}
})