summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-09-08 22:40:09 -0600
committermo khan <mo@mokhan.ca>2025-09-08 22:40:09 -0600
commitf1bd9fc8bd9988beb000a58ef3e01081271e9e8a (patch)
tree5e1157822a34cde5a258ebe6ba8d4ced00217dc9
initial commit
-rw-r--r--main.rb138
1 files changed, 138 insertions, 0 deletions
diff --git a/main.rb b/main.rb
new file mode 100644
index 0000000..3988c8f
--- /dev/null
+++ b/main.rb
@@ -0,0 +1,138 @@
+#!/usr/bin/env ruby
+# GPT-5 Action-Oriented Agent - pure stdlib
+
+require "net/http"
+require "json"
+require "uri"
+
+API_KEY = ENV["OPENAI_API_KEY"] or abort("Set OPENAI_API_KEY")
+MODEL = "gpt-5-mini"
+
+# 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
+ print "\nUser> "
+ input = STDIN.gets&.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