diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.zig | 23 | ||||
| -rw-r--r-- | src/ping_tool.zig | 57 |
2 files changed, 74 insertions, 6 deletions
diff --git a/src/main.zig b/src/main.zig index 688b61d..20da910 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,6 @@ const std = @import("std"); const panto = @import("panto"); +const ping_tool = @import("ping_tool.zig"); const Receiver = panto.provider.Receiver; const ReceiverVTable = panto.provider.ReceiverVTable; @@ -38,10 +39,14 @@ const CLIReceiver = struct { meta: ?BlockMeta, ) anyerror!void { _ = index; - _ = meta; const self: *CLIReceiver = @ptrCast(@alignCast(ptr)); - if (block_type == .Thinking) { - try self.stdout.writeAll("\x1b[2m[thinking] "); + switch (block_type) { + .Thinking => try self.stdout.writeAll("\x1b[2m[thinking] "), + .ToolUse => { + const name = if (meta) |m| (m.tool_name orelse "?") else "?"; + try self.stdout.print("\n\x1b[36m[tool: {s}]\x1b[0m ", .{name}); + }, + else => {}, } try self.file.flush(); } @@ -60,8 +65,10 @@ const CLIReceiver = struct { ) anyerror!void { _ = index; const self: *CLIReceiver = @ptrCast(@alignCast(ptr)); - if (block == .Thinking) { - try self.stdout.writeAll("\x1b[0m\n"); + switch (block) { + .Thinking => try self.stdout.writeAll("\x1b[0m\n"), + .ToolUse => try self.stdout.writeAll("\n"), + else => {}, } try self.file.flush(); } @@ -175,9 +182,13 @@ pub fn main(init: std.process.Init) !void { try conv.addSystemMessage("You are a helpful assistant."); const prov = try panto.provider.Provider.init(alloc, io, config); - const agent = panto.agent.Agent.init(alloc, prov); + var agent = panto.agent.Agent.init(alloc, prov); defer agent.deinit(); + // smoke test: register a trivial built-in tool so we can exercise + // the tool-call loop against a real LLM. + try agent.registerTool(ping_tool.tool()); + const banner_model: []const u8 = switch (config) { inline else => |c| c.model, }; diff --git a/src/ping_tool.zig b/src/ping_tool.zig new file mode 100644 index 0000000..eb212af --- /dev/null +++ b/src/ping_tool.zig @@ -0,0 +1,57 @@ +//! A trivial built-in "ping" tool used to exercise the tool-call loop +//! end-to-end against a real LLM. It accepts a `host` string and always +//! returns the literal string "PONG", regardless of what the host is. +//! +//! In phase 5 this kind of built-in will be replaced by a proper extension; +//! for now it lives in the CLI so we can verify the libpanto tool plumbing +//! without writing the Lua bridge first. + +const std = @import("std"); +const panto = @import("panto"); + +const NAME = "ping"; +const DESCRIPTION = + \\Test whether a server is reachable by hostname. Always responds with + \\"PONG" when the server is up. Use this when the user asks you to check + \\if a host is online or reachable. +; +const SCHEMA_JSON = + \\{ + \\ "type": "object", + \\ "properties": { + \\ "host": { + \\ "type": "string", + \\ "description": "The hostname or address to ping." + \\ } + \\ }, + \\ "required": ["host"] + \\} +; + +/// Returns a `Tool` value ready to register with `Agent.registerTool`. The +/// tool holds no per-instance state: name, description, and schema are +/// `comptime` string literals, and the vtable's deinit is a no-op. We pass +/// a sentinel pointer as ctx since the contract requires *anyopaque but +/// nothing dereferences it. +pub fn tool() panto.Tool { + return .{ + .name = NAME, + .description = DESCRIPTION, + .schema_json = SCHEMA_JSON, + .ctx = &ctx_sentinel, + .vtable = &vtable, + }; +} + +var ctx_sentinel: u8 = 0; + +const vtable: panto.Tool.VTable = .{ + .invoke = invoke, + .deinit = deinitNoop, +}; + +fn invoke(_: *anyopaque, _: []const u8, allocator: std.mem.Allocator) anyerror![]u8 { + return try allocator.dupe(u8, "PONG"); +} + +fn deinitNoop(_: *anyopaque, _: std.mem.Allocator) void {} |
