From 02b4c7a35ac0b714bc045d54fb4bb45d1ce4e490 Mon Sep 17 00:00:00 2001 From: t Date: Sat, 13 Jun 2026 23:45:59 -0600 Subject: Add Codex Responses support and session debugging Teach provider config and auth resolution about the Codex Responses dialect, including user-facing `style = "openai_responses"` with `dialect = "codex"`. Serialize and parse Responses traffic with provider-specific reasoning replay, assistant phase metadata, and robust function-call assembly keyed by `output_index` so streamed tool inputs survive proxy quirks and empty terminal payloads. Also persist thinking origins and message metadata across sessions, add the Anthropic interleaved-thinking header switch, write per-session debug logs, and improve the TUI and scripts for inspecting tool output and session costs. --- libpanto/src/conversation.zig | 60 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) (limited to 'libpanto/src/conversation.zig') diff --git a/libpanto/src/conversation.zig b/libpanto/src/conversation.zig index 1942a0c..760f3ee 100644 --- a/libpanto/src/conversation.zig +++ b/libpanto/src/conversation.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const config = @import("config.zig"); /// A streaming text buffer used by content blocks. /// Thin alias over ArrayList(u8) — amortized O(1) appends, @@ -13,19 +14,60 @@ pub fn textualBlockFromSlice(alloc: Allocator, slice: []const u8) !TextualBlock return buf; } +/// Provenance for a replayable thinking signature. Scoped conservatively to +/// the exact provider endpoint + wire model that produced the enclosing +/// assistant message. +pub const SignatureOrigin = struct { + api_style: config.APIStyle, + base_url: []const u8, + model: []const u8, + + pub fn init( + alloc: Allocator, + api_style: config.APIStyle, + base_url: []const u8, + model: []const u8, + ) !SignatureOrigin { + const burl = try alloc.dupe(u8, base_url); + errdefer alloc.free(burl); + const mdl = try alloc.dupe(u8, model); + return .{ .api_style = api_style, .base_url = burl, .model = mdl }; + } + + pub fn deinit(self: *SignatureOrigin, alloc: Allocator) void { + alloc.free(self.base_url); + alloc.free(self.model); + } + + pub fn dupe(self: SignatureOrigin, alloc: Allocator) !SignatureOrigin { + return init(alloc, self.api_style, self.base_url, self.model); + } + + pub fn matches(self: SignatureOrigin, api_style: config.APIStyle, base_url: []const u8, model: []const u8) bool { + return self.api_style == api_style and + std.mem.eql(u8, self.base_url, base_url) and + std.mem.eql(u8, self.model, model); + } +}; + /// A reasoning/thinking block from the assistant. /// /// `text` is the streamed reasoning content. `signature` is the opaque /// integrity token Anthropic emits with extended-thinking responses and /// requires echoed back verbatim on follow-up turns. It is optional because -/// other providers (OpenAI-compatible APIs) do not produce it. +/// other providers (OpenAI-compatible APIs) do not produce it. When present, +/// `signature_origin` records the exact provider/style/model that emitted the +/// enclosing assistant message so serializers can decide whether that opaque +/// signature is safe to replay on a later turn. pub const ThinkingBlock = struct { text: TextualBlock = .empty, signature: ?[]const u8 = null, + signature_origin: ?SignatureOrigin = null, pub fn deinit(self: *ThinkingBlock, alloc: Allocator) void { self.text.deinit(alloc); if (self.signature) |sig| alloc.free(sig); + if (self.signature_origin) |*origin| origin.deinit(alloc); } }; @@ -222,6 +264,7 @@ pub const Message = struct { block.deinit(alloc); } self.content.deinit(alloc); + if (self.metadata) |m| alloc.free(m); } }; @@ -335,6 +378,21 @@ pub const Conversation = struct { } }; +pub fn setThinkingOrigins( + allocator: Allocator, + blocks: []ContentBlock, + api_style: config.APIStyle, + base_url: []const u8, + model: []const u8, +) !void { + for (blocks) |*block| { + if (block.* != .Thinking) continue; + const origin = try SignatureOrigin.init(allocator, api_style, base_url, model); + if (block.Thinking.signature_origin) |*old| old.deinit(allocator); + block.Thinking.signature_origin = origin; + } +} + /// Derive the effective ordered list of system-text blocks from a slice of /// messages. This is the single shared rule that governs both provider /// serialization and session rebuild. -- cgit v1.3