11import { SeverityNumber } from "@opentelemetry/api-logs"
22import { SpanStatusCode , SpanKind , context , trace } from "@opentelemetry/api"
33import type { AssistantMessage , EventMessageUpdated , EventMessagePartUpdated , ToolPart } from "@opencode-ai/sdk"
4+ import {
5+ AGENT_NAME ,
6+ INPUT_MIME_TYPE ,
7+ INPUT_VALUE ,
8+ LLM_COST_TOTAL ,
9+ LLM_INPUT_MESSAGES ,
10+ LLM_MODEL_NAME ,
11+ LLM_OUTPUT_MESSAGES ,
12+ LLM_PROVIDER ,
13+ LLM_SYSTEM ,
14+ LLM_TOKEN_COUNT_COMPLETION ,
15+ LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING ,
16+ LLM_TOKEN_COUNT_PROMPT ,
17+ LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ ,
18+ LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE ,
19+ LLM_TOKEN_COUNT_TOTAL ,
20+ MimeType ,
21+ OpenInferenceSpanKind ,
22+ OUTPUT_MIME_TYPE ,
23+ OUTPUT_VALUE ,
24+ SemanticConventions ,
25+ SESSION_ID ,
26+ TOOL_ID ,
27+ TOOL_NAME ,
28+ TOOL_PARAMETERS ,
29+ } from "@arizeai/openinference-semantic-conventions"
430import { errorSummary , setBoundedMap , accumulateSessionTotals , isMetricEnabled , isTraceEnabled } from "../util.ts"
531import type { HandlerContext } from "../types.ts"
632
33+ const OPENINFERENCE_SPAN_KIND = SemanticConventions . OPENINFERENCE_SPAN_KIND
34+ const LLM_FINISH_REASON = "llm.finish_reason"
35+
736type SubtaskPart = {
837 type : "subtask"
938 sessionID : string
@@ -79,13 +108,23 @@ export function handleMessageUpdated(e: EventMessageUpdated, ctx: HandlerContext
79108 const msgKey = `${ sessionID } :${ assistant . id } `
80109 const msgSpan = ctx . messageSpans . get ( msgKey )
81110 if ( msgSpan ) {
111+ const outputText = ctx . messageOutputs . get ( msgKey )
82112 msgSpan . setAttributes ( {
83- "gen_ai.usage.input_tokens" : assistant . tokens . input ,
84- "gen_ai.usage.output_tokens" : assistant . tokens . output ,
85- "gen_ai.usage.reasoning_tokens" : assistant . tokens . reasoning ,
86- "gen_ai.usage.cache_read_tokens" : assistant . tokens . cache . read ,
87- "gen_ai.usage.cache_creation_tokens" : assistant . tokens . cache . write ,
88- "gen_ai.response.finish_reason" : assistant . error ? "error" : "stop" ,
113+ [ LLM_TOKEN_COUNT_PROMPT ] : assistant . tokens . input ,
114+ [ LLM_TOKEN_COUNT_COMPLETION ] : assistant . tokens . output ,
115+ [ LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING ] : assistant . tokens . reasoning ,
116+ [ LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ ] : assistant . tokens . cache . read ,
117+ [ LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE ] : assistant . tokens . cache . write ,
118+ [ LLM_TOKEN_COUNT_TOTAL ] : totalTokens ,
119+ [ LLM_FINISH_REASON ] : assistant . error ? "error" : ( assistant . finish ?? "stop" ) ,
120+ [ LLM_COST_TOTAL ] : assistant . cost ,
121+ ...( outputText
122+ ? {
123+ [ OUTPUT_VALUE ] : outputText ,
124+ [ OUTPUT_MIME_TYPE ] : MimeType . TEXT ,
125+ [ LLM_OUTPUT_MESSAGES ] : JSON . stringify ( [ { role : "assistant" , content : outputText } ] ) ,
126+ }
127+ : { } ) ,
89128 cost_usd : assistant . cost ,
90129 duration_ms : duration ,
91130 } )
@@ -96,6 +135,7 @@ export function handleMessageUpdated(e: EventMessageUpdated, ctx: HandlerContext
96135 }
97136 msgSpan . end ( assistant . time . completed )
98137 ctx . messageSpans . delete ( msgKey )
138+ ctx . messageOutputs . delete ( msgKey )
99139 }
100140
101141 if ( assistant . error ) {
@@ -171,6 +211,12 @@ export function handleMessageUpdated(e: EventMessageUpdated, ctx: HandlerContext
171211export function handleMessagePartUpdated ( e : EventMessagePartUpdated , ctx : HandlerContext ) {
172212 const part = e . properties . part
173213
214+ if ( part . type === "text" ) {
215+ const key = `${ part . sessionID } :${ part . messageID } `
216+ ctx . messageOutputs . set ( key , `${ ctx . messageOutputs . get ( key ) ?? "" } ${ part . text } ` )
217+ return
218+ }
219+
174220 if ( part . type === "subtask" ) {
175221 const subtask = part as unknown as SubtaskPart
176222 if ( isMetricEnabled ( "subtask.count" , ctx ) ) {
@@ -219,8 +265,13 @@ export function handleMessagePartUpdated(e: EventMessagePartUpdated, ctx: Handle
219265 startTime : toolPart . state . time . start ,
220266 kind : SpanKind . INTERNAL ,
221267 attributes : {
222- "session.id" : toolPart . sessionID ,
223- "tool.name" : toolPart . tool ,
268+ [ OPENINFERENCE_SPAN_KIND ] : OpenInferenceSpanKind . TOOL ,
269+ [ SESSION_ID ] : toolPart . sessionID ,
270+ [ TOOL_ID ] : toolPart . callID ,
271+ [ TOOL_NAME ] : toolPart . tool ,
272+ [ TOOL_PARAMETERS ] : JSON . stringify ( toolPart . state . input ) ,
273+ [ INPUT_VALUE ] : JSON . stringify ( toolPart . state . input ) ,
274+ [ INPUT_MIME_TYPE ] : MimeType . JSON ,
224275 ...ctx . commonAttrs ,
225276 } ,
226277 } ,
@@ -269,8 +320,13 @@ export function handleMessagePartUpdated(e: EventMessagePartUpdated, ctx: Handle
269320 startTime : start ,
270321 kind : SpanKind . INTERNAL ,
271322 attributes : {
272- "session.id" : toolPart . sessionID ,
273- "tool.name" : toolPart . tool ,
323+ [ OPENINFERENCE_SPAN_KIND ] : OpenInferenceSpanKind . TOOL ,
324+ [ SESSION_ID ] : toolPart . sessionID ,
325+ [ TOOL_ID ] : toolPart . callID ,
326+ [ TOOL_NAME ] : toolPart . tool ,
327+ [ TOOL_PARAMETERS ] : JSON . stringify ( toolPart . state . input ) ,
328+ [ INPUT_VALUE ] : JSON . stringify ( toolPart . state . input ) ,
329+ [ INPUT_MIME_TYPE ] : MimeType . JSON ,
274330 ...ctx . commonAttrs ,
275331 } ,
276332 } ,
@@ -280,10 +336,18 @@ export function handleMessagePartUpdated(e: EventMessagePartUpdated, ctx: Handle
280336 toolSpan . setAttribute ( "tool.success" , success )
281337 if ( success ) {
282338 const output = ( toolPart . state as { output : string } ) . output
339+ toolSpan . setAttributes ( {
340+ [ OUTPUT_VALUE ] : output ,
341+ [ OUTPUT_MIME_TYPE ] : MimeType . TEXT ,
342+ } )
283343 toolSpan . setAttribute ( "tool.result_size_bytes" , Buffer . byteLength ( output , "utf8" ) )
284344 toolSpan . setStatus ( { code : SpanStatusCode . OK } )
285345 } else {
286346 const err = ( toolPart . state as { error : string } ) . error
347+ toolSpan . setAttributes ( {
348+ [ OUTPUT_VALUE ] : err ,
349+ [ OUTPUT_MIME_TYPE ] : MimeType . TEXT ,
350+ } )
287351 toolSpan . setAttribute ( "tool.error" , err )
288352 toolSpan . setStatus ( { code : SpanStatusCode . ERROR , message : err } )
289353 }
@@ -349,14 +413,24 @@ export function startMessageSpan(
349413 : context . active ( )
350414
351415 const msgSpan = ctx . tracer . startSpan (
352- "gen_ai.chat" ,
416+ ` ${ ctx . tracePrefix } llm` ,
353417 {
354418 startTime,
355419 kind : SpanKind . CLIENT ,
356420 attributes : {
357- "gen_ai.system" : providerID ,
358- "gen_ai.request.model" : modelID ,
359- "session.id" : sessionID ,
421+ [ OPENINFERENCE_SPAN_KIND ] : OpenInferenceSpanKind . LLM ,
422+ [ SESSION_ID ] : sessionID ,
423+ [ AGENT_NAME ] : ctx . sessionTotals . get ( sessionID ) ?. agent ?? "unknown" ,
424+ [ LLM_SYSTEM ] : providerID ,
425+ [ LLM_PROVIDER ] : providerID ,
426+ [ LLM_MODEL_NAME ] : modelID ,
427+ ...( ctx . sessionInputs . has ( sessionID )
428+ ? {
429+ [ INPUT_VALUE ] : ctx . sessionInputs . get ( sessionID ) ! ,
430+ [ INPUT_MIME_TYPE ] : MimeType . TEXT ,
431+ [ LLM_INPUT_MESSAGES ] : JSON . stringify ( [ { role : "user" , content : ctx . sessionInputs . get ( sessionID ) ! } ] ) ,
432+ }
433+ : { } ) ,
360434 ...ctx . commonAttrs ,
361435 } ,
362436 } ,
0 commit comments