Skip to content

Commit f464cb7

Browse files
AnnatarHeclaude
andcommitted
feat(daemon): add complete Codex OTEL field support
Add support for all Codex-specific OTEL fields that the server expects: - ConversationID, CallID, EventKind, ToolTokens for session/tool events - AuthMode, Slug, ContextWindow, ApprovalPolicy, SandboxPolicy for config - MCPServers, Profile, ReasoningEnabled for conversation_starts - ToolArguments, ToolOutput, PromptEncrypted for tool_result Also adds field aliases for Codex OTEL format compatibility: - http.response.status_code -> statusCode - user.account_id -> userAccountUuid - error.message -> error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c6ec301 commit f464cb7

2 files changed

Lines changed: 102 additions & 10 deletions

File tree

daemon/aicode_otel_processor.go

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,13 @@ func extractResourceAttributes(resource *resourcev1.Resource) *model.AICodeOtelR
227227
// Standard resource attributes
228228
case "session.id":
229229
attrs.SessionID = value.GetStringValue()
230+
case "conversation.id":
231+
attrs.ConversationID = value.GetStringValue()
230232
case "app.version":
231233
attrs.AppVersion = value.GetStringValue()
232234
case "organization.id":
233235
attrs.OrganizationID = value.GetStringValue()
234-
case "user.account_uuid":
236+
case "user.account_uuid", "user.account_id":
235237
attrs.UserAccountUUID = value.GetStringValue()
236238
case "terminal.type":
237239
attrs.TerminalType = value.GetStringValue()
@@ -269,6 +271,7 @@ func extractResourceAttributes(resource *resourcev1.Resource) *model.AICodeOtelR
269271
func applyResourceAttributesToMetric(metric *model.AICodeOtelMetric, attrs *model.AICodeOtelResourceAttributes) {
270272
// Standard resource attributes
271273
metric.SessionID = attrs.SessionID
274+
metric.ConversationID = attrs.ConversationID
272275
metric.UserAccountUUID = attrs.UserAccountUUID
273276
metric.OrganizationID = attrs.OrganizationID
274277
metric.TerminalType = attrs.TerminalType
@@ -292,6 +295,7 @@ func applyResourceAttributesToMetric(metric *model.AICodeOtelMetric, attrs *mode
292295
func applyResourceAttributesToEvent(event *model.AICodeOtelEvent, attrs *model.AICodeOtelResourceAttributes) {
293296
// Standard resource attributes
294297
event.SessionID = attrs.SessionID
298+
event.ConversationID = attrs.ConversationID
295299
event.UserAccountUUID = attrs.UserAccountUUID
296300
event.OrganizationID = attrs.OrganizationID
297301
event.TerminalType = attrs.TerminalType
@@ -455,17 +459,61 @@ func (p *AICodeOtelProcessor) parseLogRecord(lr *logsv1.LogRecord, resourceAttrs
455459
slog.Debug("AICodeOtel: Failed to parse tool_parameters", "error", err)
456460
}
457461
}
458-
case "status_code":
462+
case "status_code", "http.response.status_code":
459463
event.StatusCode = getIntFromValue(value)
460464
case "attempt":
461465
event.Attempt = getIntFromValue(value)
466+
case "error.message":
467+
event.Error = value.GetStringValue()
462468
case "language":
463469
event.Language = value.GetStringValue()
464470
// Codex-specific fields
465471
case "reasoning_tokens":
466472
event.ReasoningTokens = getIntFromValue(value)
467473
case "provider":
468474
event.Provider = value.GetStringValue()
475+
// Codex-specific fields for tool_decision
476+
case "call_id", "callId":
477+
event.CallID = value.GetStringValue()
478+
// Codex-specific fields for sse_event
479+
case "event_kind", "eventKind":
480+
event.EventKind = value.GetStringValue()
481+
case "tool_tokens", "toolTokens":
482+
event.ToolTokens = getIntFromValue(value)
483+
// Codex-specific fields for conversation_starts
484+
case "auth_mode", "authMode":
485+
event.AuthMode = value.GetStringValue()
486+
case "slug":
487+
event.Slug = value.GetStringValue()
488+
case "context_window", "contextWindow":
489+
event.ContextWindow = getIntFromValue(value)
490+
case "approval_policy", "approvalPolicy":
491+
event.ApprovalPolicy = value.GetStringValue()
492+
case "sandbox_policy", "sandboxPolicy":
493+
event.SandboxPolicy = value.GetStringValue()
494+
case "mcp_servers", "mcpServers":
495+
event.MCPServers = getStringArrayFromValue(value)
496+
case "profile":
497+
event.Profile = value.GetStringValue()
498+
case "reasoning_enabled", "reasoningEnabled":
499+
event.ReasoningEnabled = getBoolFromValue(value)
500+
// Codex-specific fields for tool_result
501+
case "tool_arguments", "toolArguments":
502+
if jsonStr := value.GetStringValue(); jsonStr != "" {
503+
var args map[string]interface{}
504+
if err := json.Unmarshal([]byte(jsonStr), &args); err == nil {
505+
event.ToolArguments = args
506+
} else {
507+
slog.Debug("AICodeOtel: Failed to parse tool_arguments", "error", err)
508+
}
509+
}
510+
case "tool_output", "toolOutput":
511+
event.ToolOutput = value.GetStringValue()
512+
case "prompt_encrypted", "promptEncrypted":
513+
event.PromptEncrypted = getBoolFromValue(value)
514+
// Codex uses conversation.id instead of session.id
515+
case "conversation.id", "conversationId":
516+
event.ConversationID = value.GetStringValue()
469517
// Log record level attributes that override resource attrs
470518
case "user.id":
471519
event.UserID = value.GetStringValue()
@@ -477,7 +525,7 @@ func (p *AICodeOtelProcessor) parseLogRecord(lr *logsv1.LogRecord, resourceAttrs
477525
event.AppVersion = value.GetStringValue()
478526
case "organization.id":
479527
event.OrganizationID = value.GetStringValue()
480-
case "user.account_uuid":
528+
case "user.account_uuid", "user.account_id":
481529
event.UserAccountUUID = value.GetStringValue()
482530
case "terminal.type":
483531
event.TerminalType = value.GetStringValue()
@@ -559,6 +607,10 @@ func mapEventName(name string, source string) string {
559607
return model.AICodeEventApiError
560608
case "codex.exec_command":
561609
return model.AICodeEventExecCommand
610+
case "codex.conversation_starts":
611+
return model.AICodeEventConversationStarts
612+
case "codex.sse_event":
613+
return model.AICodeEventSSEEvent
562614
default:
563615
return name // Return as-is if not in our map
564616
}
@@ -620,6 +672,20 @@ func getFloatFromValue(value *commonv1.AnyValue) float64 {
620672
return 0
621673
}
622674

675+
// getStringArrayFromValue extracts a string array from an OTEL value
676+
func getStringArrayFromValue(value *commonv1.AnyValue) []string {
677+
if arr := value.GetArrayValue(); arr != nil {
678+
var result []string
679+
for _, v := range arr.GetValues() {
680+
if s := v.GetStringValue(); s != "" {
681+
result = append(result, s)
682+
}
683+
}
684+
return result
685+
}
686+
return nil
687+
}
688+
623689
// applyMetricAttribute applies an attribute to a metric
624690
func applyMetricAttribute(metric *model.AICodeOtelMetric, attr *commonv1.KeyValue, metricType string) {
625691
key := attr.GetKey()

model/aicode_otel_types.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type AICodeOtelRequest struct {
1515
type AICodeOtelResourceAttributes struct {
1616
// Standard resource attributes
1717
SessionID string
18+
ConversationID string // Codex uses conversation.id instead of session.id
1819
UserAccountUUID string
1920
OrganizationID string
2021
TerminalType string
@@ -58,14 +59,37 @@ type AICodeOtelEvent struct {
5859
Error string `json:"error,omitempty"`
5960
PromptLength int `json:"promptLength,omitempty"`
6061
Prompt string `json:"prompt,omitempty"`
62+
PromptEncrypted bool `json:"promptEncrypted,omitempty"` // Whether prompt is encrypted
6163
ToolParameters map[string]interface{} `json:"toolParameters,omitempty"`
6264
StatusCode int `json:"statusCode,omitempty"`
6365
Attempt int `json:"attempt,omitempty"`
6466
Language string `json:"language,omitempty"`
6567
Provider string `json:"provider,omitempty"` // Codex: provider (e.g., "openai")
6668

69+
// Codex-specific fields for tool_decision
70+
CallID string `json:"callId,omitempty"`
71+
72+
// Codex-specific fields for sse_event
73+
EventKind string `json:"eventKind,omitempty"`
74+
ToolTokens int `json:"toolTokens,omitempty"`
75+
76+
// Codex-specific fields for conversation_starts
77+
AuthMode string `json:"authMode,omitempty"`
78+
Slug string `json:"slug,omitempty"`
79+
ContextWindow int `json:"contextWindow,omitempty"`
80+
ApprovalPolicy string `json:"approvalPolicy,omitempty"`
81+
SandboxPolicy string `json:"sandboxPolicy,omitempty"`
82+
MCPServers []string `json:"mcpServers,omitempty"`
83+
Profile string `json:"profile,omitempty"`
84+
ReasoningEnabled bool `json:"reasoningEnabled,omitempty"`
85+
86+
// Codex-specific fields for tool_result
87+
ToolArguments map[string]interface{} `json:"toolArguments,omitempty"`
88+
ToolOutput string `json:"toolOutput,omitempty"`
89+
6790
// Embedded resource attributes (previously in session)
6891
SessionID string `json:"sessionId,omitempty"`
92+
ConversationID string `json:"conversationId,omitempty"` // Codex uses conversationId instead of sessionId
6993
UserAccountUUID string `json:"userAccountUuid,omitempty"`
7094
OrganizationID string `json:"organizationId,omitempty"`
7195
TerminalType string `json:"terminalType,omitempty"`
@@ -85,7 +109,6 @@ type AICodeOtelEvent struct {
85109
Pwd string `json:"pwd,omitempty"`
86110

87111
ClientType string `json:"clientType"` // claude_code, codex (defaults to claude_code)
88-
89112
}
90113

91114
// AICodeOtelMetric represents a metric data point from Claude Code or Codex
@@ -104,6 +127,7 @@ type AICodeOtelMetric struct {
104127

105128
// Embedded resource attributes (previously in session)
106129
SessionID string `json:"sessionId,omitempty"`
130+
ConversationID string `json:"conversationId,omitempty"` // Codex uses conversationId instead of sessionId
107131
UserAccountUUID string `json:"userAccountUuid,omitempty"`
108132
OrganizationID string `json:"organizationId,omitempty"`
109133
TerminalType string `json:"terminalType,omitempty"`
@@ -153,12 +177,14 @@ const (
153177

154178
// AI Code OTEL event types (shared between Claude Code and Codex)
155179
const (
156-
AICodeEventUserPrompt = "user_prompt"
157-
AICodeEventToolResult = "tool_result"
158-
AICodeEventApiRequest = "api_request"
159-
AICodeEventApiError = "api_error"
160-
AICodeEventToolDecision = "tool_decision"
161-
AICodeEventExecCommand = "exec_command" // Codex: shell command execution
180+
AICodeEventUserPrompt = "user_prompt"
181+
AICodeEventToolResult = "tool_result"
182+
AICodeEventApiRequest = "api_request"
183+
AICodeEventApiError = "api_error"
184+
AICodeEventToolDecision = "tool_decision"
185+
AICodeEventExecCommand = "exec_command" // Codex: shell command execution
186+
AICodeEventConversationStarts = "conversation_starts" // Codex: conversation/session start
187+
AICodeEventSSEEvent = "sse_event" // Codex: SSE streaming event
162188
)
163189

164190
// Token types for AICodeMetricTokenUsage

0 commit comments

Comments
 (0)