summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.zig23
-rw-r--r--src/ping_tool.zig57
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 {}