const std = @import("std"); const conversation = @import("conversation.zig"); const config_mod = @import("config.zig"); pub const ContentBlockType = enum { Text, Thinking, ToolUse, ToolResult, }; pub const BlockMeta = struct { /// Only populated for ToolUse blocks. Null for Text/Thinking. tool_id: ?[]const u8 = null, tool_name: ?[]const u8 = null, }; /// Vtable for receiving streaming events from a Provider. /// /// The lifecycle callbacks (`onMessageStart` ... `onMessageComplete`) return /// `anyerror!void`. Returning an error aborts the in-flight turn: the Provider /// stops streaming, calls `onError(err)`, and propagates `err` out of /// `streamStep`. No partial assistant message is appended to the conversation. /// /// `onError` is the receiver's cleanup hook. It fires exactly once per failed /// turn, whether the error originated in the receiver itself (a write failure) /// or in the Provider (HTTP/parse/stream failure). It is the last callback the /// receiver will see for that turn. `onError` itself cannot fail; receivers /// must swallow secondary failures during cleanup. pub const ReceiverVTable = struct { onMessageStart: *const fn (*anyopaque, conversation.MessageRole) anyerror!void, onBlockStart: *const fn (*anyopaque, ContentBlockType, usize, ?BlockMeta) anyerror!void, onContentDelta: *const fn (*anyopaque, usize, []const u8) anyerror!void, onBlockComplete: *const fn (*anyopaque, usize, conversation.ContentBlock) anyerror!void, onMessageComplete: *const fn (*anyopaque, conversation.Message) anyerror!void, onError: *const fn (*anyopaque, anyerror) void, }; pub const Receiver = struct { ptr: *anyopaque, vtable: *const ReceiverVTable, pub fn onMessageStart(self: Receiver, role: conversation.MessageRole) !void { try self.vtable.onMessageStart(self.ptr, role); } pub fn onBlockStart(self: Receiver, block_type: ContentBlockType, index: usize, meta: ?BlockMeta) !void { try self.vtable.onBlockStart(self.ptr, block_type, index, meta); } pub fn onContentDelta(self: Receiver, block_index: usize, delta: []const u8) !void { try self.vtable.onContentDelta(self.ptr, block_index, delta); } pub fn onBlockComplete(self: Receiver, block_index: usize, block: conversation.ContentBlock) !void { try self.vtable.onBlockComplete(self.ptr, block_index, block); } pub fn onMessageComplete(self: Receiver, message: conversation.Message) !void { try self.vtable.onMessageComplete(self.ptr, message); } pub fn onError(self: Receiver, err: anyerror) void { self.vtable.onError(self.ptr, err); } }; pub const ProviderVTable = struct { streamStep: *const fn (*anyopaque, *conversation.Conversation, *Receiver) anyerror!void, deinit: *const fn (*anyopaque) void, }; pub const Provider = struct { ptr: *anyopaque, vtable: *const ProviderVTable, /// Construct the concrete provider implementation that matches `cfg`'s /// API style, heap-allocate it, and return a `Provider` interface bound /// to it. `deinit` tears down the impl and frees the allocation. /// /// The concrete provider types (`OpenAIChatProvider`, etc.) are /// implementation details; callers interact only with this interface. pub fn init( allocator: std.mem.Allocator, io: std.Io, cfg: config_mod.Config, ) !Provider { // Imported lazily to break the circular module graph: // provider.zig <- provider_openai_chat.zig <- provider.zig. const provider_openai_chat = @import("provider_openai_chat.zig"); const provider_anthropic_messages = @import("provider_anthropic_messages.zig"); switch (cfg) { .openai_chat => |c| { const impl = try allocator.create(provider_openai_chat.OpenAIChatProvider); impl.* = provider_openai_chat.OpenAIChatProvider.init(allocator, io, c); return impl.provider(); }, .anthropic_messages => |c| { const impl = try allocator.create(provider_anthropic_messages.AnthropicMessagesProvider); impl.* = provider_anthropic_messages.AnthropicMessagesProvider.init(allocator, io, c); return impl.provider(); }, } } pub fn streamStep(self: Provider, conv: *conversation.Conversation, receiver: *Receiver) anyerror!void { return self.vtable.streamStep(self.ptr, conv, receiver); } pub fn deinit(self: Provider) void { self.vtable.deinit(self.ptr); } };