Skip to content

Commit 1930028

Browse files
author
joo
committed
Update call log session metrics UI
1 parent fb7473e commit 1930028

16 files changed

Lines changed: 723 additions & 83 deletions

core/internal/buildinfo/buildinfo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import "strings"
44

55
// Set at link time via -ldflags (see .goreleaser.yaml).
66
var (
7-
Version = "dev0.1.63"
7+
Version = "dev0.1.67"
88
Commit = "none"
99
Date = "unknown"
1010
)

core/internal/proxy/calllog.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,18 @@ type CallLogTokenUsage struct {
4545
}
4646

4747
type CallLogEntry struct {
48-
ID string `json:"id"`
49-
Session string `json:"session,omitempty"`
50-
SessionID string `json:"sessionId,omitempty"`
51-
SessionKind string `json:"sessionKind,omitempty"`
52-
StartedAt string `json:"startedAt"`
53-
CompletedAt string `json:"completedAt"`
54-
DurationMs int64 `json:"durationMs"`
55-
Request CallLogRequest `json:"request"`
56-
Upstream CallLogUpstream `json:"upstream"`
57-
TokenUsage *CallLogTokenUsage `json:"tokenUsage,omitempty"`
58-
Error string `json:"error,omitempty"`
48+
ID string `json:"id"`
49+
Session string `json:"session,omitempty"`
50+
SessionID string `json:"sessionId,omitempty"`
51+
SessionKind string `json:"sessionKind,omitempty"`
52+
StartedAt string `json:"startedAt"`
53+
CompletedAt string `json:"completedAt"`
54+
DurationMs int64 `json:"durationMs"`
55+
Request CallLogRequest `json:"request"`
56+
Upstream CallLogUpstream `json:"upstream"`
57+
TokenUsage *CallLogTokenUsage `json:"tokenUsage,omitempty"`
58+
ToolCallCount int `json:"toolCallCount,omitempty"`
59+
Error string `json:"error,omitempty"`
5960
}
6061

6162
type CallLogStore struct {
@@ -362,6 +363,7 @@ func (t *requestTrace) setUpstreamResponse(status int, headers http.Header, body
362363
body = sanitizeCallLogUpstreamBody(headers, body)
363364
t.entry.Upstream.Body = redactBodySecrets(body)
364365
t.entry.TokenUsage = ExtractCallLogTokenUsage(t.entry.Upstream.Body)
366+
t.entry.ToolCallCount = ExtractCallLogToolCallCount(t.entry.Upstream.Body)
365367
}
366368

367369
func (t *requestTrace) setError(msg string) {
@@ -381,6 +383,9 @@ func (t *requestTrace) finish() {
381383
if t.entry.TokenUsage == nil {
382384
t.entry.TokenUsage = ExtractCallLogTokenUsage(t.entry.Upstream.Body)
383385
}
386+
if t.entry.ToolCallCount == 0 {
387+
t.entry.ToolCallCount = ExtractCallLogToolCallCount(t.entry.Upstream.Body)
388+
}
384389
t.store.Push(t.entry)
385390
}
386391

core/internal/proxy/calllog_session.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package proxy
22

33
import (
44
"encoding/json"
5+
"net/url"
56
"path/filepath"
67
"regexp"
78
"strings"
@@ -15,6 +16,10 @@ const openCodeSessionHeader = "x-session-affinity"
1516
var sessionFilenameSanitizer = regexp.MustCompile(`[^a-zA-Z0-9-]+`)
1617

1718
func extractCallLogSession(headers map[string]string) (kind, sessionID string) {
19+
return extractCallLogSessionForRequest(headers, "")
20+
}
21+
22+
func extractCallLogSessionForRequest(headers map[string]string, requestURL string) (kind, sessionID string) {
1823
if id := extractHeaderValue(headers, claudeCodeSessionHeader); id != "" {
1924
return "claudecode", id
2025
}
@@ -31,9 +36,42 @@ func extractCallLogSession(headers map[string]string) (kind, sessionID string) {
3136
return "codex", id
3237
}
3338
}
39+
if id := extractCustomAPITestSessionID(requestURL); id != "" {
40+
return "test", id
41+
}
3442
return "", ""
3543
}
3644

45+
func extractCustomAPITestSessionID(rawURL string) string {
46+
path := requestPath(rawURL)
47+
if path == "" {
48+
return ""
49+
}
50+
parts := strings.Split(strings.Trim(path, "/"), "/")
51+
if len(parts) < 4 {
52+
return ""
53+
}
54+
if parts[0] != "custom-api" || parts[2] == "" || parts[3] != "v1" {
55+
return ""
56+
}
57+
if parts[1] != "same-chat-model-id" {
58+
return ""
59+
}
60+
return strings.Join(parts[:3], "-")
61+
}
62+
63+
func requestPath(rawURL string) string {
64+
rawURL = strings.TrimSpace(rawURL)
65+
if rawURL == "" {
66+
return ""
67+
}
68+
parsed, err := url.Parse(rawURL)
69+
if err == nil && parsed.Path != "" {
70+
return parsed.Path
71+
}
72+
return rawURL
73+
}
74+
3775
func extractHeaderValue(headers map[string]string, want string) string {
3876
for key, val := range headers {
3977
if !strings.EqualFold(strings.TrimSpace(key), want) {
@@ -91,7 +129,7 @@ func sanitizeSessionFilename(sessionID string) string {
91129
}
92130

93131
func callLogPathForEntry(logsDir string, entry CallLogEntry) string {
94-
kind, sessionID := extractCallLogSession(entry.Request.Headers)
132+
kind, sessionID := extractCallLogSessionForRequest(entry.Request.Headers, entry.Request.URL)
95133
if kind == "claudecode" || kind == "codex" {
96134
if safe := sanitizeSessionFilename(sessionID); safe != "" {
97135
return filepath.Join(logsDir, kind, safe+".jsonl")
@@ -104,7 +142,7 @@ func applyCallLogSessionMeta(entry *CallLogEntry) {
104142
if entry == nil {
105143
return
106144
}
107-
kind, sessionID := extractCallLogSession(entry.Request.Headers)
145+
kind, sessionID := extractCallLogSessionForRequest(entry.Request.Headers, entry.Request.URL)
108146
entry.SessionID = sessionID
109147
entry.SessionKind = kind
110148
entry.Session = callLogSessionKey(kind, sessionID)

core/internal/proxy/calllog_session_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,32 @@ func TestCallLogWithoutSessionUsesDefaultBucket(t *testing.T) {
105105
}
106106
}
107107

108+
func TestCustomAPISameChatModelRequestsUseTestSession(t *testing.T) {
109+
store := openTestCallLogStore(t)
110+
url := "http://127.0.0.1:51232/custom-api/same-chat-model-id/openai-chat/v1/chat/completions"
111+
store.Push(CallLogEntry{StartedAt: "2026-01-01T00:00:01Z", Request: CallLogRequest{Method: "POST", URL: url}})
112+
store.Push(CallLogEntry{StartedAt: "2026-01-01T00:00:02Z", Request: CallLogRequest{Method: "POST", URL: url}})
113+
114+
entries := store.ListRecentSession(0, "custom-api-same-chat-model-id-openai-chat")
115+
if len(entries) != 2 {
116+
t.Fatalf("expected 2 test session entries, got %d", len(entries))
117+
}
118+
if entries[0].Session != "test-custom-api-same-chat-model-id-openai-chat" {
119+
t.Fatalf("session = %q", entries[0].Session)
120+
}
121+
if entries[0].SessionKind != "test" {
122+
t.Fatalf("sessionKind = %q", entries[0].SessionKind)
123+
}
124+
125+
sessions := store.ListSessions(0)
126+
if len(sessions) != 1 {
127+
t.Fatalf("expected 1 session group, got %d", len(sessions))
128+
}
129+
if sessions[0].SessionID != "custom-api-same-chat-model-id-openai-chat" || sessions[0].EntryCount != 2 {
130+
t.Fatalf("unexpected session summary: %#v", sessions[0])
131+
}
132+
}
133+
108134
func TestSanitizeSessionFilenameRejectsTraversal(t *testing.T) {
109135
got := sanitizeSessionFilename("../../etc/passwd")
110136
if got != "etcpasswd" {
@@ -121,6 +147,7 @@ func TestListCallLogSessions(t *testing.T) {
121147
URL: "/claude/v1/messages",
122148
Headers: map[string]string{"X-Claude-Code-Session-Id": "abc-123"},
123149
},
150+
Upstream: CallLogUpstream{Body: `{"output":[{"type":"function_call","name":"Shell"}],"usage":{"input_tokens":10,"output_tokens":2,"total_tokens":12}}`},
124151
})
125152
store.Push(CallLogEntry{
126153
StartedAt: "2026-01-01T00:00:02Z",
@@ -129,6 +156,7 @@ func TestListCallLogSessions(t *testing.T) {
129156
URL: "/claude/v1/messages",
130157
Headers: map[string]string{"X-Claude-Code-Session-Id": "abc-123"},
131158
},
159+
Upstream: CallLogUpstream{Body: `{"output":[{"type":"function_call","name":"ReadFile"},{"type":"function_call","name":"Shell"}],"usage":{"input_tokens":5,"output_tokens":3,"total_tokens":8}}`},
132160
})
133161
store.Push(CallLogEntry{StartedAt: "2026-01-01T00:00:03Z", Request: CallLogRequest{Method: "POST", URL: "/a"}})
134162

@@ -142,6 +170,12 @@ func TestListCallLogSessions(t *testing.T) {
142170
if len(sessions[0].LogIDs) != 2 {
143171
t.Fatalf("expected 2 log ids, got %#v", sessions[0].LogIDs)
144172
}
173+
if sessions[0].TokenUsage == nil || sessions[0].TokenUsage.InputTokens != 15 || sessions[0].TokenUsage.OutputTokens != 5 || sessions[0].TokenUsage.TotalTokens != 20 {
174+
t.Fatalf("unexpected session token usage: %#v", sessions[0].TokenUsage)
175+
}
176+
if sessions[0].ToolCallCount != 3 {
177+
t.Fatalf("unexpected session tool call count: %d", sessions[0].ToolCallCount)
178+
}
145179
}
146180

147181
func TestClearCallLogDB(t *testing.T) {

0 commit comments

Comments
 (0)