opencode-anthropic-fix 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,326 @@
1
+ # Detailed Mimicry of HTTP Headers and System Prompt
2
+
3
+ This document explains, at implementation level, how the plugin mimics Claude Code signature behavior for Anthropic requests, with focus on:
4
+
5
+ - HTTP header composition
6
+ - `system` composition in the request body
7
+ - related auxiliary fields (`metadata`, `betas`, URL shaping, and toggles)
8
+
9
+ Primary code references:
10
+
11
+ - `index.mjs`
12
+ - `lib/config.mjs`
13
+
14
+ ## 1) Control switch (on/off)
15
+
16
+ Mimicry is controlled by `signature_emulation`:
17
+
18
+ ```jsonc
19
+ {
20
+ "signature_emulation": {
21
+ "enabled": true,
22
+ "fetch_claude_code_version_on_startup": true,
23
+ "prompt_compaction": "minimal",
24
+ },
25
+ }
26
+ ```
27
+
28
+ Environment overrides (in `lib/config.mjs`):
29
+
30
+ - `OPENCODE_ANTHROPIC_EMULATE_CLAUDE_CODE_SIGNATURE`
31
+ - `1/true` => enabled
32
+ - `0/false` => disabled
33
+ - `OPENCODE_ANTHROPIC_FETCH_CLAUDE_CODE_VERSION`
34
+ - `1/true` => fetch latest `@anthropic-ai/claude-code` version on startup
35
+ - `0/false` => keep internal fallback version
36
+ - `OPENCODE_ANTHROPIC_PROMPT_COMPACTION`
37
+ - `minimal` => compact long system instruction blocks (default)
38
+ - `off` => disable compaction
39
+
40
+ When `signature_emulation.enabled=false`, the plugin falls back to legacy system-prompt transform behavior (Claude Code prefix via `experimental.chat.system.transform`) and does not apply the full header/system mimicry block documented below.
41
+
42
+ ## 2) Claude CLI version used in signature behavior
43
+
44
+ In `AnthropicAuthPlugin`:
45
+
46
+ - initial fallback version: `2.1.2`
47
+ - if `fetch_claude_code_version_on_startup=true`, it performs GET on:
48
+ - `https://registry.npmjs.org/@anthropic-ai/claude-code/latest`
49
+ - short timeout (AbortController); failures are silent and fallback remains active
50
+
51
+ This version is used by:
52
+
53
+ - `user-agent`
54
+ - `x-stainless-package-version`
55
+ - `x-anthropic-billing-header` hash generation in `system`
56
+
57
+ ## 3) Request flow where mimicry is applied
58
+
59
+ Inside `auth.loader().fetch(...)`:
60
+
61
+ 1. transform URL (`transformRequestUrl`)
62
+ 2. select account and resolve token (including refresh when needed)
63
+ 3. transform body (`transformRequestBody`) with runtime context
64
+ 4. build headers (`buildRequestHeaders`)
65
+ 5. execute `fetch`
66
+
67
+ Important: body transform happens per-attempt/per-account (not only once), so `metadata.user_id` includes the actual `accountId` in use for that attempt.
68
+
69
+ ### 3.1 Protocol sequence diagram (Mermaid)
70
+
71
+ ```mermaid
72
+ sequenceDiagram
73
+ autonumber
74
+ participant Client as OpenCode Runtime
75
+ participant Plugin as AnthropicAuthPlugin
76
+ participant Account as AccountManager
77
+ participant OAuth as OAuth Token Layer
78
+ participant API as Anthropic API
79
+
80
+ Client->>Plugin: fetch(input, init)
81
+ Plugin->>Plugin: transformRequestUrl(input)
82
+
83
+ loop per attempt / account
84
+ Plugin->>Account: getCurrentAccount(model)
85
+ Account-->>Plugin: selected account
86
+
87
+ Plugin->>OAuth: resolve access token (refresh if needed)
88
+ OAuth-->>Plugin: bearer token
89
+
90
+ Plugin->>Plugin: transformRequestBody(body, signature, runtime)
91
+ Note over Plugin: inject metadata.user_id + system blocks
92
+
93
+ Plugin->>Plugin: buildRequestHeaders(...)
94
+ Note over Plugin: compose anthropic-beta (includes oauth-2025-04-20)
95
+
96
+ Plugin->>Plugin: syncBodyBetasFromHeader(body, headers)
97
+ Plugin->>API: fetch(url, {headers, body})
98
+ API-->>Plugin: response
99
+ end
100
+
101
+ Plugin-->>Client: final response
102
+ ```
103
+
104
+ ## 4) HTTP header mimicry
105
+
106
+ ### 4.1 Headers always applied
107
+
108
+ `buildRequestHeaders(...)` always ensures:
109
+
110
+ - `authorization: Bearer <token>`
111
+ - default token: account OAuth access token
112
+ - optional override: `ANTHROPIC_AUTH_TOKEN` (if set, takes precedence)
113
+ - `anthropic-beta: <final beta list>`
114
+ - `user-agent: claude-cli/<version> (external, <entrypoint>[, agent-sdk/<v>][, client-app/<app>])`
115
+ - `entrypoint`: `CLAUDE_CODE_ENTRYPOINT` or `cli`
116
+ - optional suffixes:
117
+ - `CLAUDE_AGENT_SDK_VERSION`
118
+ - `CLAUDE_AGENT_SDK_CLIENT_APP`
119
+ - always removes `x-api-key`
120
+
121
+ ### 4.2 Extra headers when mimicry is enabled
122
+
123
+ With `signature.enabled=true`, it adds:
124
+
125
+ - `anthropic-version: 2023-06-01`
126
+ - `anthropic-dangerous-direct-browser-access: true`
127
+ - `x-app: cli`
128
+ - `x-stainless-arch: <x64|arm64|...>`
129
+ - `x-stainless-lang: js`
130
+ - `x-stainless-os: <MacOS|Windows|Linux|...>`
131
+ - `x-stainless-package-version: <claudeCliVersion>`
132
+ - `x-stainless-runtime: node`
133
+ - `x-stainless-runtime-version: <process.version>`
134
+ - `x-stainless-helper-method: stream`
135
+ - `x-stainless-retry-count`
136
+ - preserves incoming value when present and not explicitly falsy
137
+ - otherwise sets `0`
138
+ - `x-stainless-helper`
139
+ - extracted dynamically from `tools`/`messages` in body
140
+ - scans keys: `x_stainless_helper`, `x-stainless-helper`, `stainless_helper`, `stainlessHelper`, `_stainless_helper`
141
+ - aggregates unique values as comma-separated list
142
+
143
+ It also injects optional env-driven headers:
144
+
145
+ - `ANTHROPIC_CUSTOM_HEADERS` (multiline `Header-Name: value`)
146
+ - each valid line is converted into a header
147
+ - `CLAUDE_CODE_CONTAINER_ID` => `x-claude-remote-container-id`
148
+ - `CLAUDE_CODE_REMOTE_SESSION_ID` => `x-claude-remote-session-id`
149
+ - `CLAUDE_AGENT_SDK_CLIENT_APP` => `x-client-app`
150
+ - `CLAUDE_CODE_ADDITIONAL_PROTECTION=1/true/yes` => `x-anthropic-additional-protection: true`
151
+
152
+ ## 5) Beta header catalog (Claude Code reference vs current plugin)
153
+
154
+ ### 5.1 Beta composition rule in the plugin
155
+
156
+ Function: `buildAnthropicBetaHeader(incomingBeta, signatureEnabled, model, provider)`
157
+
158
+ - starts with `oauth-2025-04-20`
159
+ - preserves incoming betas (`incomingBeta`) and deduplicates on merge
160
+
161
+ When `signatureEnabled=false`:
162
+
163
+ - adds `interleaved-thinking-2025-05-14` (in addition to OAuth beta)
164
+
165
+ When `signatureEnabled=true`, current implementation may add dynamically:
166
+
167
+ - `claude-code-20250219` (not added for Haiku models)
168
+ - `interleaved-thinking-2025-05-14` (if model supports it and not disabled by `DISABLE_INTERLEAVED_THINKING`)
169
+ - `context-1m-2025-08-07` (if model indicates 1M context)
170
+ - `context-management-2025-06-27` (non-interactive mode + flags)
171
+ - `structured-outputs-2025-12-15` (model supports it + `TENGU_TOOL_PEAR`)
172
+ - `tool-examples-2025-10-29` (non-interactive mode + `TENGU_SCARF_COFFEE`)
173
+ - `web-search-2025-03-05` (provider `vertex`/`foundry` + supported model)
174
+ - `prompt-caching-scope-2026-01-05` (non-interactive mode)
175
+ - additional betas from `ANTHROPIC_BETAS` (except Haiku)
176
+ - `fine-grained-tool-streaming-2025-05-14` (see note in 5.4)
177
+
178
+ Provider filter:
179
+
180
+ - if detected provider is `bedrock`, remove betas listed in `BEDROCK_UNSUPPORTED_BETAS`.
181
+
182
+ Provider detection is based on request URL hostname (`anthropic`, `bedrock`, `vertex`, `foundry`).
183
+
184
+ ### 5.2 Claude Code reference beta list (consolidated)
185
+
186
+ Automatically enabled by Claude Code (functional reference):
187
+
188
+ - `claude-code-20250219`
189
+ - `interleaved-thinking-2025-05-14`
190
+ - `context-1m-2025-08-07`
191
+ - `context-management-2025-06-27`
192
+ - `structured-outputs-2025-12-15`
193
+ - `tool-examples-2025-10-29`
194
+ - `prompt-caching-scope-2026-01-05`
195
+ - `adaptive-thinking-2026-01-28`
196
+ - `effort-2025-11-24`
197
+ - `fast-mode-2026-02-01`
198
+ - `oauth-2025-04-20`
199
+ - `token-counting-2024-11-01` (preflight `/v1/messages/count_tokens`)
200
+
201
+ Useful in specific integrations:
202
+
203
+ - `files-api-2025-04-14`
204
+ - `message-batches-2024-09-24`
205
+ - `code-execution-2025-08-25`
206
+ - `compact-2026-01-12`
207
+ - `mcp-servers-2025-12-04`
208
+
209
+ Platform-specific betas (not cross-provider defaults):
210
+
211
+ - `bedrock-2023-05-31`
212
+ - `vertex-2023-10-16`
213
+ - `oauth-2025-04-20`
214
+ - `ccr-byoc-2025-07-29`
215
+
216
+ ### 5.3 Current plugin gaps vs reference
217
+
218
+ No dedicated automatic composition yet for:
219
+
220
+ - `adaptive-thinking-2026-01-28`
221
+ - `effort-2025-11-24`
222
+ - `fast-mode-2026-02-01`
223
+ - `token-counting-2024-11-01` (preflight flow)
224
+
225
+ These can still be injected manually through `ANTHROPIC_BETAS` when operationally required.
226
+
227
+ ### 5.4 Important note on fine-grained tool streaming
228
+
229
+ In Claude Code, `fine-grained-tool-streaming` is primarily modeled through tool fields (`eager_input_streaming=true`) and feature/env flags, not as a mandatory beta header dependency.
230
+
231
+ In this plugin's current state, it may still appear in the automatically composed beta list. This remains for compatibility with already-implemented behavior, but should be treated as an alignment refinement area against the reference CLI.
232
+
233
+ ## 6) System prompt mimicry
234
+
235
+ ### 6.1 Block normalization
236
+
237
+ `normalizeSystemTextBlocks(system)` converts `system` into an array of objects:
238
+
239
+ - strings become `{ type: "text", text: "..." }`
240
+ - objects with string `text` are preserved
241
+ - preserves `cache_control` when present
242
+
243
+ ### 6.2 Text sanitization
244
+
245
+ `sanitizeSystemText(text)` applies:
246
+
247
+ - `OpenCode` => `Claude Code`
248
+ - `opencode`/`OpenCode` variants => `Claude`
249
+ - except when preceded by `/` (path-like occurrence preserved)
250
+
251
+ ### 6.3 Injected blocks when mimicry is enabled
252
+
253
+ `buildSystemPromptBlocks(...)`:
254
+
255
+ 1. sanitizes all blocks
256
+ 2. removes pre-existing blocks that are already:
257
+ - `x-anthropic-billing-header: ...`
258
+ - known identity strings (`KNOWN_IDENTITY_STRINGS`)
259
+ 3. builds final ordered list:
260
+ - (optional) billing header block
261
+ - canonical identity block with `cache_control: { type: "ephemeral" }`
262
+ - original filtered/sanitized blocks
263
+
264
+ Canonical identity string:
265
+
266
+ - `You are Claude Code, Anthropic's official CLI for Claude.`
267
+
268
+ ### 6.4 Billing header generation
269
+
270
+ `buildAnthropicBillingHeader(claudeCliVersion, messages)`:
271
+
272
+ - can be disabled by `CLAUDE_CODE_ATTRIBUTION_HEADER=0/false/no`
273
+ - extracts first `user` message text
274
+ - samples chars at positions `[4, 7, 20]` (fallback `"0"` if missing)
275
+ - computes `sha256(BILLING_HASH_SALT + sampled + claudeCliVersion)`
276
+ - `BILLING_HASH_SALT = "59cf53e54c78"`
277
+ - uses first 3 hex chars as hash suffix
278
+ - builds:
279
+
280
+ ```text
281
+ x-anthropic-billing-header: cc_version=<claudeCliVersion>.<hash3>; cc_entrypoint=<entrypoint>; cch=00000;
282
+ ```
283
+
284
+ Detail: `cc_entrypoint` here uses `CLAUDE_CODE_ENTRYPOINT` or `unknown` (different from `user-agent`, whose default is `cli`).
285
+
286
+ ## 7) Body fields related to mimicry
287
+
288
+ When mimicry is enabled, `transformRequestBody(...)` adds/updates:
289
+
290
+ - `metadata.user_id` with format:
291
+ - `user_<persistentUserId>_account_<accountId>_session_<sessionId>`
292
+
293
+ Where:
294
+
295
+ - `persistentUserId`:
296
+ - optional override via `OPENCODE_ANTHROPIC_SIGNATURE_USER_ID`
297
+ - otherwise loaded from persisted file at `getConfigDir()/anthropic-signature-user-id`
298
+ - if absent, generates UUID and persists it
299
+ - `sessionId`: UUID generated once per plugin initialization
300
+ - `accountId`: `account.accountUuid` when present; fallback to `account.id`
301
+
302
+ The plugin does not inject a `betas` field into request body. Beta flags are sent via `anthropic-beta` header only.
303
+
304
+ ## 8) Related URL shaping
305
+
306
+ `transformRequestUrl(input)` appends `?beta=true` for `/v1/messages` requests when the query parameter is not already present.
307
+
308
+ ## 9) Compatibility and fallback behavior
309
+
310
+ - Mimicry is enabled by default (config default)
311
+ - If disabled, plugin keeps auth/rotation behavior and uses legacy system transform path
312
+ - JSON parse failures in body transform do not break requests (original body is preserved)
313
+ - IO failures while persisting `persistentUserId` do not break requests (runtime UUID remains usable)
314
+ - NPM version fetch failure does not break startup (fallback version is used)
315
+
316
+ ## 10) Quick verification checklist
317
+
318
+ To audit whether mimicry is active at runtime:
319
+
320
+ 1. confirm `signature_emulation.enabled=true` (config or env)
321
+ 2. inspect request headers and verify `x-stainless-*`, `x-app`, `anthropic-version`
322
+ 3. verify `anthropic-beta` includes expected flags for model/provider
323
+ 4. inspect body and confirm:
324
+ - `system[0..]` includes identity block (and billing block unless disabled)
325
+ - `metadata.user_id` follows composed format
326
+ - `betas` is aligned with header