//! `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()); }