const std = @import("std"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Parse -p flag const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); var prompt: ?[]const u8 = null; var i: usize = 1; while (i < args.len) : (i += 1) { if (std.mem.eql(u8, args[i], "-p") and i + 1 < args.len) { i += 1; prompt = args[i]; } } const prompt_str = prompt orelse @panic("Prompt must not be empty"); const api_key = std.posix.getenv("OPENROUTER_API_KEY") orelse @panic("OPENROUTER_API_KEY is not set"); const base_url = std.posix.getenv("OPENROUTER_BASE_URL") orelse "https://openrouter.ai/api/v1"; // Build request body var body_out: std.io.Writer.Allocating = .init(allocator); defer body_out.deinit(); var jw: std.json.Stringify = .{ .writer = &body_out.writer }; try jw.write(.{ .model = "anthropic/claude-haiku-4.5", .messages = &[_]struct { role: []const u8, content: []const u8 }{ .{ .role = "user", .content = prompt_str }, }, .tools = &[_]struct { type: []const u8, function: struct { name: []const u8, description: []const u8, parameters: struct { type: []const u8, properties: struct { file_path: struct { type: []const u8, description: []const u8, }, }, required: []const []const u8, }, }, }{ .{ .type = "function", .function = .{ .name = "Read", .description = "Read and return the contents of a file", .parameters = .{ .type = "object", .properties = .{ .file_path = .{ .type = "string", .description = "The path to the file to read", }, }, .required = &[_][]const u8{"file_path"}, }, }, }, }, }); const body = body_out.written(); // Build URL and auth header const url_str = try std.fmt.allocPrint(allocator, "{s}/chat/completions", .{base_url}); defer allocator.free(url_str); const auth_value = try std.fmt.allocPrint(allocator, "Bearer {s}", .{api_key}); defer allocator.free(auth_value); // Make HTTP request var client: std.http.Client = .{ .allocator = allocator }; defer client.deinit(); var response_out: std.io.Writer.Allocating = .init(allocator); defer response_out.deinit(); _ = try client.fetch(.{ .location = .{ .url = url_str }, .method = .POST, .payload = body, .extra_headers = &.{ .{ .name = "content-type", .value = "application/json" }, .{ .name = "authorization", .value = auth_value }, }, .response_writer = &response_out.writer, }); const response_body = response_out.written(); // Parse response const parsed = try std.json.parseFromSlice(std.json.Value, allocator, response_body, .{}); defer parsed.deinit(); const choices = parsed.value.object.get("choices") orelse @panic("No choices in response"); if (choices.array.items.len == 0) { @panic("No choices in response"); } std.debug.print("Logs from your program will appear here!\n", .{}); const message = choices.array.items[0].object.get("message").?; // Check for tool_calls in the response if (message.object.get("tool_calls")) |tool_calls_val| { if (tool_calls_val != .null and tool_calls_val.array.items.len > 0) { const tool_call = tool_calls_val.array.items[0]; const func = tool_call.object.get("function").?; const func_name = func.object.get("name").?.string; const arguments_str = func.object.get("arguments").?.string; if (std.mem.eql(u8, func_name, "Read")) { const args_parsed = try std.json.parseFromSlice(std.json.Value, allocator, arguments_str, .{}); defer args_parsed.deinit(); const file_path = args_parsed.value.object.get("file_path").?.string; std.debug.print("Reading file: {s}\n", .{file_path}); const file_contents = try std.fs.cwd().readFileAlloc(allocator, file_path, std.math.maxInt(usize)); defer allocator.free(file_contents); try std.fs.File.stdout().writeAll(file_contents); return; } } } // No tool call — print the text content directly const content = message.object.get("content").?.string; try std.fs.File.stdout().writeAll(content); }