#!/usr/bin/env ruby # frozen_string_literal: true # Sample app: ask the AI to tell you about the man "Standing Sitting". # # Usage: # bundle exec ruby examples/ask_standing_sitting.rb # # Authentication (auto-detected, in order): # 1. ANTHROPIC_API_KEY env var (raw API key) # 2. Cached OAuth token in ~/.config/dispatch/claude_oauth.json # 3. Interactive OAuth login (will open your browser) require "bundler/setup" require "dispatch/adapter/claude" PROMPT = "Tell me about the man Standing Sitting." # ── Pick credentials ───────────────────────────────────────────────────────── api_key = ENV.fetch("ANTHROPIC_API_KEY", nil) explicit_key_supplied = api_key && !api_key.strip.empty? # ── Pick a model ───────────────────────────────────────────────────────────── known_models = Dispatch::Adapter::Claude::PricingTable.known_ids.sort puts "\nAvailable models:" known_models.each_with_index do |id, idx| puts " #{(idx + 1).to_s.rjust(2)}) #{id}" end print "\nSelect a model (1-#{known_models.length}): " choice = $stdin.gets&.strip selected_index = begin Integer(choice, 10) - 1 rescue StandardError -1 end if selected_index.negative? || selected_index >= known_models.length warn "Invalid selection." exit 1 end model_id = known_models[selected_index] puts "→ Using #{model_id}\n\n" # ── Pick thinking level (configurable; default "high") ─────────────────────── puts "Thinking level (extended/adaptive thinking):" puts " 1) high (default — adaptive on 4.6+, enabled on older)" puts " 2) medium" puts " 3) low" puts " 4) off (no thinking)" print "Select [1-4, blank=1]: " think_choice = $stdin.gets&.strip thinking = case think_choice when "2" then "medium" when "3" then "low" when "4" then false else "high" # "", nil, "1", or unrecognised input end puts "→ thinking: #{thinking.inspect}\n\n" # ── Build the adapter ──────────────────────────────────────────────────────── adapter = Dispatch::Adapter::Claude.new( model: model_id, api_key: explicit_key_supplied ? api_key : nil, thinking: thinking ) # Trigger auth (cached token, refresh, or interactive OAuth login as needed). status = adapter.authenticate! puts "Auth: #{status}\n\n" # ── Send the request ───────────────────────────────────────────────────────── messages = [ Dispatch::Adapter::Message.new( role: "user", content: [Dispatch::Adapter::TextBlock.new(text: PROMPT)] ) ] puts "Q: #{PROMPT}" puts "A:" response = adapter.chat(messages, stream: false) response.content.each do |block| case block when Dispatch::Adapter::TextBlock puts block.text when Dispatch::Adapter::ThinkingBlock # Skip — internal reasoning, not the answer end end # ── Print usage / cost summary ─────────────────────────────────────────────── u = response.usage puts "\n--- usage ---" puts "input_tokens: #{u.input_tokens}" puts "output_tokens: #{u.output_tokens}" puts "cache_read: #{u.cache_read_tokens}" puts "cache_create: #{u.cache_creation_tokens}" puts format("cost (USD): $%.6f", u.cost.total) if u.cost puts "stop_reason: #{response.stop_reason}" # ── Print rate-limit header info (per-response real-time quota) ─────────────── rli = adapter.rate_limit_info if rli puts "\n--- rate-limit (from response headers) ---" puts "status: #{rli.status}" puts "representative window: #{rli.representative_claim}" puts "fallback available: #{rli.fallback}" rli.windows.each do |wid, win| pct = win.utilization ? format("%.2f%%", win.utilization * 100) : "?" remains = win.utilization ? format("%.2f%%", (1.0 - win.utilization) * 100) : "?" reset = win.reset_at ? " (resets #{win.reset_at.strftime("%Y-%m-%d %H:%M:%S %Z")})" : "" puts " #{wid.ljust(12)} used=#{pct} remaining=#{remains} status=#{win.status}#{reset}" end log_path = File.expand_path("~/.config/dispatch/claude_ratelimit.jsonl") puts "\n[Rate-limit log: #{log_path}]" else puts "\n(No unified rate-limit headers received.)" puts "\nDebug: ALL response headers from the last API call:" hdrs = adapter.last_response_headers if hdrs && !hdrs.empty? hdrs.sort.each { |k, v| puts " #{k}: #{v}" } else puts " (none captured)" end end