11import { SeverityNumber } from "@opentelemetry/api-logs"
2- import type { EventSessionCreated , EventSessionIdle , EventSessionError } from "@opencode-ai/sdk"
3- import { errorSummary } from "../util.ts"
2+ import type { EventSessionCreated , EventSessionIdle , EventSessionError , EventSessionStatus } from "@opencode-ai/sdk"
3+ import { errorSummary , setBoundedMap } from "../util.ts"
44import type { HandlerContext } from "../types.ts"
55
6- /** Increments the session counter and emits a `session.created` log event. */
6+ /** Increments the session counter, records start time, and emits a `session.created` log event. */
77export function handleSessionCreated ( e : EventSessionCreated , ctx : HandlerContext ) {
88 const sessionID = e . properties . info . id
99 const createdAt = e . properties . info . time . created
1010 ctx . instruments . sessionCounter . add ( 1 , { ...ctx . commonAttrs , "session.id" : sessionID } )
11+ setBoundedMap ( ctx . sessionTotals , sessionID , { startMs : createdAt , tokens : 0 , cost : 0 , messages : 0 } )
1112 ctx . logger . emit ( {
1213 severityNumber : SeverityNumber . INFO ,
1314 severityText : "INFO" ,
@@ -16,7 +17,7 @@ export function handleSessionCreated(e: EventSessionCreated, ctx: HandlerContext
1617 body : "session.created" ,
1718 attributes : { "event.name" : "session.created" , "session.id" : sessionID , ...ctx . commonAttrs } ,
1819 } )
19- return ctx . log ( "info" , "otel: session.created" , { sessionID } )
20+ return ctx . log ( "info" , "otel: session.created" , { sessionID, createdAt } )
2021}
2122
2223function sweepSession ( sessionID : string , ctx : HandlerContext ) {
@@ -28,23 +29,50 @@ function sweepSession(sessionID: string, ctx: HandlerContext) {
2829 }
2930}
3031
31- /** Emits a `session.idle` log event and clears any pending tool spans and permissions for the session . */
32+ /** Emits a `session.idle` log event, records duration and session total histograms, and clears pending state . */
3233export function handleSessionIdle ( e : EventSessionIdle , ctx : HandlerContext ) {
3334 const sessionID = e . properties . sessionID
35+ const totals = ctx . sessionTotals . get ( sessionID )
36+ ctx . sessionTotals . delete ( sessionID )
3437 sweepSession ( sessionID , ctx )
38+
39+ const attrs = { ...ctx . commonAttrs , "session.id" : sessionID }
40+ let duration_ms : number | undefined
41+
42+ if ( totals ) {
43+ duration_ms = Date . now ( ) - totals . startMs
44+ ctx . instruments . sessionDurationHistogram . record ( duration_ms , attrs )
45+ ctx . instruments . sessionTokenGauge . record ( totals . tokens , attrs )
46+ ctx . instruments . sessionCostGauge . record ( totals . cost , attrs )
47+ }
48+
3549 ctx . logger . emit ( {
3650 severityNumber : SeverityNumber . INFO ,
3751 severityText : "INFO" ,
3852 timestamp : Date . now ( ) ,
3953 observedTimestamp : Date . now ( ) ,
4054 body : "session.idle" ,
41- attributes : { "event.name" : "session.idle" , "session.id" : sessionID , ...ctx . commonAttrs } ,
55+ attributes : {
56+ "event.name" : "session.idle" ,
57+ "session.id" : sessionID ,
58+ total_tokens : totals ?. tokens ?? 0 ,
59+ total_cost_usd : totals ?. cost ?? 0 ,
60+ total_messages : totals ?. messages ?? 0 ,
61+ ...ctx . commonAttrs ,
62+ } ,
63+ } )
64+ ctx . log ( "debug" , "otel: session.idle" , {
65+ sessionID,
66+ ...( totals ? { duration_ms, total_tokens : totals . tokens , total_cost_usd : totals . cost , total_messages : totals . messages } : { } ) ,
4267 } )
4368}
4469
4570/** Emits a `session.error` log event and clears any pending tool spans and permissions for the session. */
4671export function handleSessionError ( e : EventSessionError , ctx : HandlerContext ) {
47- const sessionID = e . properties . sessionID ?? "unknown"
72+ const rawID = e . properties . sessionID
73+ const sessionID = rawID ?? "unknown"
74+ const error = errorSummary ( e . properties . error )
75+ if ( rawID ) ctx . sessionTotals . delete ( rawID )
4876 sweepSession ( sessionID , ctx )
4977 ctx . logger . emit ( {
5078 severityNumber : SeverityNumber . ERROR ,
@@ -55,8 +83,18 @@ export function handleSessionError(e: EventSessionError, ctx: HandlerContext) {
5583 attributes : {
5684 "event.name" : "session.error" ,
5785 "session.id" : sessionID ,
58- error : errorSummary ( e . properties . error ) ,
86+ error,
5987 ...ctx . commonAttrs ,
6088 } ,
6189 } )
90+ ctx . log ( "error" , "otel: session.error" , { sessionID, error } )
91+ }
92+
93+ /** Increments the retry counter when the session enters a retry state. */
94+ export function handleSessionStatus ( e : EventSessionStatus , ctx : HandlerContext ) {
95+ if ( e . properties . status . type !== "retry" ) return
96+ const { sessionID, status } = e . properties
97+ const { attempt, message : retryMessage } = status
98+ ctx . instruments . retryCounter . add ( 1 , { ...ctx . commonAttrs , "session.id" : sessionID } )
99+ ctx . log ( "debug" , "otel: retry counter incremented" , { sessionID, attempt, retryMessage } )
62100}
0 commit comments