//! `public.zig` — the sole exported root of `libpanto`. //! //! This file is the **explicit public API allowlist**. Internal modules keep //! their `pub` declarations (needed for cross-file linking) but are never //! re-exported here; the public surface is what this file names, not //! "whatever happens to be `pub`." //! //! The surface is organized around two user jobs (see //! `docs/libpanto-cleanup.md`): //! 1. Run an agent loop — configure a provider, register tools, submit a //! turn, consume the pull event stream, persist. //! 2. Construct & operate on conversations — build a `Conversation` by //! hand, do context-management surgery. //! //! Three façade buckets: //! - **behavioral** (`Agent`, `Stream`, `Conversation`): wrap a pointer to //! a heap-pinned internal, exposing only the chosen methods. //! - **data (read/output)** (`Event`, `Usage`, `Pricing`, `SessionInfo`): //! aliased straight through; inspected field-by-field. //! - **data (constructed)** (`ContentBlock`, `Message`, the block types, //! the `Config` family): aliased straight through; Zig users build them //! with `ArrayList` directly. const std = @import("std"); const config_mod = @import("config.zig"); const conversation_mod = @import("conversation.zig"); const agent_mod = @import("agent.zig"); const stream_mod = @import("stream.zig"); const provider_mod = @import("provider.zig"); const tool_mod = @import("tool.zig"); const tool_source_mod = @import("tool_source.zig"); const pricing_mod = @import("pricing.zig"); const session_store_mod = @import("session_store.zig"); const fs_store_mod = @import("file_system_jsonl_store.zig"); const null_store_mod = @import("null_store.zig"); // =========================================================================== // Process lifecycle // =========================================================================== /// Initialize the process-global HTTP client. Call once before any turn. pub fn init(allocator: std.mem.Allocator, io: std.Io) void { config_mod.initHttp(allocator, io); } /// Tear down the process-global HTTP client. Call once at shutdown. pub fn deinit() void { config_mod.deinitHttp(); } // =========================================================================== // Config (data, aliased) // =========================================================================== pub const Config = config_mod.Config; pub const ProviderConfig = config_mod.ProviderConfig; pub const OpenAIChatConfig = config_mod.OpenAIChatConfig; pub const AnthropicMessagesConfig = config_mod.AnthropicMessagesConfig; pub const APIStyle = config_mod.APIStyle; pub const ReasoningEffort = config_mod.ReasoningEffort; pub const CompactionConfig = config_mod.CompactionConfig; pub const RetryConfig = config_mod.RetryConfig; pub const WireIdentity = config_mod.WireIdentity; // =========================================================================== // Conversation construction (data, aliased — Job 2) // =========================================================================== pub const ContentBlock = conversation_mod.ContentBlock; pub const Message = conversation_mod.Message; pub const MessageRole = conversation_mod.MessageRole; pub const TextualBlock = conversation_mod.TextualBlock; pub const ThinkingBlock = conversation_mod.ThinkingBlock; pub const ToolUseBlock = conversation_mod.ToolUseBlock; pub const ToolResultBlock = conversation_mod.ToolResultBlock; pub const ResultPartStored = conversation_mod.ResultPartStored; pub const StoredMediaPart = conversation_mod.StoredMediaPart; pub const SystemBlock = conversation_mod.SystemBlock; pub const SystemMode = conversation_mod.SystemMode; pub const CompactionSummaryBlock = conversation_mod.CompactionSummaryBlock; pub const Usage = conversation_mod.Usage; pub const effectiveSystemBlocks = conversation_mod.effectiveSystemBlocks; /// A borrowed handle onto a conversation owned by an `Agent`, or a /// standalone-owned conversation built by hand. Pointer-wrapping makes the /// "don't move the conversation" invariant structural. pub const Conversation = struct { inner: *conversation_mod.Conversation, /// Read the in-memory messages directly, for context-management surgery. pub fn messages(self: Conversation) []conversation_mod.Message { return self.inner.messages.items; } pub fn addUserMessage(self: Conversation, text: []const u8) !void { return self.inner.addUserMessage(text); } pub fn addAssistantMessage(self: Conversation, blocks: []const ContentBlock, usage: ?Usage) !void { if (usage) |u| { return self.inner.addAssistantMessageWithUsage(blocks, u); } return self.inner.addAssistantMessage(blocks); } pub fn addSystemMessage(self: Conversation, text: []const u8) !void { return self.inner.addSystemMessage(text); } pub fn replaceSystemMessage(self: Conversation, text: []const u8) !void { return self.inner.replaceSystemMessage(text); } pub fn addCompactionSummary(self: Conversation, text: []const u8) !void { return self.inner.addCompactionSummary(text); } }; // =========================================================================== // Tools (data + small façade — Job 1) // =========================================================================== pub const Tool = tool_mod.Tool; pub const ToolSource = tool_source_mod.ToolSource; pub const ToolDecl = tool_mod.ToolDecl; pub const ToolCall = tool_source_mod.Call; pub const ToolCallResult = tool_source_mod.CallResult; pub const MediaPart = tool_mod.MediaPart; pub const ResultPart = tool_mod.ResultPart; /// Result-part ergonomics: a thin struct wrapper around `[]ResultPart` /// (a bare alias can't carry methods). pub const ResultParts = struct { items: []ResultPart, pub fn fromText(alloc: std.mem.Allocator, text: []const u8) !ResultParts { return .{ .items = try tool_mod.textResult(alloc, text) }; } pub fn fromTextOwned(alloc: std.mem.Allocator, text: []u8) !ResultParts { return .{ .items = try tool_mod.ownedTextResult(alloc, text) }; } pub fn deinit(self: ResultParts, alloc: std.mem.Allocator) void { tool_mod.freeResultParts(alloc, self.items); } }; // =========================================================================== // Stream (behavioral, pointer-wrapped) // =========================================================================== pub const Event = stream_mod.Event; pub const ContentBlockType = provider_mod.ContentBlockType; pub const ProviderRetryInfo = provider_mod.ProviderRetryInfo; /// The transparent turn state machine, renamed from the internal `Phase`. pub const State = agent_mod.Stream.Phase; pub const Stream = struct { inner: *agent_mod.Stream, /// `!?Event`: a value is progress (including the terminal event), `null` /// is exhaustion, an error is a genuine failure. pub fn next(self: Stream) !?Event { return self.inner.next(); } pub fn deinit(self: Stream) void { // The internal `Stream.deinit` persists the turn tail and frees its // own heap allocation. self.inner.deinit(); } pub fn state(self: Stream) State { return self.inner.phase; } }; // =========================================================================== // Agent (behavioral, pointer-wrapped) // =========================================================================== pub const UserMessage = struct { text: []const u8 }; pub const CompactionResult = agent_mod.Agent.CompactionResult; pub const Agent = struct { inner: *agent_mod.Agent, pub fn registerTool(self: Agent, t: Tool) !void { return self.inner.registerTool(t); } pub fn registerToolSource(self: Agent, src: ToolSource) !void { return self.inner.registerToolSource(src); } pub fn setConfig(self: Agent, cfg: *const Config) void { self.inner.setConfig(cfg); } pub fn run(self: Agent, message: UserMessage) !Stream { return .{ .inner = try self.inner.run(.{ .text = message.text }) }; } /// Compact and persist (the explicit `/compact` entry point). pub fn compact(self: Agent, system_prompt: []const u8, extra: ?[]const u8) !CompactionResult { return self.inner.compactAndPersist(system_prompt, extra); } /// A borrowed handle onto the agent's live conversation. pub fn conversation(self: Agent) Conversation { return .{ .inner = &self.inner.conversation }; } }; // =========================================================================== // Pricing (data, aliased — Job 1) // =========================================================================== pub const Pricing = pricing_mod.Pricing; pub const PricingRegistry = pricing_mod.Registry; // =========================================================================== // Sessions // =========================================================================== pub const SessionStore = session_store_mod.SessionStore; pub const Session = session_store_mod.Session; pub const SessionInfo = session_store_mod.SessionInfo; pub const PersistentMessage = session_store_mod.PersistentMessage; pub const NullStore = null_store_mod.NullStore; pub const FileSystemJSONLStore = fs_store_mod.FileSystemJSONLStore; // =========================================================================== // Deep-embedder escape hatches. // // The `panto` CLI is a *deep* embedder: it drives the agent loop at a level // the curated `Agent`/`Conversation` façades deliberately do not expose // (system-prompt seeding through the agent, `compaction_system_prompt`, the // injectable `open_stream_fn` test seam, direct `conversation` field access, // raw standalone `Conversation` values, `compactAndPersist`, etc.). Rather // than inflate the curated surface to cover one in-tree embedder, these two // internal namespaces stay reachable for embedders who opt into the // unstable internal API. A C-ABI or language binding should use the curated // surface above, not these. // // FLAGGED (see docs/libpanto-cleanup.md): exposing `agent`/`conversation` // internals is a known gap between the curated façade and what a deep // embedder needs. Revisit if/when the façade grows the missing operations // (e.g. `Conversation` standalone construction, agent system-prompt and // compaction-prompt plumbing). pub const agent = agent_mod; pub const conversation = conversation_mod; // =========================================================================== // Tests // =========================================================================== const openai_chat_json = @import("openai_chat_json.zig"); const provider_openai_chat = @import("provider_openai_chat.zig"); const anthropic_messages_json = @import("anthropic_messages_json.zig"); const provider_anthropic_messages = @import("provider_anthropic_messages.zig"); const compaction_mod = @import("compaction.zig"); const sse_mod = @import("sse.zig"); const tool_registry_mod = @import("tool_registry.zig"); const session_mod = @import("session.zig"); const turn_persist_mod = @import("turn_persist.zig"); const image_mod = @import("image.zig"); test { // See the note in the old root.zig: gate test logs at `.err` so expected // warning-path tests stay quiet. std.testing.log_level = .err; std.testing.refAllDecls(@This()); // Internal modules' tests (not part of the public surface, but their // tests must still run). std.testing.refAllDecls(config_mod); std.testing.refAllDecls(conversation_mod); std.testing.refAllDecls(agent_mod); std.testing.refAllDecls(stream_mod); std.testing.refAllDecls(provider_mod); std.testing.refAllDecls(tool_mod); std.testing.refAllDecls(tool_source_mod); std.testing.refAllDecls(tool_registry_mod); std.testing.refAllDecls(pricing_mod); std.testing.refAllDecls(session_store_mod); std.testing.refAllDecls(fs_store_mod); std.testing.refAllDecls(null_store_mod); std.testing.refAllDecls(session_mod); std.testing.refAllDecls(turn_persist_mod); std.testing.refAllDecls(compaction_mod); std.testing.refAllDecls(sse_mod); std.testing.refAllDecls(image_mod); std.testing.refAllDecls(openai_chat_json); std.testing.refAllDecls(provider_openai_chat); std.testing.refAllDecls(anthropic_messages_json); std.testing.refAllDecls(provider_anthropic_messages); }