@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9
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 +88 -88
- package/dist/opencode-anthropic-auth-cli.mjs +804 -507
- package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
- package/package.json +67 -59
- package/src/__tests__/billing-edge-cases.test.ts +59 -59
- package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
- package/src/__tests__/cc-comparison.test.ts +87 -87
- package/src/__tests__/cc-credentials.test.ts +254 -250
- package/src/__tests__/cch-drift-checker.test.ts +51 -51
- package/src/__tests__/cch-native-style.test.ts +56 -56
- package/src/__tests__/debug-gating.test.ts +42 -42
- package/src/__tests__/decomposition-smoke.test.ts +68 -68
- package/src/__tests__/fingerprint-regression.test.ts +575 -566
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
- package/src/__tests__/helpers/conversation-history.ts +119 -119
- package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
- package/src/__tests__/helpers/deferred.ts +69 -69
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
- package/src/__tests__/helpers/in-memory-storage.ts +88 -88
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
- package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
- package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
- package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
- package/src/__tests__/helpers/sse.ts +209 -209
- package/src/__tests__/index.parallel.test.ts +605 -595
- package/src/__tests__/sanitization-regex.test.ts +112 -112
- package/src/__tests__/state-bounds.test.ts +90 -90
- package/src/account-identity.test.ts +197 -192
- package/src/account-identity.ts +69 -67
- package/src/account-state.test.ts +86 -86
- package/src/account-state.ts +25 -25
- package/src/accounts/matching.test.ts +335 -0
- package/src/accounts/matching.ts +167 -0
- package/src/accounts/persistence.test.ts +345 -0
- package/src/accounts/persistence.ts +432 -0
- package/src/accounts/repair.test.ts +276 -0
- package/src/accounts/repair.ts +407 -0
- package/src/accounts.dedup.test.ts +621 -621
- package/src/accounts.test.ts +933 -929
- package/src/accounts.ts +633 -989
- package/src/backoff.test.ts +345 -345
- package/src/backoff.ts +219 -219
- package/src/betas.ts +124 -124
- package/src/bun-fetch.test.ts +345 -342
- package/src/bun-fetch.ts +424 -424
- package/src/bun-proxy.test.ts +25 -25
- package/src/bun-proxy.ts +209 -209
- package/src/cc-credentials.ts +111 -111
- package/src/circuit-breaker.test.ts +184 -184
- package/src/circuit-breaker.ts +169 -169
- package/src/cli/commands/auth.ts +963 -0
- package/src/cli/commands/config.ts +547 -0
- package/src/cli/formatting.test.ts +406 -0
- package/src/cli/formatting.ts +219 -0
- package/src/cli.ts +255 -2022
- package/src/commands/handlers/betas.ts +100 -0
- package/src/commands/handlers/config.ts +99 -0
- package/src/commands/handlers/files.ts +375 -0
- package/src/commands/oauth-flow.ts +181 -166
- package/src/commands/prompts.ts +61 -61
- package/src/commands/router.test.ts +421 -0
- package/src/commands/router.ts +143 -635
- package/src/config.test.ts +482 -482
- package/src/config.ts +412 -404
- package/src/constants.ts +48 -48
- package/src/drift/cch-constants.ts +95 -95
- package/src/env.ts +111 -105
- package/src/headers/billing.ts +33 -33
- package/src/headers/builder.ts +130 -130
- package/src/headers/cch.ts +75 -75
- package/src/headers/stainless.ts +25 -25
- package/src/headers/user-agent.ts +23 -23
- package/src/index.ts +436 -828
- package/src/models.ts +27 -27
- package/src/oauth.test.ts +102 -102
- package/src/oauth.ts +178 -178
- package/src/parent-pid-watcher.test.ts +148 -148
- package/src/parent-pid-watcher.ts +69 -69
- package/src/plugin-helpers.ts +82 -82
- package/src/refresh-helpers.ts +145 -139
- package/src/refresh-lock.test.ts +94 -94
- package/src/refresh-lock.ts +93 -93
- package/src/request/body.history.test.ts +579 -571
- package/src/request/body.ts +255 -255
- package/src/request/metadata.ts +65 -65
- package/src/request/retry.test.ts +156 -156
- package/src/request/retry.ts +67 -67
- package/src/request/url.ts +21 -21
- package/src/request-orchestration-helpers.ts +648 -0
- package/src/response/index.ts +5 -5
- package/src/response/mcp.ts +58 -58
- package/src/response/streaming.test.ts +313 -311
- package/src/response/streaming.ts +412 -410
- package/src/rotation.test.ts +304 -301
- package/src/rotation.ts +205 -205
- package/src/storage.test.ts +547 -547
- package/src/storage.ts +315 -291
- package/src/system-prompt/builder.ts +38 -38
- package/src/system-prompt/index.ts +5 -5
- package/src/system-prompt/normalize.ts +60 -60
- package/src/system-prompt/sanitize.ts +30 -30
- package/src/thinking.ts +21 -20
- package/src/token-refresh.test.ts +265 -265
- package/src/token-refresh.ts +219 -214
- package/src/types.ts +30 -30
- package/dist/bun-proxy.mjs +0 -291
package/src/constants.ts
CHANGED
|
@@ -14,58 +14,58 @@ export const TOKEN_COUNTING_BETA_FLAG = "token-counting-2024-11-01";
|
|
|
14
14
|
export const CLAUDE_CODE_IDENTITY_STRING = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
15
15
|
|
|
16
16
|
export const KNOWN_IDENTITY_STRINGS = new Set([
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
CLAUDE_CODE_IDENTITY_STRING,
|
|
18
|
+
"You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK.",
|
|
19
|
+
"You are a Claude agent, built on Anthropic's Claude Agent SDK.",
|
|
20
20
|
]);
|
|
21
21
|
|
|
22
22
|
export const BEDROCK_UNSUPPORTED_BETAS = new Set([
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
"interleaved-thinking-2025-05-14",
|
|
24
|
+
"context-1m-2025-08-07",
|
|
25
|
+
"tool-search-tool-2025-10-19",
|
|
26
26
|
]);
|
|
27
27
|
|
|
28
28
|
export const EXPERIMENTAL_BETA_FLAGS = new Set([
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
29
|
+
"adaptive-thinking-2026-01-28",
|
|
30
|
+
"advanced-tool-use-2025-11-20",
|
|
31
|
+
"advisor-tool-2026-03-01",
|
|
32
|
+
"afk-mode-2026-01-31",
|
|
33
|
+
"ccr-byoc-2025-07-29",
|
|
34
|
+
"ccr-triggers-2026-01-30",
|
|
35
|
+
"code-execution-2025-08-25",
|
|
36
|
+
"context-1m-2025-08-07",
|
|
37
|
+
"context-management-2025-06-27",
|
|
38
|
+
"environments-2025-11-01",
|
|
39
|
+
"fast-mode-2026-02-01",
|
|
40
|
+
"files-api-2025-04-14",
|
|
41
|
+
"fine-grained-tool-streaming-2025-05-14",
|
|
42
|
+
"interleaved-thinking-2025-05-14",
|
|
43
|
+
"mcp-client-2025-11-20",
|
|
44
|
+
"prompt-caching-scope-2026-01-05",
|
|
45
|
+
"redact-thinking-2026-02-12",
|
|
46
|
+
"skills-2025-10-02",
|
|
47
|
+
"structured-outputs-2025-12-15",
|
|
48
|
+
"task-budgets-2026-03-13",
|
|
49
|
+
"token-efficient-tools-2026-03-28",
|
|
50
|
+
"tool-search-tool-2025-10-19",
|
|
51
|
+
"web-search-2025-03-05",
|
|
52
52
|
]);
|
|
53
53
|
|
|
54
54
|
export const BETA_SHORTCUTS = new Map<string, string>([
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
["1m", "context-1m-2025-08-07"],
|
|
56
|
+
["1m-context", "context-1m-2025-08-07"],
|
|
57
|
+
["context-1m", "context-1m-2025-08-07"],
|
|
58
|
+
["fast", "fast-mode-2026-02-01"],
|
|
59
|
+
["fast-mode", "fast-mode-2026-02-01"],
|
|
60
|
+
["opus-fast", "fast-mode-2026-02-01"],
|
|
61
61
|
]);
|
|
62
62
|
|
|
63
63
|
export const STAINLESS_HELPER_KEYS = [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
"x_stainless_helper",
|
|
65
|
+
"x-stainless-helper",
|
|
66
|
+
"stainless_helper",
|
|
67
|
+
"stainlessHelper",
|
|
68
|
+
"_stainless_helper",
|
|
69
69
|
] as const;
|
|
70
70
|
|
|
71
71
|
export const USER_ID_STORAGE_FILE = "anthropic-signature-user-id";
|
|
@@ -75,12 +75,12 @@ export const PENDING_OAUTH_TTL_MS = 10 * 60 * 1000;
|
|
|
75
75
|
export const FOREGROUND_EXPIRY_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
|
|
76
76
|
|
|
77
77
|
export const COMPACT_TITLE_GENERATOR_SYSTEM_PROMPT = [
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
"You are a title generator. You output ONLY a thread title. Nothing else.",
|
|
79
|
+
"",
|
|
80
|
+
"Rules:",
|
|
81
|
+
"- Use the same language as the user message.",
|
|
82
|
+
"- Output exactly one line.",
|
|
83
|
+
"- Keep the title at or below 50 characters.",
|
|
84
|
+
"- No explanations, prefixes, or suffixes.",
|
|
85
|
+
"- Keep important technical terms, numbers, and filenames when present.",
|
|
86
86
|
].join("\n");
|
|
@@ -3,131 +3,131 @@ export const EXPECTED_CCH_SALT = "59cf53e54c78";
|
|
|
3
3
|
export const EXPECTED_CCH_SEED = 0x6e52_736a_c806_831en;
|
|
4
4
|
|
|
5
5
|
export const EXPECTED_XXHASH64_PRIMES = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
0x9e37_79b1_85eb_ca87n,
|
|
7
|
+
0xc2b2_ae3d_27d4_eb4fn,
|
|
8
|
+
0x1656_67b1_9e37_79f9n,
|
|
9
|
+
0x85eb_ca77_c2b2_ae63n,
|
|
10
|
+
0x27d4_eb2f_1656_67c5n,
|
|
11
11
|
] as const;
|
|
12
12
|
|
|
13
13
|
export type DriftSeverity = "critical" | "warning";
|
|
14
14
|
|
|
15
15
|
export interface DriftFinding {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
name: string;
|
|
17
|
+
severity: DriftSeverity;
|
|
18
|
+
expected: string;
|
|
19
|
+
actual: string;
|
|
20
|
+
count: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface DriftScanReport {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
target: string;
|
|
25
|
+
mode: "standalone" | "bundle";
|
|
26
|
+
findings: DriftFinding[];
|
|
27
|
+
checked: {
|
|
28
|
+
placeholder: number;
|
|
29
|
+
salt: number;
|
|
30
|
+
seed: number;
|
|
31
|
+
primes: number[];
|
|
32
|
+
};
|
|
33
|
+
passed: boolean;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function encodeAscii(value: string): Uint8Array {
|
|
37
|
-
|
|
37
|
+
return new TextEncoder().encode(value);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export function bigintToLittleEndianBytes(value: bigint): Uint8Array {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
const bytes = new Uint8Array(8);
|
|
42
|
+
let remaining = value;
|
|
43
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
44
|
+
bytes[index] = Number(remaining & 0xffn);
|
|
45
|
+
remaining >>= 8n;
|
|
46
|
+
}
|
|
47
|
+
return bytes;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export function findAllOccurrences(haystack: Uint8Array, needle: Uint8Array): number[] {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
if (needle.length === 0 || haystack.length < needle.length) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
const matches: number[] = [];
|
|
56
|
+
outer: for (let start = 0; start <= haystack.length - needle.length; start += 1) {
|
|
57
|
+
for (let offset = 0; offset < needle.length; offset += 1) {
|
|
58
|
+
if (haystack[start + offset] !== needle[offset]) {
|
|
59
|
+
continue outer;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
matches.push(start);
|
|
61
63
|
}
|
|
62
|
-
matches
|
|
63
|
-
}
|
|
64
|
-
return matches;
|
|
64
|
+
return matches;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function addFinding(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
findings: DriftFinding[],
|
|
69
|
+
count: number,
|
|
70
|
+
name: string,
|
|
71
|
+
severity: DriftSeverity,
|
|
72
|
+
expected: string,
|
|
73
|
+
actual: string,
|
|
74
74
|
): void {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
if (count > 0) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
findings.push({ name, severity, expected, actual, count });
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
export function scanCchConstants(bytes: Uint8Array, target: string, mode: "standalone" | "bundle"): DriftScanReport {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const findings: DriftFinding[] = [];
|
|
90
|
-
addFinding(
|
|
91
|
-
findings,
|
|
92
|
-
placeholderMatches.length,
|
|
93
|
-
"cch placeholder",
|
|
94
|
-
"critical",
|
|
95
|
-
`cch=${EXPECTED_CCH_PLACEHOLDER}`,
|
|
96
|
-
"not found",
|
|
97
|
-
);
|
|
98
|
-
addFinding(findings, saltMatches.length, "cc_version salt", "critical", EXPECTED_CCH_SALT, "not found");
|
|
82
|
+
const placeholderMatches = findAllOccurrences(bytes, encodeAscii(`cch=${EXPECTED_CCH_PLACEHOLDER}`));
|
|
83
|
+
const saltMatches = findAllOccurrences(bytes, encodeAscii(EXPECTED_CCH_SALT));
|
|
84
|
+
const seedMatches = findAllOccurrences(bytes, bigintToLittleEndianBytes(EXPECTED_CCH_SEED));
|
|
85
|
+
const primeMatches = EXPECTED_XXHASH64_PRIMES.map(
|
|
86
|
+
(prime) => findAllOccurrences(bytes, bigintToLittleEndianBytes(prime)).length,
|
|
87
|
+
);
|
|
99
88
|
|
|
100
|
-
|
|
89
|
+
const findings: DriftFinding[] = [];
|
|
101
90
|
addFinding(
|
|
102
|
-
findings,
|
|
103
|
-
seedMatches.length,
|
|
104
|
-
"native cch seed",
|
|
105
|
-
"critical",
|
|
106
|
-
`0x${EXPECTED_CCH_SEED.toString(16)}`,
|
|
107
|
-
"not found",
|
|
108
|
-
);
|
|
109
|
-
for (const [index, count] of primeMatches.entries()) {
|
|
110
|
-
addFinding(
|
|
111
91
|
findings,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"
|
|
115
|
-
`
|
|
92
|
+
placeholderMatches.length,
|
|
93
|
+
"cch placeholder",
|
|
94
|
+
"critical",
|
|
95
|
+
`cch=${EXPECTED_CCH_PLACEHOLDER}`,
|
|
116
96
|
"not found",
|
|
117
|
-
|
|
97
|
+
);
|
|
98
|
+
addFinding(findings, saltMatches.length, "cc_version salt", "critical", EXPECTED_CCH_SALT, "not found");
|
|
99
|
+
|
|
100
|
+
if (mode === "standalone") {
|
|
101
|
+
addFinding(
|
|
102
|
+
findings,
|
|
103
|
+
seedMatches.length,
|
|
104
|
+
"native cch seed",
|
|
105
|
+
"critical",
|
|
106
|
+
`0x${EXPECTED_CCH_SEED.toString(16)}`,
|
|
107
|
+
"not found",
|
|
108
|
+
);
|
|
109
|
+
for (const [index, count] of primeMatches.entries()) {
|
|
110
|
+
addFinding(
|
|
111
|
+
findings,
|
|
112
|
+
count,
|
|
113
|
+
`xxHash64 prime ${index + 1}`,
|
|
114
|
+
"warning",
|
|
115
|
+
`0x${EXPECTED_XXHASH64_PRIMES[index].toString(16)}`,
|
|
116
|
+
"not found",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
118
119
|
}
|
|
119
|
-
}
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
121
|
+
return {
|
|
122
|
+
target,
|
|
123
|
+
mode,
|
|
124
|
+
findings,
|
|
125
|
+
checked: {
|
|
126
|
+
placeholder: placeholderMatches.length,
|
|
127
|
+
salt: saltMatches.length,
|
|
128
|
+
seed: seedMatches.length,
|
|
129
|
+
primes: primeMatches,
|
|
130
|
+
},
|
|
131
|
+
passed: findings.length === 0,
|
|
132
|
+
};
|
|
133
133
|
}
|
package/src/env.ts
CHANGED
|
@@ -9,138 +9,144 @@ import { getConfigDir } from "./config.js";
|
|
|
9
9
|
import { BETA_SHORTCUTS, DEBUG_SYSTEM_PROMPT_ENV, USER_ID_STORAGE_FILE } from "./constants.ts";
|
|
10
10
|
|
|
11
11
|
export function isTruthyEnv(value: string | undefined): boolean {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
if (!value) return false;
|
|
13
|
+
const normalized = value.trim().toLowerCase();
|
|
14
|
+
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function isFalsyEnv(value: string | undefined): boolean {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if (!value) return false;
|
|
19
|
+
const normalized = value.trim().toLowerCase();
|
|
20
|
+
return normalized === "0" || normalized === "false" || normalized === "no";
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export function isNonInteractiveMode(): boolean {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
if (isTruthyEnv(process.env.CI)) return true;
|
|
25
|
+
return !process.stdout.isTTY;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function getClaudeEntrypoint(): string {
|
|
29
|
-
|
|
29
|
+
return process.env.CLAUDE_CODE_ENTRYPOINT || "cli";
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function resolveBetaShortcut(value: string | undefined): string {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
if (!value) return "";
|
|
34
|
+
const trimmed = value.trim();
|
|
35
|
+
const mapped = BETA_SHORTCUTS.get(trimmed.toLowerCase());
|
|
36
|
+
return mapped || trimmed;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export function parseAnthropicCustomHeaders(): Record<string, string> {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
40
|
+
const raw = process.env.ANTHROPIC_CUSTOM_HEADERS;
|
|
41
|
+
if (!raw) return {};
|
|
42
|
+
|
|
43
|
+
const headers: Record<string, string> = {};
|
|
44
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
45
|
+
const trimmed = line.trim();
|
|
46
|
+
if (!trimmed) continue;
|
|
47
|
+
const sep = trimmed.indexOf(":");
|
|
48
|
+
if (sep <= 0) continue;
|
|
49
|
+
const key = trimmed.slice(0, sep).trim();
|
|
50
|
+
const value = trimmed.slice(sep + 1).trim();
|
|
51
|
+
if (!key || !value) continue;
|
|
52
|
+
headers[key] = value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return headers;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export function getOrCreateSignatureUserId(): string {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
const envUserId = process.env.OPENCODE_ANTHROPIC_SIGNATURE_USER_ID?.trim();
|
|
60
|
+
if (envUserId) return envUserId;
|
|
61
|
+
|
|
62
|
+
const configDir = getConfigDir();
|
|
63
|
+
const userIdPath = join(configDir, USER_ID_STORAGE_FILE);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
if (existsSync(userIdPath)) {
|
|
67
|
+
const existing = readFileSync(userIdPath, "utf-8").trim();
|
|
68
|
+
// CC uses 64-char hex (32 random bytes). Accept existing hex IDs;
|
|
69
|
+
// regenerate if we find an old UUID-format ID.
|
|
70
|
+
if (existing && /^[0-9a-f]{64}$/.test(existing)) return existing;
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
// fall through and generate a new id
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// CC generates device_id as randomBytes(32).toString("hex") → 64-char hex
|
|
77
|
+
const generated = randomBytes(32).toString("hex");
|
|
78
|
+
try {
|
|
79
|
+
mkdirSync(configDir, { recursive: true });
|
|
80
|
+
writeFileSync(userIdPath, `${generated}\n`, {
|
|
81
|
+
encoding: "utf-8",
|
|
82
|
+
mode: 0o600,
|
|
83
|
+
});
|
|
84
|
+
} catch {
|
|
85
|
+
// Ignore filesystem errors; caller still gets generated ID for this runtime.
|
|
71
86
|
}
|
|
72
|
-
|
|
73
|
-
// fall through and generate a new id
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// CC generates device_id as randomBytes(32).toString("hex") → 64-char hex
|
|
77
|
-
const generated = randomBytes(32).toString("hex");
|
|
78
|
-
try {
|
|
79
|
-
mkdirSync(configDir, { recursive: true });
|
|
80
|
-
writeFileSync(userIdPath, `${generated}\n`, {
|
|
81
|
-
encoding: "utf-8",
|
|
82
|
-
mode: 0o600,
|
|
83
|
-
});
|
|
84
|
-
} catch {
|
|
85
|
-
// Ignore filesystem errors; caller still gets generated ID for this runtime.
|
|
86
|
-
}
|
|
87
|
-
return generated;
|
|
87
|
+
return generated;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
export function shouldDebugSystemPrompt(): boolean {
|
|
91
|
-
|
|
91
|
+
return isTruthyEnv(process.env[DEBUG_SYSTEM_PROMPT_ENV]);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
export function logTransformedSystemPrompt(body: string | undefined): void {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// The plugin relocates non-CC system blocks into the first user message
|
|
117
|
-
// wrapped in <system-instructions>. Check there too so title-generator
|
|
118
|
-
// requests are still suppressed from the debug log after the relocation
|
|
119
|
-
// pass runs.
|
|
120
|
-
const messages = parsed.messages;
|
|
121
|
-
if (Array.isArray(messages) && messages.length > 0) {
|
|
122
|
-
const firstMsg = messages[0];
|
|
123
|
-
if (firstMsg && firstMsg.role === "user") {
|
|
124
|
-
const content = firstMsg.content;
|
|
125
|
-
if (typeof content === "string" && isTitleGeneratorText(content)) {
|
|
126
|
-
return;
|
|
95
|
+
if (!shouldDebugSystemPrompt()) return;
|
|
96
|
+
if (!body || typeof body !== "string") return;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(body);
|
|
100
|
+
if (!Object.hasOwn(parsed, "system")) return;
|
|
101
|
+
// Avoid circular import: inline the title-check here
|
|
102
|
+
const isTitleGeneratorText = (text: unknown): boolean => {
|
|
103
|
+
if (typeof text !== "string") return false;
|
|
104
|
+
const lowered = text.trim().toLowerCase();
|
|
105
|
+
return lowered.includes("you are a title generator") || lowered.includes("generate a brief title");
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const system = parsed.system;
|
|
109
|
+
if (
|
|
110
|
+
Array.isArray(system) &&
|
|
111
|
+
system.some(
|
|
112
|
+
(item: { type?: string; text?: string }) => item.type === "text" && isTitleGeneratorText(item.text),
|
|
113
|
+
)
|
|
114
|
+
) {
|
|
115
|
+
return;
|
|
127
116
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
|
|
118
|
+
// The plugin relocates non-CC system blocks into the first user message
|
|
119
|
+
// wrapped in <system-instructions>. Check there too so title-generator
|
|
120
|
+
// requests are still suppressed from the debug log after the relocation
|
|
121
|
+
// pass runs.
|
|
122
|
+
const messages = parsed.messages;
|
|
123
|
+
if (Array.isArray(messages) && messages.length > 0) {
|
|
124
|
+
const firstMsg = messages[0];
|
|
125
|
+
if (firstMsg && firstMsg.role === "user") {
|
|
126
|
+
const content = firstMsg.content;
|
|
127
|
+
if (typeof content === "string" && isTitleGeneratorText(content)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(content)) {
|
|
131
|
+
for (const block of content) {
|
|
132
|
+
if (
|
|
133
|
+
block &&
|
|
134
|
+
typeof block === "object" &&
|
|
135
|
+
isTitleGeneratorText((block as { text?: unknown }).text)
|
|
136
|
+
) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
132
141
|
}
|
|
133
|
-
}
|
|
134
142
|
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
143
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
// eslint-disable-next-line no-console -- explicit debug logger gated by OPENCODE_ANTHROPIC_DEBUG_SYSTEM_PROMPT
|
|
145
|
+
console.error(
|
|
146
|
+
"[opencode-anthropic-auth][system-debug] transformed system:",
|
|
147
|
+
JSON.stringify(parsed.system, null, 2),
|
|
148
|
+
);
|
|
149
|
+
} catch {
|
|
150
|
+
// Ignore parse errors in debug logging path.
|
|
151
|
+
}
|
|
146
152
|
}
|