Skip to content

Commit a1b9c7f

Browse files
committed
Logging
1 parent 838288c commit a1b9c7f

1 file changed

Lines changed: 90 additions & 6 deletions

File tree

src/provider/claude.zig

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -306,15 +306,99 @@ fn deinitFn(ptr: *anyopaque) void {
306306
self.deinit();
307307
}
308308

309+
const ServerToolUse = struct {
310+
web_search_requests: ?u64 = null,
311+
web_fetch_requests: ?u64 = null,
312+
};
313+
314+
const Usage = struct {
315+
input_tokens: ?u64 = null,
316+
output_tokens: ?u64 = null,
317+
cache_read_input_tokens: ?u64 = null,
318+
cache_creation_input_tokens: ?u64 = null,
319+
server_tool_use: ?ServerToolUse = null,
320+
};
321+
322+
const ModelUsageEntry = struct {
323+
inputTokens: ?u64 = null,
324+
outputTokens: ?u64 = null,
325+
cacheReadInputTokens: ?u64 = null,
326+
contextWindow: ?u64 = null,
327+
maxOutputTokens: ?u64 = null,
328+
};
329+
330+
const ClaudeJson = struct {
331+
result: ?[]const u8 = null,
332+
is_error: bool = false,
333+
session_id: ?[]const u8 = null,
334+
duration_ms: ?u64 = null,
335+
duration_api_ms: ?u64 = null,
336+
num_turns: ?u64 = null,
337+
total_cost_usd: ?f64 = null,
338+
usage: ?Usage = null,
339+
modelUsage: ?std.json.Value = null,
340+
};
341+
309342
fn parseClaudeResponse(allocator: std.mem.Allocator, line: []const u8) ?[]const u8 {
310-
const parsed = std.json.parseFromSlice(struct {
311-
result: ?[]const u8 = null,
312-
is_error: bool = false,
313-
}, allocator, line, .{ .ignore_unknown_fields = true }) catch return null;
343+
const parsed = std.json.parseFromSlice(ClaudeJson, allocator, line, .{ .ignore_unknown_fields = true }) catch return null;
314344
defer parsed.deinit();
315345

316-
if (parsed.value.is_error) return null;
317-
const result = parsed.value.result orelse return null;
346+
const v = parsed.value;
347+
348+
if (v.is_error) return null;
349+
350+
// Log metadata
351+
if (v.total_cost_usd) |_| {
352+
const usage = v.usage orelse Usage{};
353+
const in = usage.input_tokens orelse 0;
354+
const out = usage.output_tokens orelse 0;
355+
const cache_read = usage.cache_read_input_tokens orelse 0;
356+
const cache_create = usage.cache_creation_input_tokens orelse 0;
357+
const total_tokens = in + out + cache_read + cache_create;
358+
359+
// Extract context window and model name from modelUsage (first entry)
360+
var context_window: u64 = 0;
361+
var model_name: []const u8 = "unknown";
362+
if (v.modelUsage) |mu| {
363+
if (mu == .object) {
364+
var it = mu.object.iterator();
365+
if (it.next()) |entry| {
366+
model_name = entry.key_ptr.*;
367+
const model_parsed = std.json.parseFromValue(ModelUsageEntry, allocator, entry.value_ptr.*, .{ .ignore_unknown_fields = true }) catch null;
368+
if (model_parsed) |mp| {
369+
context_window = mp.value.contextWindow orelse 0;
370+
}
371+
}
372+
}
373+
}
374+
375+
const web_searches = if (usage.server_tool_use) |stu| stu.web_search_requests orelse 0 else 0;
376+
377+
if (context_window > 0) {
378+
const total_k = total_tokens / 1000;
379+
const ctx_k = context_window / 1000;
380+
const pct = (total_tokens * 100) / context_window;
381+
std.log.info("claude: {s} | {d}ms | turns={d} | ctx={d}k/{d}k ({d}%) | web_searches={d}", .{
382+
model_name,
383+
v.duration_ms orelse 0,
384+
v.num_turns orelse 0,
385+
total_k,
386+
ctx_k,
387+
pct,
388+
web_searches,
389+
});
390+
} else {
391+
std.log.info("claude: {s} | {d}ms | turns={d} | tokens={d} | web_searches={d}", .{
392+
model_name,
393+
v.duration_ms orelse 0,
394+
v.num_turns orelse 0,
395+
total_tokens,
396+
web_searches,
397+
});
398+
}
399+
}
400+
401+
const result = v.result orelse return null;
318402
const trimmed = std.mem.trim(u8, result, &std.ascii.whitespace);
319403
if (trimmed.len == 0) return null;
320404

0 commit comments

Comments
 (0)