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.
- package/.github/workflows/publish.yml +31 -0
- package/.husky/pre-commit +2 -0
- package/.husky/pre-push +3 -0
- package/.planning/CLI-ENHANCEMENTS.md +221 -0
- package/.planning/codebase/ARCHITECTURE.md +201 -0
- package/.planning/codebase/CONCERNS.md +219 -0
- package/.planning/codebase/CONVENTIONS.md +335 -0
- package/.planning/codebase/INTEGRATIONS.md +215 -0
- package/.planning/codebase/STACK.md +160 -0
- package/.planning/codebase/STRUCTURE.md +244 -0
- package/.planning/codebase/TESTING.md +456 -0
- package/.prettierignore +3 -0
- package/.prettierrc +10 -0
- package/CONTRIBUTING.md +525 -0
- package/LICENSE +21 -0
- package/README.md +478 -0
- package/agents.md +22 -0
- package/bun.lock +43 -0
- package/cli.mjs +1773 -0
- package/cli.test.mjs +1797 -0
- package/docs/mimese-http-header-system-prompt.md +326 -0
- package/docs/plans/afcw-upstream-watcher.json +495 -0
- package/eslint.config.mjs +69 -0
- package/index.mjs +2209 -0
- package/index.test.mjs +2690 -0
- package/lib/accounts.mjs +670 -0
- package/lib/accounts.test.mjs +782 -0
- package/lib/backoff.mjs +217 -0
- package/lib/backoff.test.mjs +296 -0
- package/lib/config.mjs +366 -0
- package/lib/config.test.mjs +307 -0
- package/lib/oauth.mjs +103 -0
- package/lib/rotation.mjs +287 -0
- package/lib/rotation.test.mjs +346 -0
- package/lib/storage.mjs +265 -0
- package/lib/storage.test.mjs +470 -0
- package/nul +1 -0
- package/package.json +40 -0
- package/script/publish.ts +29 -0
- package/scripts/build.mjs +34 -0
- package/scripts/install.mjs +319 -0
|
@@ -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
|