summaryrefslogtreecommitdiff
path: root/libpanto-c/src/lib.zig
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto-c/src/lib.zig')
-rw-r--r--libpanto-c/src/lib.zig110
1 files changed, 109 insertions, 1 deletions
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);