From 27e37c62bc1483c0489d6db01344906472a1f8d3 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 11 Jun 2026 11:15:40 -0600 Subject: libpanto-go some libpanto-c coverage expansion was needed --- libpanto-c/src/lib.zig | 110 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) (limited to 'libpanto-c/src') diff --git a/libpanto-c/src/lib.zig b/libpanto-c/src/lib.zig index 046236a..81ce0ce 100644 --- a/libpanto-c/src/lib.zig +++ b/libpanto-c/src/lib.zig @@ -47,6 +47,7 @@ pub const PantoToolDetails = extern struct { index: usize, id: PantoSlice, name: pub const PantoContentDelta = extern struct { index: usize, delta: PantoSlice }; pub const PantoBlockComplete = extern struct { index: usize, block_type: PantoContentBlockTag }; pub const PantoMessageComplete = extern struct { has_usage: bool, usage: PantoUsage }; +pub const PantoCompactionResult = extern struct { compacted: bool, kept_turns: usize, summarized_messages: usize }; pub const PantoProviderRetry = extern struct { attempt: usize, max_attempts: usize, delay_ms: u64, error_name: PantoSlice, has_status_code: bool, status_code: u16, has_retry_after_ms: bool, retry_after_ms: u64, message: PantoSlice, compaction: bool }; pub const PantoToolDispatchStart = extern struct { count: usize }; pub const PantoToolDispatchComplete = extern struct { message_index: usize }; @@ -147,6 +148,98 @@ export fn panto_conversation_destroy(c: ?*PantoConversation) void { allocator.destroy(h); } } +fn unwrapConversation(c: *PantoConversation) *panto.Conversation { + return &(@as(*ConversationHandle, @ptrCast(@alignCast(c))).conversation); +} +fn msgAt(c: *PantoConversation, index: usize) ?*panto.Message { + const cc = unwrapConversation(c); + if (index >= cc.messages.items.len) return null; + return &cc.messages.items[index]; +} +fn blockAt(c: *PantoConversation, mi: usize, bi: usize) ?*panto.ContentBlock { + const m = msgAt(c, mi) orelse return null; + if (bi >= m.content.items.len) return null; + return &m.content.items[bi]; +} +export fn panto_conversation_message_count(c: *PantoConversation) usize { + return unwrapConversation(c).messages.items.len; +} +export fn panto_conversation_message_role(c: *PantoConversation, index: usize) PantoMessageRole { + const m = msgAt(c, index) orelse return .user; + return @enumFromInt(@intFromEnum(m.role)); +} +export fn panto_conversation_message_has_usage(c: *PantoConversation, index: usize) bool { + const m = msgAt(c, index) orelse return false; + return m.usage != null; +} +export fn panto_conversation_message_usage(c: *PantoConversation, index: usize) PantoUsage { + const m = msgAt(c, index) orelse return .{ .input = 0, .output = 0, .cache_read = 0, .cache_write = 0, .reasoning = 0 }; + return if (m.usage) |u| usage(u) else .{ .input = 0, .output = 0, .cache_read = 0, .cache_write = 0, .reasoning = 0 }; +} +export fn panto_conversation_message_block_count(c: *PantoConversation, index: usize) usize { + const m = msgAt(c, index) orelse return 0; + return m.content.items.len; +} +export fn panto_conversation_block_tag(c: *PantoConversation, mi: usize, bi: usize) PantoContentBlockTag { + const b = blockAt(c, mi, bi) orelse return .Text; + return @enumFromInt(@intFromEnum(b.*)); +} +export fn panto_conversation_block_text(c: *PantoConversation, mi: usize, bi: usize) PantoSlice { + const b = blockAt(c, mi, bi) orelse return .{ .ptr = null, .len = 0 }; + return switch (b.*) { + .Text => |t| outSlice(t.items), + .Thinking => |t| outSlice(t.text.items), + .ToolUse => |t| outSlice(t.input.items), + .ToolResult => |tr| blk: { + for (tr.parts.items) |p| if (p == .text) break :blk outSlice(p.text.items); + break :blk .{ .ptr = null, .len = 0 }; + }, + .System => |s| outSlice(s.text.items), + .CompactionSummary => |s| outSlice(s.text.items), + }; +} +export fn panto_conversation_block_tool_id(c: *PantoConversation, mi: usize, bi: usize) PantoSlice { + const b = blockAt(c, mi, bi) orelse return .{ .ptr = null, .len = 0 }; + return switch (b.*) { .ToolUse => |t| outSlice(t.id), .ToolResult => |t| outSlice(t.tool_use_id), else => .{ .ptr = null, .len = 0 } }; +} +export fn panto_conversation_block_tool_name(c: *PantoConversation, mi: usize, bi: usize) PantoSlice { + const b = blockAt(c, mi, bi) orelse return .{ .ptr = null, .len = 0 }; + return switch (b.*) { .ToolUse => |t| outSlice(t.name), else => .{ .ptr = null, .len = 0 } }; +} +export fn panto_conversation_block_tool_result_is_error(c: *PantoConversation, mi: usize, bi: usize) bool { + const b = blockAt(c, mi, bi) orelse return false; + return switch (b.*) { .ToolResult => |t| t.is_error, else => false }; +} +export fn panto_conversation_block_system_mode(c: *PantoConversation, mi: usize, bi: usize) PantoSystemMode { + const b = blockAt(c, mi, bi) orelse return .append; + return switch (b.*) { .System => |s| @enumFromInt(@intFromEnum(s.mode)), else => .append }; +} +export fn panto_conversation_add_system_message(c: *PantoConversation, text: ?[*:0]const u8) PantoStatus { + unwrapConversation(c).addSystemMessage(cstr(text)) catch |e| return setErr("{t}", .{e}); + return .ok; +} +export fn panto_conversation_replace_system_message(c: *PantoConversation, text: ?[*:0]const u8) PantoStatus { + unwrapConversation(c).replaceSystemMessage(cstr(text)) catch |e| return setErr("{t}", .{e}); + return .ok; +} +export fn panto_conversation_add_user_text(c: *PantoConversation, text: ?[*:0]const u8) PantoStatus { + const cc = unwrapConversation(c); + const tb = panto.textualBlockFromSlice(cc.allocator, cstr(text)) catch |e| return setErr("{t}", .{e}); + const b: panto.ContentBlock = .{ .Text = tb }; + cc.addUserMessage(&.{b}) catch |e| return setErr("{t}", .{e}); + return .ok; +} +export fn panto_conversation_add_assistant_text(c: *PantoConversation, text: ?[*:0]const u8, has_usage: bool, u: PantoUsage) PantoStatus { + const cc = unwrapConversation(c); + const tb = panto.textualBlockFromSlice(cc.allocator, cstr(text)) catch |e| return setErr("{t}", .{e}); + const b: panto.ContentBlock = .{ .Text = tb }; + cc.addAssistantMessage(&.{b}, if (has_usage) zigUsage(u) else null) catch |e| return setErr("{t}", .{e}); + return .ok; +} +export fn panto_conversation_add_compaction_summary(c: *PantoConversation, text: ?[*:0]const u8) PantoStatus { + unwrapConversation(c).addCompactionSummary(cstr(text)) catch |e| return setErr("{t}", .{e}); + return .ok; +} export fn panto_session_create_null(out: **PantoSession) PantoStatus { const h = allocator.create(SessionHandle) catch return setErr("OutOfMemory", .{}); var ns = panto.NullStore.init(allocator); @@ -382,7 +475,7 @@ fn builtinStoreAppend(ctx: *anyopaque, session_id: PantoSlice, messages: [*]cons const s: *panto.SessionStore = @ptrCast(@alignCast(ctx)); const pmsgs = allocator.alloc(panto.PersistentMessage, len) catch return setErr("OutOfMemory", .{}); defer allocator.free(pmsgs); - for (messages[0..len], 0..) |m, i| pmsgs[i] = .{ .message = .{ .role = @enumFromInt(@intFromEnum(m.role)), .content = &.{}, .metadata = if (m.has_metadata) slice(m.metadata) else null }, .usage = if (m.has_usage) m.usage else null }; + for (messages[0..len], 0..) |m, i| pmsgs[i] = .{ .message = .{ .role = @enumFromInt(@intFromEnum(m.role)), .content = .empty, .metadata = if (m.has_metadata) slice(m.metadata) else null }, .usage = if (m.has_usage) zigUsage(m.usage) else null, .identity = .{ .api_style = .openai_chat, .base_url = "", .model = "" } }; s.appendMessages(slice(session_id), pmsgs) catch |e| return setErr("{t}", .{e}); return .ok; } @@ -449,6 +542,10 @@ export fn panto_agent_session_id(a: *PantoAgent) PantoSlice { const h: *AgentHandle = @ptrCast(@alignCast(a)); return outSlice(h.agent.sessionId()); } +export fn panto_agent_conversation(a: *PantoAgent) *PantoConversation { + const h: *AgentHandle = @ptrCast(@alignCast(a)); + return @ptrCast(&h.agent.conversation); +} export fn panto_agent_set_config(a: *PantoAgent, c: *const PantoConfig) PantoStatus { const h: *AgentHandle = @ptrCast(@alignCast(a)); const cfg = convertConfig(c) catch |e| return setErr("{t}", .{e}); @@ -468,6 +565,14 @@ export fn panto_agent_set_system_prompt(a: *PantoAgent, text: ?[*:0]const u8) Pa h.agent.setSystemPrompt(cstr(text)) catch |e| return setErr("{t}", .{e}); return .ok; } +export fn panto_agent_compact(a: *PantoAgent, override_system_prompt: ?[*:0]const u8, extra_instructions: ?[*:0]const u8, out: *PantoCompactionResult) PantoStatus { + const h: *AgentHandle = @ptrCast(@alignCast(a)); + const osp = if (override_system_prompt == null or cstr(override_system_prompt).len == 0) null else cstr(override_system_prompt); + const extra = if (extra_instructions == null or cstr(extra_instructions).len == 0) null else cstr(extra_instructions); + const r = h.agent.compact(osp, extra) catch |e| return setErr("{t}", .{e}); + out.* = .{ .compacted = r.compacted, .kept_turns = r.kept_turns, .summarized_messages = r.summarized_messages }; + return .ok; +} export fn panto_agent_run(a: *PantoAgent, text: ?[*:0]const u8, out: **PantoStream) PantoStatus { const h: *AgentHandle = @ptrCast(@alignCast(a)); const st = h.agent.run(.{ .text = cstr(text) }) catch |e| return setErr("{t}", .{e}); @@ -525,6 +630,9 @@ fn marshalEvent(ev: panto.Event) !PantoEvent { fn usage(u: panto.Usage) PantoUsage { return .{ .input = u.input, .output = u.output, .cache_read = u.cache_read, .cache_write = u.cache_write, .reasoning = u.reasoning }; } +fn zigUsage(u: PantoUsage) panto.Usage { + return .{ .input = u.input, .output = u.output, .cache_read = u.cache_read, .cache_write = u.cache_write, .reasoning = u.reasoning }; +} test "c abi exports compile" { try std.testing.expect(true); -- cgit v1.3