diff options
Diffstat (limited to 'libpanto/src/null_store.zig')
| -rw-r--r-- | libpanto/src/null_store.zig | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/libpanto/src/null_store.zig b/libpanto/src/null_store.zig new file mode 100644 index 0000000..a9bd813 --- /dev/null +++ b/libpanto/src/null_store.zig @@ -0,0 +1,77 @@ +//! `NullStore`: a no-op `SessionStore` for embedders who opt out of +//! persistence (and the default backing for an `Agent` constructed without +//! an explicit store). +//! +//! Every append is dropped. `loadConversation` returns an empty +//! conversation with no dangling prompt. `activeModel` is null and the +//! session id is the empty string. The store is stateless, so a single +//! process-global instance backs every `NullStore` handle. + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const session_store_mod = @import("session_store.zig"); +const conversation_mod = @import("conversation.zig"); + +const SessionStore = session_store_mod.SessionStore; +const LoadedSession = session_store_mod.LoadedSession; +const DiskMessage = session_store_mod.DiskMessage; +const ActiveModel = session_store_mod.ActiveModel; + +/// Stateless singleton context. The vtable ignores `ctx` entirely; we hand +/// out a pointer to this so the `*anyopaque` is always valid. +var singleton: u8 = 0; + +fn appendMessagesVT( + _: *anyopaque, + _: []DiskMessage, + _: []const ?[]const u8, + _: []const ?[]const u8, +) anyerror!void {} + +fn loadConversationVT(_: *anyopaque, alloc: Allocator) anyerror!LoadedSession { + return .{ + .conversation = conversation_mod.Conversation.init(alloc), + .dangling_user = null, + }; +} + +fn sessionIdVT(_: *anyopaque) []const u8 { + return ""; +} + +fn activeModelVT(_: *anyopaque) ?ActiveModel { + return null; +} + +const vtable: SessionStore.VTable = .{ + .appendMessages = appendMessagesVT, + .loadConversation = loadConversationVT, + .sessionId = sessionIdVT, + .activeModel = activeModelVT, +}; + +/// A no-op `SessionStore`. Cheap to call repeatedly; all handles share one +/// stateless context. +pub fn store() SessionStore { + return .{ .ptr = &singleton, .vtable = &vtable }; +} + +const testing = std.testing; + +test "NullStore: appends are dropped and load returns empty" { + var msg_content = try testing.allocator.alloc(session_store_mod.DiskContentBlock, 1); + msg_content[0] = .{ .text = .{ .text = try testing.allocator.dupe(u8, "hi") } }; + var messages = [_]DiskMessage{.{ .role = .user, .content = msg_content }}; + defer messages[0].deinit(testing.allocator); + + const s = store(); + try s.appendMessages(&messages, &.{null}, &.{null}); + + var loaded = try s.loadConversation(testing.allocator); + defer loaded.deinit(testing.allocator); + try testing.expectEqual(@as(usize, 0), loaded.conversation.messages.items.len); + try testing.expect(loaded.dangling_user == null); + try testing.expect(s.activeModel() == null); + try testing.expectEqualStrings("", s.sessionId()); +} |
