@@ -3,9 +3,20 @@ import type { AssistantMessage, EventMessageUpdated, EventMessagePartUpdated, To
33import { errorSummary , setBoundedMap , accumulateSessionTotals , isMetricEnabled } from "../util.ts"
44import type { HandlerContext } from "../types.ts"
55
6+ type SubtaskPart = {
7+ type : "subtask"
8+ sessionID : string
9+ messageID : string
10+ prompt : string
11+ description : string
12+ agent : string
13+ }
14+
615/**
716 * Handles a completed assistant message: increments token and cost counters and emits
817 * either an `api_request` or `api_error` log event depending on whether the message errored.
18+ * The `agent` attribute is sourced from the session totals, which are populated by the
19+ * `chat.message` hook when the user prompt is received.
920 */
1021export function handleMessageUpdated ( e : EventMessageUpdated , ctx : HandlerContext ) {
1122 const msg = e . properties . info
@@ -15,45 +26,47 @@ export function handleMessageUpdated(e: EventMessageUpdated, ctx: HandlerContext
1526
1627 const { sessionID, modelID, providerID } = assistant
1728 const duration = assistant . time . completed - assistant . time . created
29+ const agent = ctx . sessionTotals . get ( sessionID ) ?. agent ?? "unknown"
1830
1931 const totalTokens = assistant . tokens . input + assistant . tokens . output + assistant . tokens . reasoning
2032 + assistant . tokens . cache . read + assistant . tokens . cache . write
2133
2234 if ( isMetricEnabled ( "token.usage" , ctx ) ) {
2335 const { tokenCounter } = ctx . instruments
24- tokenCounter . add ( assistant . tokens . input , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , type : "input" } )
25- tokenCounter . add ( assistant . tokens . output , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , type : "output" } )
26- tokenCounter . add ( assistant . tokens . reasoning , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , type : "reasoning" } )
27- tokenCounter . add ( assistant . tokens . cache . read , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , type : "cacheRead" } )
28- tokenCounter . add ( assistant . tokens . cache . write , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , type : "cacheCreation" } )
36+ tokenCounter . add ( assistant . tokens . input , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , agent , type : "input" } )
37+ tokenCounter . add ( assistant . tokens . output , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , agent , type : "output" } )
38+ tokenCounter . add ( assistant . tokens . reasoning , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , agent , type : "reasoning" } )
39+ tokenCounter . add ( assistant . tokens . cache . read , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , agent , type : "cacheRead" } )
40+ tokenCounter . add ( assistant . tokens . cache . write , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , agent , type : "cacheCreation" } )
2941 }
3042
3143 if ( isMetricEnabled ( "cost.usage" , ctx ) ) {
32- ctx . instruments . costCounter . add ( assistant . cost , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID } )
44+ ctx . instruments . costCounter . add ( assistant . cost , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , agent } )
3345 }
3446
3547 if ( isMetricEnabled ( "cache.count" , ctx ) ) {
3648 if ( assistant . tokens . cache . read > 0 ) {
37- ctx . instruments . cacheCounter . add ( 1 , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , type : "cacheRead" } )
49+ ctx . instruments . cacheCounter . add ( 1 , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , agent , type : "cacheRead" } )
3850 }
3951 if ( assistant . tokens . cache . write > 0 ) {
40- ctx . instruments . cacheCounter . add ( 1 , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , type : "cacheCreation" } )
52+ ctx . instruments . cacheCounter . add ( 1 , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , agent , type : "cacheCreation" } )
4153 }
4254 }
4355
4456 if ( isMetricEnabled ( "message.count" , ctx ) ) {
45- ctx . instruments . messageCounter . add ( 1 , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID } )
57+ ctx . instruments . messageCounter . add ( 1 , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , agent } )
4658 }
4759
4860 if ( isMetricEnabled ( "model.usage" , ctx ) ) {
49- ctx . instruments . modelUsageCounter . add ( 1 , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , provider : providerID } )
61+ ctx . instruments . modelUsageCounter . add ( 1 , { ...ctx . commonAttrs , "session.id" : sessionID , model : modelID , provider : providerID , agent } )
5062 }
5163
5264 accumulateSessionTotals ( sessionID , totalTokens , assistant . cost , ctx )
5365
5466 ctx . log ( "debug" , "otel: token+cost counters incremented" , {
5567 sessionID,
5668 model : modelID ,
69+ agent,
5770 input : assistant . tokens . input ,
5871 output : assistant . tokens . output ,
5972 reasoning : assistant . tokens . reasoning ,
@@ -74,6 +87,7 @@ export function handleMessageUpdated(e: EventMessageUpdated, ctx: HandlerContext
7487 "session.id" : sessionID ,
7588 model : modelID ,
7689 provider : providerID ,
90+ agent,
7791 error : errorSummary ( assistant . error ) ,
7892 duration_ms : duration ,
7993 ...ctx . commonAttrs ,
@@ -82,6 +96,7 @@ export function handleMessageUpdated(e: EventMessageUpdated, ctx: HandlerContext
8296 return ctx . log ( "error" , "otel: api_error" , {
8397 sessionID,
8498 model : modelID ,
99+ agent,
85100 error : errorSummary ( assistant . error ) ,
86101 duration_ms : duration ,
87102 } )
@@ -98,6 +113,7 @@ export function handleMessageUpdated(e: EventMessageUpdated, ctx: HandlerContext
98113 "session.id" : sessionID ,
99114 model : modelID ,
100115 provider : providerID ,
116+ agent,
101117 cost_usd : assistant . cost ,
102118 duration_ms : duration ,
103119 input_tokens : assistant . tokens . input ,
@@ -111,6 +127,7 @@ export function handleMessageUpdated(e: EventMessageUpdated, ctx: HandlerContext
111127 return ctx . log ( "info" , "otel: api_request" , {
112128 sessionID,
113129 model : modelID ,
130+ agent,
114131 cost_usd : assistant . cost ,
115132 duration_ms : duration ,
116133 input_tokens : assistant . tokens . input ,
@@ -121,9 +138,43 @@ export function handleMessageUpdated(e: EventMessageUpdated, ctx: HandlerContext
121138/**
122139 * Tracks tool execution time between `running` and `completed`/`error` part updates,
123140 * records a `tool.duration` histogram measurement, and emits a `tool_result` log event.
141+ * Also handles `subtask` parts, incrementing the sub-agent invocation counter and emitting
142+ * a `subtask_invoked` log event.
124143 */
125144export function handleMessagePartUpdated ( e : EventMessagePartUpdated , ctx : HandlerContext ) {
126145 const part = e . properties . part
146+
147+ if ( part . type === "subtask" ) {
148+ const subtask = part as unknown as SubtaskPart
149+ if ( isMetricEnabled ( "subtask.count" , ctx ) ) {
150+ ctx . instruments . subtaskCounter . add ( 1 , {
151+ ...ctx . commonAttrs ,
152+ "session.id" : subtask . sessionID ,
153+ agent : subtask . agent ,
154+ } )
155+ }
156+ ctx . logger . emit ( {
157+ severityNumber : SeverityNumber . INFO ,
158+ severityText : "INFO" ,
159+ timestamp : Date . now ( ) ,
160+ observedTimestamp : Date . now ( ) ,
161+ body : "subtask_invoked" ,
162+ attributes : {
163+ "event.name" : "subtask_invoked" ,
164+ "session.id" : subtask . sessionID ,
165+ agent : subtask . agent ,
166+ description : subtask . description ,
167+ prompt_length : subtask . prompt . length ,
168+ ...ctx . commonAttrs ,
169+ } ,
170+ } )
171+ return ctx . log ( "info" , "otel: subtask_invoked" , {
172+ sessionID : subtask . sessionID ,
173+ agent : subtask . agent ,
174+ description : subtask . description ,
175+ } )
176+ }
177+
127178 if ( part . type !== "tool" ) return
128179
129180 const toolPart = part as ToolPart
0 commit comments