summaryrefslogtreecommitdiff
path: root/src/public.zig
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-07-07 11:26:32 -0600
committert <t@tjp.lol>2026-07-07 11:26:45 -0600
commitf83578fdc9264019a1a1cef8c5484a161167d3dd (patch)
tree888f11767f944d61e5ca8eb92fa1b2dba295a4b8 /src/public.zig
initial commit, moved libpanto over from the pantograph repo
Diffstat (limited to 'src/public.zig')
-rw-r--r--src/public.zig299
1 files changed, 299 insertions, 0 deletions
diff --git a/src/public.zig b/src/public.zig
new file mode 100644
index 0000000..d8b9f7b
--- /dev/null
+++ b/src/public.zig
@@ -0,0 +1,299 @@
+//! `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.
+//!
+//! Every name here is a straight alias of an internal decl: the internal
+//! types have been shaped to *be* the public API (public methods + a
+//! transparent public field or two, every other field underscore-prefixed
+//! internal state), so this file selects the surface rather than wrapping
+//! it. Three buckets:
+//! - **behavioral** (`Agent`, `Stream`, `Conversation`): heap-pinned
+//! drivers whose `init` hands back a cheap, movable pointer; the chosen
+//! methods plus a transparent field (`Agent.conversation`,
+//! `Stream.state`) are the surface.
+//! - **data (read/output)** (`Event`, `Usage`, `Pricing`, `SessionInfo`):
+//! inspected field-by-field.
+//! - **data (constructed)** (`ContentBlock`, `Message`, the block types,
+//! the `Config` family): Zig users build them with `ArrayList` directly.
+
+const std = @import("std");
+
+const config_mod = @import("config.zig");
+const auth_mod = @import("auth.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();
+}
+
+/// Borrow the process-global HTTP client (asserts `init` has run). Embedders
+/// driving the OAuth auth flows need this to pass to `oauthLogin` etc.
+pub const httpClient = config_mod.httpClient;
+
+// ===========================================================================
+// 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 OpenAIResponsesConfig = config_mod.OpenAIResponsesConfig;
+pub const APIStyle = config_mod.APIStyle;
+pub const ReasoningEffort = config_mod.ReasoningEffort;
+pub const Thinking = config_mod.Thinking;
+pub const Effort = config_mod.Effort;
+pub const CompactionConfig = config_mod.CompactionConfig;
+pub const RetryConfig = config_mod.RetryConfig;
+pub const WireIdentity = config_mod.WireIdentity;
+pub const Header = config_mod.Header;
+
+// ===========================================================================
+// Auth (data + flows, aliased)
+// ===========================================================================
+
+pub const AuthConfig = auth_mod.AuthConfig;
+pub const AuthType = auth_mod.AuthType;
+pub const ApiKeyAuth = auth_mod.ApiKeyAuth;
+pub const OAuthDeviceAuth = auth_mod.OAuthDeviceAuth;
+pub const ExchangeConfig = auth_mod.ExchangeConfig;
+pub const DeviceDialect = auth_mod.DeviceDialect;
+pub const TokenRequestFormat = auth_mod.TokenRequestFormat;
+pub const TokenSet = auth_mod.TokenSet;
+pub const ParsedTokenSet = auth_mod.ParsedTokenSet;
+pub const ResolvedCredential = auth_mod.ResolvedCredential;
+
+/// Token persistence under an embedder-chosen auth directory. Files are
+/// written owner-only.
+pub const loadTokenSet = auth_mod.loadTokenSet;
+pub const saveTokenSet = auth_mod.saveTokenSet;
+pub const deleteTokenSet = auth_mod.deleteTokenSet;
+
+// OAuth device-flow mechanism: interactive login, refresh, exchange, and the
+// pure credential/JWT helpers the embedder's auth manager composes.
+pub const AuthError = auth_mod.AuthError;
+pub const Presenter = auth_mod.Presenter;
+pub const DeviceCodePrompt = auth_mod.DeviceCodePrompt;
+pub const OAuthTokens = auth_mod.OAuthTokens;
+pub const oauthLogin = auth_mod.login;
+pub const refreshTokens = auth_mod.refreshTokens;
+pub const runExchange = auth_mod.runExchange;
+pub const buildCredential = auth_mod.buildCredential;
+pub const tokensToTokenSet = auth_mod.tokensToTokenSet;
+pub const needsRefresh = auth_mod.needsRefresh;
+pub const needsExchange = auth_mod.needsExchange;
+pub const parseJwtExp = auth_mod.parseJwtExp;
+pub const extractAccountId = auth_mod.extractAccountId;
+
+/// Non-streaming HTTP helper over the process-global client, plus JSON
+/// path readers — the building blocks of the OAuth flows.
+pub const http = @import("http_helper.zig");
+
+// ===========================================================================
+// 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;
+/// Build a `TextualBlock` (the streaming text buffer used inside content
+/// blocks) from a slice, copying the bytes with `alloc`. The canonical way
+/// for binding code to construct `.Text`/`.Thinking`/tool-result text when
+/// assembling `ContentBlock`s by hand (e.g. for conversation resumption).
+pub const textualBlockFromSlice = conversation_mod.textualBlockFromSlice;
+pub const SignatureOrigin = conversation_mod.SignatureOrigin;
+/// Duplicate / free a `Message.identity`'s owned slices. Binding code uses
+/// these to attach a per-message wire identity when rebuilding a conversation
+/// for resumption (so it survives a later compaction).
+pub const dupeWireIdentity = conversation_mod.dupeWireIdentity;
+pub const freeWireIdentity = conversation_mod.freeWireIdentity;
+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, aliased)
+// ===========================================================================
+
+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 wrapper around `[]ResultPart` carrying
+/// the `fromText`/`fromTextOwned` constructors and `deinit`. Aliased
+/// straight through.
+pub const ResultParts = tool_mod.ResultParts;
+
+// ===========================================================================
+// Stream (behavioral, heap-pinned)
+// ===========================================================================
+
+pub const Event = stream_mod.Event;
+pub const ContentBlockType = provider_mod.ContentBlockType;
+pub const ProviderRetryInfo = provider_mod.ProviderRetryInfo;
+
+/// The pull event stream for one turn. Aliased straight through: its public
+/// surface is exactly `next`/`deinit` plus the transparent `state` field
+/// (`State`); every other field is underscore-prefixed internal state.
+/// `Agent.run` returns a `*Stream` (heap-pinned); the caller owns it and
+/// must `deinit` it (which persists the turn tail and frees the allocation).
+///
+/// `next()` returns `!?Event`: a value is progress (including the terminal
+/// event), `null` is exhaustion, an error is a genuine failure.
+pub const Stream = agent_mod.Stream;
+
+// ===========================================================================
+// Agent (behavioral, heap-pinned)
+// ===========================================================================
+
+pub const UserMessage = agent_mod.UserMessage;
+pub const CompactionResult = agent_mod.CompactionResult;
+
+/// The agent loop driver. Aliased straight through: its public surface is
+/// exactly the chosen methods (`init`/`deinit`, `registerTool`,
+/// `registerToolSource`, `setConfig`, `run`, `addSystemMessage`,
+/// `setSystemPrompt`, `compact`, `sessionId`) plus the transparent
+/// `conversation` field; every other field is underscore-prefixed internal
+/// state.
+///
+/// `init` heap-pins the agent and returns a `*Agent` — a cheap, movable
+/// handle out of the gate ("don't move the Agent" stops being a rule anyone
+/// can violate). The caller owns it and must `deinit` it. `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`.
+///
+/// Operate on the live conversation in place via the `conversation` field
+/// (valid for the agent's lifetime; do not retain past it).
+pub const Agent = agent_mod.Agent;
+
+// ===========================================================================
+// Pricing (data, aliased)
+// ===========================================================================
+
+pub const Pricing = pricing_mod.Pricing;
+pub const PricingRegistry = pricing_mod.Registry;
+/// Compute the cost of a single turn's `Usage` under the given `Pricing`,
+/// in micro-cents (1/1,000,000 of a cent per token). Returns null when any
+/// priced component of any nonzero category is null (the poison rule —
+/// don't pretend a turn with unknown cache pricing is free). Aliased
+/// through `libpanto` because the embedder accumulates session totals
+/// across potentially-switched models.
+pub const costMicroCents = pricing_mod.costMicroCents;
+/// Add a turn's micro-cents cost to a running session total. Either
+/// side null => result null. The single accumulation point for the
+/// per-turn session cost (see `pricing.zig` for why).
+pub const addCost = pricing_mod.addCost;
+
+// ===========================================================================
+// 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 openai_responses_json = @import("openai_responses_json.zig");
+const provider_openai_responses = @import("provider_openai_responses.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(auth_mod);
+ std.testing.refAllDecls(@import("http_helper.zig"));
+ 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);
+ std.testing.refAllDecls(openai_responses_json);
+ std.testing.refAllDecls(provider_openai_responses);
+}