diff options
| author | t <t@tjp.lol> | 2026-06-13 23:45:59 -0600 |
|---|---|---|
| committer | t <t@tjp.lol> | 2026-06-15 15:08:32 -0600 |
| commit | 02b4c7a35ac0b714bc045d54fb4bb45d1ce4e490 (patch) | |
| tree | 134196a82dd6fdd55b735be4c0cf38428c85038d /libpanto/src/conversation.zig | |
| parent | 71643a5d69ffc40882c9fcde3cc8a3bcf02d7396 (diff) | |
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.
Diffstat (limited to 'libpanto/src/conversation.zig')
| -rw-r--r-- | libpanto/src/conversation.zig | 60 |
1 files changed, 59 insertions, 1 deletions
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. |
