@ex-machina/opencode-anthropic-auth 1.5.0 → 1.6.0
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/README.md +16 -3
- package/dist/transform.d.ts +6 -0
- package/dist/transform.js +31 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Add the plugin to your OpenCode configuration:
|
|
|
21
21
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
24
|
-
"plugin": ["@ex-machina/opencode-anthropic-auth@1.
|
|
24
|
+
"plugin": ["@ex-machina/opencode-anthropic-auth@1.5.1"]
|
|
25
25
|
}
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -33,6 +33,16 @@ The plugin provides three authentication options:
|
|
|
33
33
|
- **Create an API Key** - OAuth flow via `console.anthropic.com` that creates an API key on your behalf.
|
|
34
34
|
- **Manually enter API Key** - Standard API key entry for users who already have one.
|
|
35
35
|
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
The plugin supports the following environment variables:
|
|
39
|
+
|
|
40
|
+
| Variable | Description |
|
|
41
|
+
|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
42
|
+
| `ANTHROPIC_BASE_URL` | Override the API endpoint URL (e.g. for proxying). Must be a valid HTTP(S) URL. |
|
|
43
|
+
| `ANTHROPIC_INSECURE` | Set to `1` or `true` to skip TLS certificate verification. Only effective when `ANTHROPIC_BASE_URL` is also set. |
|
|
44
|
+
| `EXPERIMENTAL_KEEP_SYSTEM_PROMPT` | Set to `1` or `true` to keep the sanitized system prompt in the `system[]` field instead of relocating it to a user message. See [System Prompt Sanitization](#system-prompt-sanitization). |
|
|
45
|
+
|
|
36
46
|
## How It Works
|
|
37
47
|
|
|
38
48
|
For Claude Pro/Max authentication, the plugin:
|
|
@@ -46,13 +56,16 @@ For Claude Pro/Max authentication, the plugin:
|
|
|
46
56
|
|
|
47
57
|
### System Prompt Sanitization
|
|
48
58
|
|
|
49
|
-
The Anthropic API for Max subscriptions
|
|
59
|
+
The Anthropic API for Max subscriptions has specific requirements for the system prompt to identify as Claude Code. The plugin rewrites the system prompt on each request using an **anchor-based** approach that minimizes what gets changed:
|
|
50
60
|
|
|
51
61
|
1. **Identity swap** — The OpenCode identity line is removed and replaced with the Claude Code identity.
|
|
52
62
|
2. **Paragraph removal by anchor** — Any paragraph containing a known URL anchor (e.g. `github.com/anomalyco/opencode`, `opencode.ai/docs`) is removed entirely. This is resilient to upstream rewording — as long as the anchor URL appears somewhere in the paragraph, the removal works regardless of surrounding text changes.
|
|
53
63
|
3. **Inline text replacements** — Short branded strings inside paragraphs we want to keep are replaced (e.g. "OpenCode" → "the assistant" in the professional objectivity section).
|
|
54
64
|
|
|
55
|
-
Everything else in the system prompt is preserved: tone/style guidance, task management instructions, tool usage policy, environment info, skills, user/project instructions, and file paths containing "opencode".
|
|
65
|
+
Everything else in the system prompt is preserved: tone/style guidance, task management instructions, tool usage policy, environment info, skills, user/project instructions, and file paths containing "opencode". The system prompt is then **split** and only the billing header and identity line are left in the system prompt. The remainder is moved into a user message to bypass system prompt checks.
|
|
66
|
+
|
|
67
|
+
> [!NOTE]
|
|
68
|
+
> Set `EXPERIMENTAL_KEEP_SYSTEM_PROMPT=1` to skip the relocation step. The sanitized system prompt will remain in `system[]` in its entirety. This may cause API rejections for OAuth-authenticated requests.
|
|
56
69
|
|
|
57
70
|
## Development
|
|
58
71
|
|
package/dist/transform.d.ts
CHANGED
|
@@ -27,6 +27,12 @@ export declare function stripToolPrefix(text: string): string;
|
|
|
27
27
|
* Only effective when ANTHROPIC_BASE_URL is also set.
|
|
28
28
|
*/
|
|
29
29
|
export declare function isInsecure(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Check if system prompt relocation should be skipped.
|
|
32
|
+
* When enabled, sanitized system blocks stay in system[] instead of
|
|
33
|
+
* being moved to the first user message.
|
|
34
|
+
*/
|
|
35
|
+
export declare function experimentalKeepSystemPrompt(): boolean;
|
|
30
36
|
/**
|
|
31
37
|
* Rewrite the request URL to add ?beta=true for /v1/messages requests.
|
|
32
38
|
* When ANTHROPIC_BASE_URL is set, overrides the origin (protocol + host)
|
package/dist/transform.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { buildBillingHeaderValue } from './cch';
|
|
2
2
|
import { CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_IDENTITY, OPENCODE_IDENTITY, PARAGRAPH_REMOVAL_ANCHORS, REQUIRED_BETAS, TEXT_REPLACEMENTS, TOOL_PREFIX, USER_AGENT, } from './constants';
|
|
3
|
+
/**
|
|
4
|
+
* Prefix a tool name with TOOL_PREFIX and uppercase the first character.
|
|
5
|
+
* Claude Code uses PascalCase tool names (e.g. mcp_Bash, mcp_Read);
|
|
6
|
+
* lowercase names (mcp_bash, mcp_read) are flagged as non-Claude-Code clients.
|
|
7
|
+
*/
|
|
8
|
+
function prefixName(name) {
|
|
9
|
+
return `${TOOL_PREFIX}${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Reverse prefixName: strip TOOL_PREFIX and restore the original leading case.
|
|
13
|
+
*/
|
|
14
|
+
function unprefixName(name) {
|
|
15
|
+
return `${name.charAt(0).toLowerCase()}${name.slice(1)}`;
|
|
16
|
+
}
|
|
3
17
|
/**
|
|
4
18
|
* Merge headers from a Request object and/or a RequestInit headers value
|
|
5
19
|
* into a single Headers instance.
|
|
@@ -68,7 +82,7 @@ export function prefixToolNames(body) {
|
|
|
68
82
|
if (parsed.tools && Array.isArray(parsed.tools)) {
|
|
69
83
|
parsed.tools = parsed.tools.map((tool) => ({
|
|
70
84
|
...tool,
|
|
71
|
-
name: tool.name ?
|
|
85
|
+
name: tool.name ? prefixName(tool.name) : tool.name,
|
|
72
86
|
}));
|
|
73
87
|
}
|
|
74
88
|
if (parsed.messages && Array.isArray(parsed.messages)) {
|
|
@@ -76,10 +90,7 @@ export function prefixToolNames(body) {
|
|
|
76
90
|
if (msg.content && Array.isArray(msg.content)) {
|
|
77
91
|
msg.content = msg.content.map((block) => {
|
|
78
92
|
if (block.type === 'tool_use' && block.name) {
|
|
79
|
-
return {
|
|
80
|
-
...block,
|
|
81
|
-
name: `${TOOL_PREFIX}${block.name}`,
|
|
82
|
-
};
|
|
93
|
+
return { ...block, name: prefixName(block.name) };
|
|
83
94
|
}
|
|
84
95
|
return block;
|
|
85
96
|
});
|
|
@@ -97,7 +108,7 @@ export function prefixToolNames(body) {
|
|
|
97
108
|
* Strip TOOL_PREFIX from tool names in streaming response text.
|
|
98
109
|
*/
|
|
99
110
|
export function stripToolPrefix(text) {
|
|
100
|
-
return text.replace(/"name"\s*:\s*"mcp_([^"]+)"/g,
|
|
111
|
+
return text.replace(/"name"\s*:\s*"mcp_([^"]+)"/g, (_match, name) => `"name": "${unprefixName(name)}"`);
|
|
101
112
|
}
|
|
102
113
|
/**
|
|
103
114
|
* Check if TLS verification should be skipped for custom API endpoints.
|
|
@@ -109,6 +120,15 @@ export function isInsecure() {
|
|
|
109
120
|
const raw = process.env.ANTHROPIC_INSECURE?.trim();
|
|
110
121
|
return raw === '1' || raw === 'true';
|
|
111
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if system prompt relocation should be skipped.
|
|
125
|
+
* When enabled, sanitized system blocks stay in system[] instead of
|
|
126
|
+
* being moved to the first user message.
|
|
127
|
+
*/
|
|
128
|
+
export function experimentalKeepSystemPrompt() {
|
|
129
|
+
const raw = process.env.EXPERIMENTAL_KEEP_SYSTEM_PROMPT?.trim();
|
|
130
|
+
return raw === '1' || raw === 'true';
|
|
131
|
+
}
|
|
112
132
|
/**
|
|
113
133
|
* Parse ANTHROPIC_BASE_URL from the environment.
|
|
114
134
|
* Returns a valid HTTP(S) URL or null if unset/invalid.
|
|
@@ -277,7 +297,9 @@ export function rewriteRequestBody(body) {
|
|
|
277
297
|
// Third-party system prompts trigger a 400 rejection when they
|
|
278
298
|
// appear in `system[]`. Keep only the identity block in `system[]`
|
|
279
299
|
// and prepend everything else to the first user message.
|
|
280
|
-
if (
|
|
300
|
+
if (!experimentalKeepSystemPrompt() &&
|
|
301
|
+
Array.isArray(parsed.system) &&
|
|
302
|
+
parsed.system.length > 1) {
|
|
281
303
|
const kept = [parsed.system[0]]; // identity block
|
|
282
304
|
const movedTexts = [];
|
|
283
305
|
for (let i = 1; i < parsed.system.length; i++) {
|
|
@@ -310,7 +332,7 @@ export function rewriteRequestBody(body) {
|
|
|
310
332
|
if (parsed.tools && Array.isArray(parsed.tools)) {
|
|
311
333
|
parsed.tools = parsed.tools.map((tool) => ({
|
|
312
334
|
...tool,
|
|
313
|
-
name: tool.name ?
|
|
335
|
+
name: tool.name ? prefixName(tool.name) : tool.name,
|
|
314
336
|
}));
|
|
315
337
|
}
|
|
316
338
|
if (parsed.messages && Array.isArray(parsed.messages)) {
|
|
@@ -318,7 +340,7 @@ export function rewriteRequestBody(body) {
|
|
|
318
340
|
if (msg.content && Array.isArray(msg.content)) {
|
|
319
341
|
msg.content = msg.content.map((block) => {
|
|
320
342
|
if (block.type === 'tool_use' && block.name) {
|
|
321
|
-
return { ...block, name:
|
|
343
|
+
return { ...block, name: prefixName(block.name) };
|
|
322
344
|
}
|
|
323
345
|
return block;
|
|
324
346
|
});
|