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
readonlyvcs: {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
importtype{AssistantMessage}from"@opencode-ai/sdk/v2"
22
importtype{TuiPlugin,TuiPluginApi}from"@opencode-ai/plugin/tui"
33
importtype{BuiltinTuiPlugin}from"../builtins"
4-
import{createMemo}from"solid-js"
4+
import{createMemo,Show}from"solid-js"
55

66
constid="internal:sidebar-context"
77

@@ -15,6 +15,31 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
1515
constmsg=createMemo(()=>props.api.state.session.messages(props.session_id))
1616
constsession=createMemo(()=>props.api.state.session.get(props.session_id))
1717
constcost=createMemo(()=>session()?.cost??0)
18+
constallSessions=createMemo(()=>props.api.state.session.list())
19+
20+
constsubagentCost=createMemo(()=>{
21+
constlist=allSessions()
22+
lettotal=0
23+
constqueue=[props.session_id]
24+
constvisited=newSet<string>()
25+
26+
while(queue.length>0){
27+
constcurrentID=queue.shift()!
28+
if(visited.has(currentID))continue
29+
visited.add(currentID)
30+
31+
for(constsoflist){
32+
if(s.parentID===currentID&&s.id!==props.session_id){
33+
total+=s.cost??0
34+
queue.push(s.id)
35+
}
36+
}
37+
}
38+
returntotal
39+
})
40+
41+
consthasSubagents=createMemo(()=>subagentCost()>0)
42+
consttotalCost=createMemo(()=>cost()+subagentCost())
1843

1944
conststate=createMemo(()=>{
2045
constlast=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
<textfg={theme().textMuted}>{state().tokens.toLocaleString()} tokens</text>
4368
<textfg={theme().textMuted}>{state().percent??0}% used</text>
44-
<textfg={theme().textMuted}>{money.format(cost())} spent</text>
69+
<Show
70+
when={hasSubagents()}
71+
fallback={<textfg={theme().textMuted}>{money.format(cost())} spent</text>}
72+
>
73+
<boxmarginTop={1}>
74+
<textfg={theme().textMuted}>
75+
Main Agent: <spanstyle={{fg: theme().text}}>{money.format(cost())}</span>
76+
</text>
77+
<textfg={theme().textMuted}>
78+
Sub-agents: <spanstyle={{fg: theme().text}}>{money.format(subagentCost())}</span>
79+
</text>
80+
<textfg={theme().textMuted}>
81+
Total Cost: <spanstyle={{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
returnsync.data.session.length
122122
},
123+
list(){
124+
returnsync.data.session
125+
},
123126
get(sessionID){
124127
returnsync.session.get(sessionID)
125128
},

0 commit comments

Comments
 (0)