diff options
| author | t <t@tjp.lol> | 2026-06-11 11:16:25 -0600 |
|---|---|---|
| committer | t <t@tjp.lol> | 2026-06-11 11:51:54 -0600 |
| commit | 6d725a9e35c3086f61cb6579d2840f5a09be450a (patch) | |
| tree | 646897da7374af871381ecbbebccd8b401cfa7ee /libpanto-c/src/lib.zig | |
| parent | 27e37c62bc1483c0489d6db01344906472a1f8d3 (diff) | |
expand libpanto-go and -c to the full libpanto API surface
Diffstat (limited to 'libpanto-c/src/lib.zig')
| -rw-r--r-- | libpanto-c/src/lib.zig | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/libpanto-c/src/lib.zig b/libpanto-c/src/lib.zig index 81ce0ce..9b55bee 100644 --- a/libpanto-c/src/lib.zig +++ b/libpanto-c/src/lib.zig @@ -18,6 +18,8 @@ const SessionHandle = struct { session: panto.Session, owns_info: bool = true }; const StoreHandle = struct { ctx: *anyopaque, vtable: *const PantoSessionStoreVTable }; const NullStoreHandle = struct { store: panto.NullStore }; const FileSystemJSONLStoreHandle = struct { store: panto.FileSystemJSONLStore }; +const ToolHandle = struct { ctx: *anyopaque, invoke: PantoToolInvokeFn, destroy: ?PantoToolDestroyFn, decl: panto.ToolDecl }; +const ToolSourceHandle = struct { ctx: *anyopaque, invoke_batch: PantoToolSourceInvokeBatchFn, destroy: ?PantoToolDestroyFn, name: []u8, decls: []panto.ToolDecl }; pub const PantoSlice = extern struct { ptr: ?[*]const u8, len: usize }; pub const PantoStatus = enum(c_int) { ok = 0, err = 1 }; @@ -63,6 +65,16 @@ pub const PantoSessionStoreVTable = extern struct { append_messages: *const fn (*anyopaque, PantoSlice, [*]const PantoPersistentMessage, usize) callconv(.c) PantoStatus, destroy: ?*const fn (*anyopaque) callconv(.c) void, }; +pub const PantoToolDecl = extern struct { name: PantoSlice, description: PantoSlice, schema_json: PantoSlice }; +pub const PantoMediaPart = extern struct { media_type: PantoSlice, has_media_type: bool, data: PantoSlice }; +pub const PantoResultPartTag = enum(c_int) { text = 0, media = 1 }; +pub const PantoResultPart = extern struct { tag: PantoResultPartTag, data: extern union { text: PantoSlice, media: PantoMediaPart } }; +pub const PantoResultParts = extern struct { ptr: ?[*]PantoResultPart, len: usize }; +pub const PantoToolCall = extern struct { tool_name: PantoSlice, input: PantoSlice }; +pub const PantoToolCallResult = extern struct { ok: bool, parts: PantoResultParts, err: PantoSlice }; +pub const PantoToolInvokeFn = *const fn (*anyopaque, PantoSlice, *PantoResultParts) callconv(.c) PantoStatus; +pub const PantoToolSourceInvokeBatchFn = *const fn (*anyopaque, [*]const PantoToolCall, [*]PantoToolCallResult, usize) callconv(.c) PantoStatus; +pub const PantoToolDestroyFn = *const fn (*anyopaque) callconv(.c) void; fn setErr(comptime fmt: []const u8, args: anytype) PantoStatus { setErrVoid(fmt, args); @@ -637,3 +649,93 @@ fn zigUsage(u: PantoUsage) panto.Usage { test "c abi exports compile" { try std.testing.expect(true); } + +fn cloneToolDecl(d: PantoToolDecl) !panto.ToolDecl { + return .{ .name = try dupSlice(d.name), .description = try dupSlice(d.description), .schema_json = try dupSlice(d.schema_json) }; +} +fn freeToolDecl(d: panto.ToolDecl) void { + allocator.free(d.name); + allocator.free(d.description); + allocator.free(d.schema_json); +} +fn toolInvoke(ctx: *anyopaque, input: []const u8, alloc: std.mem.Allocator) anyerror!panto.ResultParts { + _ = alloc; + const h: *ToolHandle = @ptrCast(@alignCast(ctx)); + var out: PantoResultParts = .{ .ptr = null, .len = 0 }; + if (h.invoke(h.ctx, outSlice(input), &out) != .ok) return error.ToolCallbackFailed; + return convertResultParts(out); +} +fn toolDeinit(ctx: *anyopaque, alloc: std.mem.Allocator) void { + _ = alloc; + const h: *ToolHandle = @ptrCast(@alignCast(ctx)); + if (h.destroy) |d| d(h.ctx); + freeToolDecl(h.decl); + allocator.destroy(h); +} +fn sourceInvokeBatch(ctx: *anyopaque, calls: []const panto.ToolCall, results: []panto.ToolCallResult, alloc: std.mem.Allocator) anyerror!void { + _ = alloc; + const h: *ToolSourceHandle = @ptrCast(@alignCast(ctx)); + const ccalls = try allocator.alloc(PantoToolCall, calls.len); + defer allocator.free(ccalls); + const cresults = try allocator.alloc(PantoToolCallResult, results.len); + defer allocator.free(cresults); + for (calls, 0..) |c, i| ccalls[i] = .{ .tool_name = outSlice(c.tool_name), .input = outSlice(c.input) }; + for (cresults) |*r| r.* = .{ .ok = false, .parts = .{ .ptr = null, .len = 0 }, .err = .{ .ptr = null, .len = 0 } }; + if (h.invoke_batch(h.ctx, ccalls.ptr, cresults.ptr, cresults.len) != .ok) return error.ToolSourceCallbackFailed; + for (cresults, 0..) |r, i| { + if (r.ok) results[i] = .{ .ok = try convertResultParts(r.parts) } else results[i] = .{ .err = error.ToolCallbackFailed }; + } +} +fn sourceDeinit(ctx: *anyopaque, alloc: std.mem.Allocator) void { + _ = alloc; + const h: *ToolSourceHandle = @ptrCast(@alignCast(ctx)); + if (h.destroy) |d| d(h.ctx); + allocator.free(h.name); + for (h.decls) |d| freeToolDecl(d); + allocator.free(h.decls); + allocator.destroy(h); +} +fn convertResultParts(parts: PantoResultParts) !panto.ResultParts { + const out = try allocator.alloc(panto.ResultPart, parts.len); + errdefer allocator.free(out); + if (parts.ptr) |p| { + for (p[0..parts.len], 0..) |part, i| { + out[i] = switch (part.tag) { + .text => .{ .text = try dupSlice(part.data.text) }, + .media => .{ .media = .{ .media_type = if (part.data.media.has_media_type) try dupSlice(part.data.media.media_type) else null, .data = try dupSlice(part.data.media.data) } }, + }; + } + } + return .{ .items = out }; +} +export fn panto_agent_register_tool(a: *PantoAgent, decl: PantoToolDecl, ctx: *anyopaque, invoke: PantoToolInvokeFn, destroy: ?PantoToolDestroyFn) PantoStatus { + const ah: *AgentHandle = @ptrCast(@alignCast(a)); + const h = allocator.create(ToolHandle) catch return setErr("OutOfMemory", .{}); + h.* = .{ .ctx = ctx, .invoke = invoke, .destroy = destroy, .decl = cloneToolDecl(decl) catch |e| { allocator.destroy(h); return setErr("{t}", .{e}); } }; + ah.agent.registerTool(.{ .decl = h.decl, .ctx = h, .vtable = &.{ .invoke = toolInvoke, .deinit = toolDeinit } }) catch |e| return setErr("{t}", .{e}); + return .ok; +} +export fn panto_agent_register_tool_source(a: *PantoAgent, name: PantoSlice, decls: [*]const PantoToolDecl, len: usize, ctx: *anyopaque, invoke_batch: PantoToolSourceInvokeBatchFn, destroy: ?PantoToolDestroyFn) PantoStatus { + const ah: *AgentHandle = @ptrCast(@alignCast(a)); + const h = allocator.create(ToolSourceHandle) catch return setErr("OutOfMemory", .{}); + h.name = dupSlice(name) catch |e| { allocator.destroy(h); return setErr("{t}", .{e}); }; + h.decls = allocator.alloc(panto.ToolDecl, len) catch |e| { allocator.free(h.name); allocator.destroy(h); return setErr("{t}", .{e}); }; + for (decls[0..len], 0..) |d, i| h.decls[i] = cloneToolDecl(d) catch |e| return setErr("{t}", .{e}); + h.ctx = ctx; h.invoke_batch = invoke_batch; h.destroy = destroy; + ah.agent.registerToolSource(.{ .name = h.name, .tools = h.decls, .ctx = h, .vtable = &.{ .invoke_batch = sourceInvokeBatch, .deinit = sourceDeinit } }) catch |e| return setErr("{t}", .{e}); + return .ok; +} +export fn panto_result_parts_text(text: ?[*:0]const u8) PantoResultParts { + const part = allocator.alloc(PantoResultPart, 1) catch return .{ .ptr = null, .len = 0 }; + part[0] = .{ .tag = .text, .data = .{ .text = dupOut(cstr(text)) catch .{ .ptr = null, .len = 0 } } }; + return .{ .ptr = part.ptr, .len = 1 }; +} +export fn panto_result_parts_destroy(parts: PantoResultParts) void { + if (parts.ptr) |p| { + for (p[0..parts.len]) |part| switch (part.tag) { + .text => freePS(part.data.text), + .media => { if (part.data.media.has_media_type) freePS(part.data.media.media_type); freePS(part.data.media.data); }, + }; + allocator.free(p[0..parts.len]); + } +} |
