Skip to content

Commit a1f59f8

Browse files
committed
feat(tui): expose subagent cost breakdown in sidebar
1 parent e74ec3a commit a1f59f8

5 files changed

Lines changed: 48 additions & 2 deletions

File tree

FORK.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ git push -u origin <your-custom-branch>
4747

4848
### 2026-07-17
4949

50+
- **Exposed Sub-agent Cost Calculations in TUI Sidebar:** Added recursive cost-summing for sub-agent threads. Updates the TUI Context sidebar to display a detailed breakdown of Main Agent cost, Sub-agent total cost, and combined Total Cost under the active session ID.
5051
- **Ported PR #23262 (File Cycling in Permission Prompt):** Ported the file cycling feature from upstream PR #23262 to [packages/tui/src/routes/session/permission.tsx](file:///Users/mmcdonnell/code/opencode/packages/tui/src/routes/session/permission.tsx). This enables cycling through multiple files inside the TUI permission dialog using `[` and `]` keys.
5152
- **Added [FORK.md](file:///Users/mmcdonnell/code/opencode/FORK.md):** Outlines branching, syncing, and custom workspace strategy.
5253
- **Added [Makefile](file:///Users/mmcdonnell/code/opencode/Makefile):** Standard checkmake-compliant targets for building, cleaning, testing, and syncing with automatic Bun dependency management.

packages/opencode/test/fixture/tui-plugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
314314
},
315315
session: {
316316
count: opts.state?.session?.count ?? (() => 0),
317+
list: opts.state?.session?.list ?? (() => []),
317318
get: opts.state?.session?.get ?? (() => undefined),
318319
diff: opts.state?.session?.diff ?? (() => []),
319320
todo: opts.state?.session?.todo ?? (() => []),

packages/plugin/src/tui.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ export type TuiState = {
385385
readonly vcs: { branch?: string; default_branch?: string } | undefined
386386
session: {
387387
count: () => number
388+
list: () => ReadonlyArray<Session>
388389
get: (sessionID: string) => Session | undefined
389390
diff: (sessionID: string) => ReadonlyArray<TuiSidebarFileItem>
390391
todo: (sessionID: string) => ReadonlyArray<TuiSidebarTodoItem>

packages/tui/src/feature-plugins/sidebar/context.tsx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { AssistantMessage } from "@opencode-ai/sdk/v2"
22
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
33
import type { BuiltinTuiPlugin } from "../builtins"
4-
import { createMemo } from "solid-js"
4+
import { createMemo, Show } from "solid-js"
55

66
const id = "internal:sidebar-context"
77

@@ -15,6 +15,31 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
1515
const msg = createMemo(() => props.api.state.session.messages(props.session_id))
1616
const session = createMemo(() => props.api.state.session.get(props.session_id))
1717
const cost = createMemo(() => session()?.cost ?? 0)
18+
const allSessions = createMemo(() => props.api.state.session.list())
19+
20+
const subagentCost = createMemo(() => {
21+
const list = allSessions()
22+
let total = 0
23+
const queue = [props.session_id]
24+
const visited = new Set<string>()
25+
26+
while (queue.length > 0) {
27+
const currentID = queue.shift()!
28+
if (visited.has(currentID)) continue
29+
visited.add(currentID)
30+
31+
for (const s of list) {
32+
if (s.parentID === currentID && s.id !== props.session_id) {
33+
total += s.cost ?? 0
34+
queue.push(s.id)
35+
}
36+
}
37+
}
38+
return total
39+
})
40+
41+
const hasSubagents = createMemo(() => subagentCost() > 0)
42+
const totalCost = createMemo(() => cost() + subagentCost())
1843

1944
const state = createMemo(() => {
2045
const last = msg().findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
@@ -41,7 +66,22 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
4166
</text>
4267
<text fg={theme().textMuted}>{state().tokens.toLocaleString()} tokens</text>
4368
<text fg={theme().textMuted}>{state().percent ?? 0}% used</text>
44-
<text fg={theme().textMuted}>{money.format(cost())} spent</text>
69+
<Show
70+
when={hasSubagents()}
71+
fallback={<text fg={theme().textMuted}>{money.format(cost())} spent</text>}
72+
>
73+
<box marginTop={1}>
74+
<text fg={theme().textMuted}>
75+
Main Agent: <span style={{ fg: theme().text }}>{money.format(cost())}</span>
76+
</text>
77+
<text fg={theme().textMuted}>
78+
Sub-agents: <span style={{ fg: theme().text }}>{money.format(subagentCost())}</span>
79+
</text>
80+
<text fg={theme().textMuted}>
81+
Total Cost: <span style={{ fg: theme().warning }}>{money.format(totalCost())}</span>
82+
</text>
83+
</box>
84+
</Show>
4585
</box>
4686
)
4787
}

packages/tui/src/plugin/adapters.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ function stateApi(sync: ReturnType<typeof useSync>): TuiPluginApi["state"] {
120120
count() {
121121
return sync.data.session.length
122122
},
123+
list() {
124+
return sync.data.session
125+
},
123126
get(sessionID) {
124127
return sync.session.get(sessionID)
125128
},

0 commit comments

Comments
 (0)