//! `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) // =========================================================================== 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; /// The conversation type. Aliased straight through: its interface has been /// pared down to exactly the public surface (constructors `init`/`deinit`, /// the `add*`/`replace*` builders, and the `messages`/`allocator` data /// fields), so no façade is needed. This is what `Session.load` returns, /// what `Agent.init` adopts, and what `Agent.conversation()` borrows a /// pointer to (for in-place surgery). Build one standalone with `init` for /// by-hand construction; once handed to an `Agent` it is owned by the agent. pub const Conversation = conversation_mod.Conversation; // =========================================================================== // Tools (data + small façade) // =========================================================================== 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, /// Construct an agent. The inner is heap-pinned at `init`, so the /// returned `Agent` is a cheap copyable handle ("don't move the Agent" /// stops being a rule anyone can violate). `session` is minted via /// `store.create()` (fresh) or `resolve`/`latest` (resume). When /// `maybe_conversation` is non-null it is adopted (ownership /// transferred) — the resume path hands the value returned by /// `Session.load`. pub fn init( allocator: std.mem.Allocator, io: std.Io, config: *const Config, session: Session, maybe_conversation: ?Conversation, ) !Agent { const inner = try allocator.create(agent_mod.Agent); inner.* = agent_mod.Agent.init(allocator, io, config, session, maybe_conversation); return .{ .inner = inner }; } pub fn deinit(self: Agent) void { const allocator = self.inner.allocator; self.inner.deinit(); allocator.destroy(self.inner); } 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 }) }; } /// Append a system message to the conversation and persist it (the /// `.append` `SystemMode`: it adds to the effective prompt). pub fn addSystemMessage(self: Agent, text: []const u8) !void { return self.inner.addSystemMessage(text, .append); } /// Replace the effective system prompt and persist it (the `.replace` /// `SystemMode`: it discards all prior system text). On replay the log /// reconstructs the same effective prompt. pub fn setSystemPrompt(self: Agent, text: []const u8) !void { return self.inner.addSystemMessage(text, .replace); } /// Compact and persist (the explicit `/compact` entry point). /// `override_system_prompt` falls back to /// `config.compaction.compaction_prompt` when null. `extra` is appended /// to the compaction prompt for this run (the `/compact $ARGUMENTS` /// path). pub fn compact(self: Agent, override_system_prompt: ?[]const u8, extra: ?[]const u8) !CompactionResult { return self.inner.compactAndPersist(override_system_prompt, extra); } /// A borrowed pointer to the agent's live conversation, for in-place /// context-management surgery. Valid for the agent's lifetime; do not /// retain past it. pub fn conversation(self: Agent) *Conversation { return &self.inner.conversation; } /// The id of the session this agent appends to. pub fn sessionId(self: Agent) []const u8 { return self.inner.session.info.id; } }; // =========================================================================== // Pricing (data, aliased) // =========================================================================== 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; // =========================================================================== // 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); }