#!/usr/bin/env ruby # GPT-5 Action-Oriented Agent - pure stdlib require "net/http" require "json" require "uri" require "reline" API_KEY = ENV["OPENAI_API_KEY"] or abort("Set OPENAI_API_KEY") MODEL = "gpt-5" # Tools GPT-5 can use TOOLS = [ { type: "function", function: { name: "read_file", description: "Read contents of a file", parameters: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } } }, { type: "function", function: { name: "write_file", description: "Write contents to a file", parameters: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path","content"] } } }, { type: "function", function: { name: "run_command", description: "Run a shell command and return output", parameters: { type: "object", properties: { command: { type: "string" } }, required: ["command"] } } } ] # Execute tools def run_tool(name, args) case name when "read_file" begin { content: File.read(args["path"]) } rescue => e { error: e.message } end when "write_file" begin File.write(args["path"], args["content"]) { status: "ok" } rescue => e { error: e.message } end when "run_command" begin { output: `#{args["command"]}`.strip } rescue => e { error: e.message } end else { error: "unknown tool #{name}" } end end # Call OpenAI def openai_chat(messages, tools) uri = URI("https://api.openai.com/v1/chat/completions") req = Net::HTTP::Post.new(uri) req["Authorization"] = "Bearer #{API_KEY}" req["Content-Type"] = "application/json" req.body = { model: MODEL, messages: messages, tools: tools, tool_choice: "auto" }.to_json res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) } raise "HTTP #{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess) JSON.parse(res.body) end # Conversation with bias-for-action messages = [ { role: "system", content: <<~MSG You are a reasoning coding and system agent. Prioritize taking practical actions whenever possible. Use read_file, write_file, and run_command proactively to achieve user goals. Explain your reasoning briefly in content. Avoid unnecessary delays; act whenever sufficient info is available. MSG } ] puts ">> Type instructions (or 'exit')" loop do input = Reline.readline("User> ", true)&.strip break if input.nil? || input.downcase == "exit" messages << { role: "user", content: input } loop do resp = openai_chat(messages, TOOLS) msg = resp.dig("choices", 0, "message") messages << msg # Print reasoning content immediately if msg["content"] && !msg["content"].empty? puts "\nAssistant> #{msg['content']}" end # Execute any tool calls if msg["tool_calls"] msg["tool_calls"].each do |call| name = call.dig("function","name") args = JSON.parse(call.dig("function","arguments")) result = run_tool(name, args) messages << { role: "tool", tool_call_id: call["id"], content: JSON.dump(result) } end next # continue reasoning on tool results end break # no tool calls left, turn is done end end