From 9c64a7d4462a11674e2dea481b037b5f5d9c62fc Mon Sep 17 00:00:00 2001 From: t Date: Tue, 2 Jun 2026 09:02:49 -0600 Subject: system prompt building and logging --- libpanto/src/session.zig | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) (limited to 'libpanto/src/session.zig') diff --git a/libpanto/src/session.zig b/libpanto/src/session.zig index e77d121..c1fd1c4 100644 --- a/libpanto/src/session.zig +++ b/libpanto/src/session.zig @@ -100,9 +100,18 @@ pub const MessageEntry = struct { pub const DiskMessageRole = enum { system, user, assistant }; +/// Mode for a system-role message. Mirrors `conversation.SystemMode`. +/// `append` adds to the effective prompt; `replace` discards all prior +/// system text. Only meaningful on system messages; absent on disk means +/// `append` (back-compatible with pre-mode logs). +pub const DiskSystemMode = enum { append, replace }; + pub const DiskMessage = struct { role: DiskMessageRole, content: []DiskContentBlock, // owned + /// System-message mode. Recorded only for system-role messages; an + /// absent `mode` on disk parses back as `.append`. + mode: DiskSystemMode = .append, // Assistant-only metadata. Null for system/user messages. provider: ?[]const u8 = null, // owned model: ?[]const u8 = null, // owned @@ -296,6 +305,12 @@ fn writeDiskMessage(s: *std.json.Stringify, msg: DiskMessage) !void { try s.beginObject(); try s.objectField("role"); try s.write(@tagName(msg.role)); + // `mode` is meaningful only for system messages. Emit it there so the + // append/replace semantics round-trip; omit it everywhere else. + if (msg.role == .system) { + try s.objectField("mode"); + try s.write(@tagName(msg.mode)); + } try s.objectField("content"); try s.beginArray(); for (msg.content) |block| { @@ -479,6 +494,14 @@ fn parseDiskMessage(allocator: Allocator, obj: std.json.ObjectMap) ParseError!Di if (role_v != .string) return error.MissingField; const role = std.meta.stringToEnum(DiskMessageRole, role_v.string) orelse return error.UnknownRole; + // `mode` is optional; absent defaults to `.append`. Unknown values are + // tolerated as `.append` rather than rejecting an otherwise-valid log. + const mode: DiskSystemMode = blk: { + const mv = obj.get("mode") orelse break :blk .append; + if (mv != .string) break :blk .append; + break :blk std.meta.stringToEnum(DiskSystemMode, mv.string) orelse .append; + }; + const content_v = obj.get("content") orelse return error.MissingField; if (content_v != .array) return error.MissingField; var content_list = try std.ArrayList(DiskContentBlock).initCapacity(allocator, content_v.array.items.len); @@ -520,6 +543,7 @@ fn parseDiskMessage(allocator: Allocator, obj: std.json.ObjectMap) ParseError!Di return .{ .role = role, .content = content, + .mode = mode, .provider = provider, .model = model, .stop_reason = stop_reason, @@ -612,6 +636,13 @@ pub fn contentBlockToDisk( const content = try allocator.dupe(u8, tr.content.items); return .{ .tool_result = .{ .tool_use_id = tuid, .content = content } }; }, + // A `.System` block becomes a disk text block; its mode rides on + // the enclosing `DiskMessage.mode` (set by the session manager), + // not on the block itself. + .System => |sb| { + const text = try allocator.dupe(u8, sb.text.items); + return .{ .text = .{ .text = text } }; + }, } } @@ -808,6 +839,47 @@ test "serialize/parse tool result message entry" { try testing.expectEqualStrings("anthropic", got.provider.?); } +test "system message mode round-trips; absent mode defaults to append" { + const a = testing.allocator; + + // replace-mode system entry round-trips. + { + var content = try a.alloc(DiskContentBlock, 1); + content[0] = .{ .text = .{ .text = try dupe(a, "fresh seed") } }; + const entry: SessionEntry = .{ .message = .{ + .base = .{ + .id = try dupe(a, "aabbccdd"), + .parent_id = null, + .timestamp = try dupe(a, "2026-04-25T17:40:00Z"), + }, + .message = .{ + .role = .system, + .content = content, + .mode = .replace, + }, + } }; + defer entry.deinit(a); + + const line = try serializeEntry(a, entry); + defer a.free(line); + try testing.expect(std.mem.indexOf(u8, line, "\"mode\":\"replace\"") != null); + + var fe = try parseLine(a, line); + defer fe.deinit(a); + try testing.expectEqual(DiskSystemMode.replace, fe.entry.message.message.mode); + } + + // A legacy system entry with no `mode` parses back as append. + { + const line = + \\{"type":"message","id":"abcdefab","parentId":null,"timestamp":"2026-04-25T17:40:00Z","message":{"role":"system","content":[{"type":"text","text":"hi"}]}} + ; + var fe = try parseLine(a, line); + defer fe.deinit(a); + try testing.expectEqual(DiskSystemMode.append, fe.entry.message.message.mode); + } +} + test "parse: null parentId is handled" { const a = testing.allocator; const line = -- cgit v1.3