@@ -36,7 +36,7 @@ use std::collections::HashMap;
3636
3737use super :: { normalise_provider, RecordBuilder } ;
3838use crate :: config:: Config ;
39- use crate :: db:: { Database , SessionMeta , ToolUsage } ;
39+ use crate :: db:: { Database , ModeTime , SessionMeta , ToolUsage } ;
4040
4141pub fn sync ( cfg : & Config , db : & Database ) -> Result < usize > {
4242 let src_path = locate_db ( cfg) ?;
@@ -46,7 +46,7 @@ pub fn sync(cfg: &Config, db: &Database) -> Result<usize> {
4646 let last_sync = db. get_last_sync ( "opencode" ) ?. unwrap_or_default ( ) ;
4747
4848 // ── Try the "standard" schema first ───────────────────────────────────────
49- let ( records, metas, tool_usage) = match read_standard_schema ( & src, & last_sync, cfg) {
49+ let ( records, metas, tool_usage, mode_times ) = match read_standard_schema ( & src, & last_sync, cfg) {
5050 Ok ( r) => r,
5151 Err ( e) => {
5252 // Fallback: try alternate column names seen in some builds
@@ -72,6 +72,9 @@ pub fn sync(cfg: &Config, db: &Database) -> Result<usize> {
7272 if !tool_usage. is_empty ( ) {
7373 db. insert_tool_usage ( & tool_usage) ?;
7474 }
75+ if !mode_times. is_empty ( ) {
76+ db. insert_mode_times ( & mode_times) ?;
77+ }
7578 db. update_sync_state ( "opencode" ) ?;
7679 Ok ( inserted)
7780}
@@ -113,7 +116,7 @@ fn read_standard_schema(
113116 conn : & Connection ,
114117 since : & str ,
115118 cfg : & Config ,
116- ) -> Result < ( Vec < crate :: db:: UsageRecord > , Vec < SessionMeta > , Vec < ToolUsage > ) > {
119+ ) -> Result < ( Vec < crate :: db:: UsageRecord > , Vec < SessionMeta > , Vec < ToolUsage > , Vec < ModeTime > ) > {
117120 let builder = RecordBuilder { source : "opencode" } ;
118121
119122 // Convert RFC 3339 last_sync to Unix ms for integer comparison.
@@ -137,6 +140,8 @@ let mut stmt = conn.prepare(
137140 COALESCE(json_extract(m.data, '$.tokens.cacheRead'), 0) AS cache_read,
138141 COALESCE(json_extract(m.data, '$.tokens.cacheWrite'), 0) AS cache_write,
139142 COALESCE(json_extract(m.data, '$.tokens.total'), 0) AS total_tokens,
143+ COALESCE(json_extract(m.data, '$.mode'), 'unknown') AS mode,
144+ COALESCE(json_extract(m.data, '$.time.completed'), 0) - COALESCE(json_extract(m.data, '$.time.created'), 0) AS duration_ms,
140145 m.time_created,
141146 COALESCE(json_extract(m.data, '$.path.cwd'), s.directory) AS cwd,
142147 s.parent_id,
@@ -163,18 +168,21 @@ let mut stmt = conn.prepare(
163168 row. get :: < _ , i64 > ( 6 ) ?, // cache_read_tokens
164169 row. get :: < _ , i64 > ( 7 ) ?, // cache_write_tokens
165170 row. get :: < _ , i64 > ( 8 ) ?, // total_tokens
166- row. get :: < _ , i64 > ( 9 ) ?, // time_created (Unix ms)
167- row. get :: < _ , Option < String > > ( 10 ) ?, // cwd
168- row. get :: < _ , Option < String > > ( 11 ) ?, // parent_id
169- row. get :: < _ , i64 > ( 12 ) ?, // additions
170- row. get :: < _ , i64 > ( 13 ) ?, // deletions
171- row. get :: < _ , i64 > ( 14 ) ?, // files_changed
171+ row. get :: < _ , Option < String > > ( 9 ) ?, // mode
172+ row. get :: < _ , i64 > ( 10 ) ?, // duration_ms
173+ row. get :: < _ , i64 > ( 11 ) ?, // time_created (Unix ms)
174+ row. get :: < _ , Option < String > > ( 12 ) ?, // cwd
175+ row. get :: < _ , Option < String > > ( 13 ) ?, // parent_id
176+ row. get :: < _ , i64 > ( 14 ) ?, // additions
177+ row. get :: < _ , i64 > ( 15 ) ?, // deletions
178+ row. get :: < _ , i64 > ( 16 ) ?, // files_changed
172179 ) )
173180 } ) ?;
174181
175182 let mut records = Vec :: new ( ) ;
176183 let mut session_metas: HashMap < String , SessionMeta > = HashMap :: new ( ) ;
177184 let mut tool_counts: HashMap < String , HashMap < String , i64 > > = HashMap :: new ( ) ;
185+ let mut mode_durations: HashMap < String , HashMap < String , i64 > > = HashMap :: new ( ) ;
178186
179187 // Also fetch tool calls from the part table
180188 let mut tool_stmt = conn. prepare (
@@ -210,6 +218,8 @@ let mut stmt = conn.prepare(
210218 cache_read,
211219 cache_write,
212220 total_tokens,
221+ mode,
222+ duration_ms,
213223 time_ms,
214224 cwd,
215225 parent_id,
@@ -218,6 +228,16 @@ let mut stmt = conn.prepare(
218228 files_changed,
219229 ) = row;
220230
231+ // Track mode time per session
232+ let mode_str = mode. unwrap_or_else ( || "unknown" . to_string ( ) ) ;
233+ if duration_ms > 0 {
234+ let duration_sec = duration_ms / 1000 ;
235+ mode_durations
236+ . entry ( session_id. clone ( ) )
237+ . or_default ( )
238+ . insert ( mode_str. clone ( ) , duration_sec) ;
239+ }
240+
221241 // Track session metadata (code diff summary) - accumulate per session
222242 let entry = session_metas. entry ( session_id. clone ( ) ) . or_insert ( SessionMeta {
223243 session_id : session_id. clone ( ) ,
@@ -281,7 +301,19 @@ let mut stmt = conn.prepare(
281301 }
282302 }
283303
284- Ok ( ( records, metas, tool_usage) )
304+ // Convert mode_durations to ModeTime vec
305+ let mut mode_times: Vec < ModeTime > = Vec :: new ( ) ;
306+ for ( session_id, modes) in mode_durations {
307+ for ( mode, duration) in modes {
308+ mode_times. push ( ModeTime {
309+ session_id : session_id. clone ( ) ,
310+ mode,
311+ duration_secs : duration,
312+ } ) ;
313+ }
314+ }
315+
316+ Ok ( ( records, metas, tool_usage, mode_times) )
285317}
286318
287319// ── Flat-column fallback (older OpenCode builds) ──────────────────────────────
@@ -290,7 +322,7 @@ fn read_alternate_schema(
290322 conn : & Connection ,
291323 since : & str ,
292324 cfg : & Config ,
293- ) -> Result < ( Vec < crate :: db:: UsageRecord > , Vec < SessionMeta > , Vec < ToolUsage > ) > {
325+ ) -> Result < ( Vec < crate :: db:: UsageRecord > , Vec < SessionMeta > , Vec < ToolUsage > , Vec < ModeTime > ) > {
294326 let builder = RecordBuilder { source : "opencode" } ;
295327
296328 // Older builds stored columns directly on the message table
@@ -353,7 +385,7 @@ fn read_alternate_schema(
353385 records. push ( record) ;
354386 }
355387 // No session metadata or tool usage available in alternate schema
356- Ok ( ( records, Vec :: new ( ) , Vec :: new ( ) ) )
388+ Ok ( ( records, Vec :: new ( ) , Vec :: new ( ) , Vec :: new ( ) ) )
357389}
358390
359391fn infer_provider_from_model ( model : & str ) -> String {
0 commit comments