11import { SeverityNumber } from "@opentelemetry/api-logs"
2- import { SpanStatusCode , trace } from "@opentelemetry/api"
2+ import { SpanStatusCode } from "@opentelemetry/api"
33import type { EventSessionCreated , EventSessionIdle , EventSessionError , EventSessionStatus } from "@opencode-ai/sdk"
4- import { AGENT_NAME , OpenInferenceSpanKind , SemanticConventions , SESSION_ID } from "@arizeai/openinference-semantic-conventions"
5- import { agentAttrs , errorSummary , getSessionAgentMeta , setBoundedMap , isMetricEnabled , isTraceEnabled } from "../util.ts"
4+ import {
5+ AGENT_NAME ,
6+ INPUT_MIME_TYPE ,
7+ INPUT_VALUE ,
8+ LLM_INPUT_MESSAGES ,
9+ MimeType ,
10+ OpenInferenceSpanKind ,
11+ SemanticConventions ,
12+ SESSION_ID ,
13+ } from "@arizeai/openinference-semantic-conventions"
14+ import {
15+ agentAttrs ,
16+ errorSummary ,
17+ getSessionAgentMeta ,
18+ setBoundedMap ,
19+ isMetricEnabled ,
20+ isTraceEnabled ,
21+ resolveSessionTraceContext ,
22+ } from "../util.ts"
623import type { HandlerContext , SessionAgentType } from "../types.ts"
724
825const OPENINFERENCE_SPAN_KIND = SemanticConventions . OPENINFERENCE_SPAN_KIND
926
27+ /** Starts or refreshes the root run span for a single user turn, keyed by the user message ID. */
28+ export function handleRunStarted (
29+ runID : string ,
30+ sessionID : string ,
31+ agent : string ,
32+ promptText : string ,
33+ model : string ,
34+ startTime : number ,
35+ ctx : HandlerContext ,
36+ ) {
37+ ctx . activeRuns . set ( sessionID , runID )
38+ ctx . pendingRuns . delete ( sessionID )
39+ if ( promptText ) setBoundedMap ( ctx . runInputs , runID , promptText )
40+ if ( ! isTraceEnabled ( "session" , ctx ) ) return
41+ const existing = ctx . runSpans . get ( runID )
42+ if ( existing ) {
43+ existing . setAttributes ( {
44+ [ AGENT_NAME ] : agent ,
45+ ...( promptText
46+ ? {
47+ [ INPUT_VALUE ] : promptText ,
48+ [ INPUT_MIME_TYPE ] : MimeType . TEXT ,
49+ [ LLM_INPUT_MESSAGES ] : JSON . stringify ( [ { role : "user" , content : promptText } ] ) ,
50+ }
51+ : { } ) ,
52+ model,
53+ } )
54+ return
55+ }
56+
57+ const runSpan = ctx . tracer . startSpan (
58+ `${ ctx . tracePrefix } session` ,
59+ {
60+ startTime,
61+ attributes : {
62+ [ OPENINFERENCE_SPAN_KIND ] : OpenInferenceSpanKind . AGENT ,
63+ [ SESSION_ID ] : sessionID ,
64+ [ AGENT_NAME ] : agent ,
65+ "agent.type" : "primary" ,
66+ "session.is_subagent" : false ,
67+ ...( promptText
68+ ? {
69+ [ INPUT_VALUE ] : promptText ,
70+ [ INPUT_MIME_TYPE ] : MimeType . TEXT ,
71+ [ LLM_INPUT_MESSAGES ] : JSON . stringify ( [ { role : "user" , content : promptText } ] ) ,
72+ }
73+ : { } ) ,
74+ model,
75+ ...ctx . commonAttrs ,
76+ } ,
77+ } ,
78+ ctx . rootContext ( ) ,
79+ )
80+ ctx . runSpans . set ( runID , runSpan )
81+ setBoundedMap ( ctx . runSpanContexts , runID , runSpan . spanContext ( ) )
82+ }
83+
1084/** Increments the session counter, records start time, starts the root session span, and emits a `session.created` log event. */
1185export function handleSessionCreated ( e : EventSessionCreated , ctx : HandlerContext ) {
1286 const { id : sessionID , time, parentID } = e . properties . info
@@ -18,16 +92,7 @@ export function handleSessionCreated(e: EventSessionCreated, ctx: HandlerContext
1892 }
1993 setBoundedMap ( ctx . sessionTotals , sessionID , { startMs : createdAt , tokens : 0 , cost : 0 , messages : 0 , agent : "unknown" , agentType } )
2094
21- // WARNING: disabling "session" traces while "llm" or "tool" traces remain enabled
22- // leaves those child spans without a local session parent. If OPENCODE_TRACEPARENT
23- // is set, they fall back to that remote parent; otherwise they become root spans.
24- if ( isTraceEnabled ( "session" , ctx ) ) {
25- const parentSpan = parentID ? ctx . sessionSpans . get ( parentID ) : undefined
26- const baseCtx = ctx . rootContext ( )
27- const spanCtx = parentSpan
28- ? trace . setSpan ( baseCtx , parentSpan )
29- : baseCtx
30-
95+ if ( isTraceEnabled ( "session" , ctx ) && parentID ) {
3196 const sessionSpan = ctx . tracer . startSpan (
3297 `${ ctx . tracePrefix } session` ,
3398 {
@@ -41,9 +106,10 @@ export function handleSessionCreated(e: EventSessionCreated, ctx: HandlerContext
41106 ...ctx . commonAttrs ,
42107 } ,
43108 } ,
44- spanCtx ,
109+ resolveSessionTraceContext ( parentID , ctx ) ,
45110 )
46- setBoundedMap ( ctx . sessionSpans , sessionID , sessionSpan )
111+ ctx . sessionSpans . set ( sessionID , sessionSpan )
112+ setBoundedMap ( ctx . sessionSpanContexts , sessionID , sessionSpan . spanContext ( ) )
47113 }
48114
49115 ctx . emitLog ( {
@@ -74,7 +140,7 @@ function sweepSession(sessionID: string, ctx: HandlerContext) {
74140 ctx . pendingToolSpans . delete ( key )
75141 }
76142 }
77- ctx . sessionInputs . delete ( sessionID )
143+ ctx . pendingRuns . delete ( sessionID )
78144 const msgPrefix = `${ sessionID } :`
79145 for ( const [ key , span ] of ctx . messageSpans ) {
80146 if ( key . startsWith ( msgPrefix ) ) {
@@ -128,6 +194,23 @@ export function handleSessionIdle(e: EventSessionIdle, ctx: HandlerContext) {
128194 sessionSpan . end ( )
129195 ctx . sessionSpans . delete ( sessionID )
130196 }
197+ const runID = ctx . activeRuns . get ( sessionID )
198+ if ( runID ) ctx . activeRuns . delete ( sessionID )
199+ const runSpan = runID ? ctx . runSpans . get ( runID ) : undefined
200+ if ( runSpan ) {
201+ if ( totals ) {
202+ runSpan . setAttributes ( {
203+ [ AGENT_NAME ] : totals . agent ,
204+ "agent.type" : totals . agentType ,
205+ "session.total_tokens" : totals . tokens ,
206+ "session.total_cost_usd" : totals . cost ,
207+ "session.total_messages" : totals . messages ,
208+ } )
209+ }
210+ runSpan . setStatus ( { code : SpanStatusCode . OK } )
211+ runSpan . end ( )
212+ ctx . runSpans . delete ( runID ! )
213+ }
131214
132215 ctx . emitLog ( {
133216 severityNumber : SeverityNumber . INFO ,
@@ -173,6 +256,16 @@ export function handleSessionError(e: EventSessionError, ctx: HandlerContext) {
173256 sessionSpan . end ( )
174257 ctx . sessionSpans . delete ( rawID )
175258 }
259+ const runID = ctx . activeRuns . get ( rawID )
260+ if ( runID ) ctx . activeRuns . delete ( rawID )
261+ const runSpan = runID ? ctx . runSpans . get ( runID ) : undefined
262+ if ( runSpan ) {
263+ if ( totals ) runSpan . setAttributes ( { [ AGENT_NAME ] : totals . agent , "agent.type" : totals . agentType } )
264+ runSpan . setStatus ( { code : SpanStatusCode . ERROR , message : error } )
265+ runSpan . setAttribute ( "error" , error )
266+ runSpan . end ( )
267+ ctx . runSpans . delete ( runID ! )
268+ }
176269 }
177270
178271 ctx . emitLog ( {
0 commit comments