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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
# frozen_string_literal: true
module Dispatch
module Adapter
class Claude < Base
module Cloaking
CLAUDE_AGENT_INSTRUCTION =
"You are a Claude agent, built on Anthropic's Claude Agent SDK."
# Models that skip the Claude-Agent instruction block (per oh-my-pi).
SKIP_AGENT_INSTRUCTION_PATTERN = /claude-3-5-haiku/i
TOOL_PREFIX = "proxy_"
BUILTINS = %w[web_search code_execution text_editor computer].freeze
USER_ID_REGEX = /\A
user_[0-9a-fA-F]{64}
_account_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
_session_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
\z/x
module_function
# Generate the billing header string that gets prepended to the system
# block array on OAuth requests.
#
# @param payload [Hash, nil] the full request params hash
# @return [String] the billing header value
def billing_header(payload)
payload_json = JSON.generate(payload || {})
cch = Digest::SHA256.hexdigest(payload_json)[0, 5]
build_hash = SecureRandom.hex(2)[0, 3]
"x-anthropic-billing-header: cc_version=#{Headers::CLAUDE_CODE_VERSION}.#{build_hash}; cc_entrypoint=cli; cch=#{cch};"
end
# Prefix a tool name with "proxy_" unless it is a builtin or already prefixed.
#
# @param name [String]
# @return [String]
def apply_prefix(name)
return name if BUILTINS.include?(name.downcase)
return name if name.downcase.start_with?(TOOL_PREFIX)
"#{TOOL_PREFIX}#{name}"
end
# Strip the "proxy_" prefix from a tool name (inverse of apply_prefix).
#
# @param name [String]
# @return [String]
def strip_prefix(name)
return name unless name.downcase.start_with?(TOOL_PREFIX)
name.sub(/\A#{TOOL_PREFIX}/i, "")
end
# Returns true if the string matches the cloaking user-id format.
def cloaking_user_id?(str)
str.is_a?(String) && USER_ID_REGEX.match?(str)
end
# Generate a fresh cloaking user id.
def generate_cloaking_user_id
user = SecureRandom.hex(32)
account = SecureRandom.uuid.downcase
session = SecureRandom.uuid.downcase
"user_#{user}_account_#{account}_session_#{session}"
end
# Resolve the user_id for a request's metadata.
# In OAuth mode, only pass through an already-cloaked id; otherwise generate one.
# In API-key mode, pass through any String as-is; return nil for nil.
def resolve_user_id(provided, is_oauth)
return provided if provided.is_a?(String) && (!is_oauth || cloaking_user_id?(provided))
return nil unless is_oauth
generate_cloaking_user_id
end
# Build the system-block array for the request body.
#
# @param user_system [String, Array<Hash>, nil]
# @param is_oauth [Boolean]
# @param model_id [String]
# @param cache_control [Hash, nil] attached to the last user block
# @param billing_payload [Hash, nil] forwarded to billing_header
# @return [Array<Hash>, nil]
def build_system_blocks(user_system, is_oauth:, model_id:, cache_control: nil, billing_payload: nil)
# Normalise user_system into an Array<Hash>
user_blocks = normalise_system(user_system)
# Short-circuit: pre-existing billing header → forward as-is
return attach_cache_control(user_blocks, cache_control) if user_blocks.any? { |b| b["text"].to_s.start_with?("x-anthropic-billing-header:") }
# Non-OAuth: no cloaking
unless is_oauth
return nil if user_blocks.empty?
return attach_cache_control(user_blocks, cache_control)
end
# OAuth: inject billing + optional agent instruction
prefix_blocks = []
billing_text = billing_header(billing_payload)
prefix_blocks << text_block(billing_text)
prefix_blocks << text_block(CLAUDE_AGENT_INSTRUCTION) unless model_id.to_s.match?(SKIP_AGENT_INSTRUCTION_PATTERN)
all_blocks = prefix_blocks + user_blocks
attach_cache_control(all_blocks, cache_control)
end
# Normalise a String, Array of TextBlock/Hash, or nil into Array<Hash>
def normalise_system(user_system)
case user_system
when nil
[]
when String
user_system.empty? ? [] : [text_block(user_system)]
when Array
user_system.map do |block|
if block.respond_to?(:to_h)
h = block.to_h
# Convert symbol keys to string keys
h.transform_keys(&:to_s)
else
h = block.transform_keys(&:to_s)
h
end
end
else
[text_block(user_system.to_s)]
end
end
# Build a plain text block hash.
def text_block(text)
{ "type" => "text", "text" => text }
end
# Attach cache_control to the last block (if provided).
def attach_cache_control(blocks, cache_control)
return blocks if cache_control.nil? || blocks.empty?
result = blocks.map(&:dup)
result.last["cache_control"] = cache_control
result
end
end
end
end
end
|