summaryrefslogtreecommitdiffhomepage
path: root/examples/ask_standing_sitting.rb
blob: c1877fe1bc9fdac3ce30b1d64ee2a596c740b289 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/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