@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/config.ts
CHANGED
|
@@ -5,122 +5,122 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
export type AccountSelectionStrategy = "sticky" | "round-robin" | "hybrid";
|
|
6
6
|
|
|
7
7
|
export interface HealthScoreConfig {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
initial: number;
|
|
9
|
+
success_reward: number;
|
|
10
|
+
rate_limit_penalty: number;
|
|
11
|
+
failure_penalty: number;
|
|
12
|
+
recovery_rate_per_hour: number;
|
|
13
|
+
min_usable: number;
|
|
14
|
+
max_score: number;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface TokenBucketConfig {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
max_tokens: number;
|
|
19
|
+
regeneration_rate_per_minute: number;
|
|
20
|
+
initial_tokens: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface ToastConfig {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
/** Suppress non-error toasts */
|
|
25
|
+
quiet: boolean;
|
|
26
|
+
/** Minimum seconds between account-switch toasts */
|
|
27
|
+
debounce_seconds: number;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface OverrideModelLimitsConfig {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
/** When true, overrides model context limits for 1M-window models */
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
/** Context window size to inject (tokens). Default: 1_000_000 */
|
|
34
|
+
context: number;
|
|
35
|
+
/** Max output tokens to inject. 0 = leave model default unchanged */
|
|
36
|
+
output: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export interface IdleRefreshConfig {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
/** Opportunistically refresh near-expiry idle accounts */
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
/** Refresh idle accounts within this many minutes of expiry */
|
|
43
|
+
window_minutes: number;
|
|
44
|
+
/** Minimum minutes between idle refresh attempts per account */
|
|
45
|
+
min_interval_minutes: number;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export interface HeaderConfig {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
emulation_profile?: string;
|
|
50
|
+
overrides?: Record<string, string>;
|
|
51
|
+
disable?: string[];
|
|
52
|
+
billing_header?: boolean;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export interface AnthropicAuthConfig {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
56
|
+
account_selection_strategy: AccountSelectionStrategy;
|
|
57
|
+
failure_ttl_seconds: number;
|
|
58
|
+
debug: boolean;
|
|
59
|
+
signature_emulation: {
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
fetch_claude_code_version_on_startup: boolean;
|
|
62
|
+
prompt_compaction: "minimal" | "off";
|
|
63
|
+
sanitize_system_prompt: boolean;
|
|
64
|
+
};
|
|
65
|
+
override_model_limits: OverrideModelLimitsConfig;
|
|
66
|
+
custom_betas: string[];
|
|
67
|
+
health_score: HealthScoreConfig;
|
|
68
|
+
token_bucket: TokenBucketConfig;
|
|
69
|
+
toasts: ToastConfig;
|
|
70
|
+
headers: HeaderConfig;
|
|
71
|
+
idle_refresh: IdleRefreshConfig;
|
|
72
|
+
cc_credential_reuse: {
|
|
73
|
+
enabled: boolean;
|
|
74
|
+
auto_detect: boolean;
|
|
75
|
+
prefer_over_oauth: boolean;
|
|
76
|
+
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export const DEFAULT_CONFIG: AnthropicAuthConfig = {
|
|
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
|
-
|
|
80
|
+
account_selection_strategy: "sticky",
|
|
81
|
+
failure_ttl_seconds: 3600,
|
|
82
|
+
debug: false,
|
|
83
|
+
signature_emulation: {
|
|
84
|
+
enabled: true,
|
|
85
|
+
fetch_claude_code_version_on_startup: true,
|
|
86
|
+
prompt_compaction: "minimal",
|
|
87
|
+
sanitize_system_prompt: false,
|
|
88
|
+
},
|
|
89
|
+
override_model_limits: {
|
|
90
|
+
enabled: true,
|
|
91
|
+
context: 1_000_000,
|
|
92
|
+
output: 0,
|
|
93
|
+
},
|
|
94
|
+
custom_betas: [],
|
|
95
|
+
health_score: {
|
|
96
|
+
initial: 70,
|
|
97
|
+
success_reward: 1,
|
|
98
|
+
rate_limit_penalty: -10,
|
|
99
|
+
failure_penalty: -20,
|
|
100
|
+
recovery_rate_per_hour: 2,
|
|
101
|
+
min_usable: 50,
|
|
102
|
+
max_score: 100,
|
|
103
|
+
},
|
|
104
|
+
token_bucket: {
|
|
105
|
+
max_tokens: 50,
|
|
106
|
+
regeneration_rate_per_minute: 6,
|
|
107
|
+
initial_tokens: 50,
|
|
108
|
+
},
|
|
109
|
+
toasts: {
|
|
110
|
+
quiet: false,
|
|
111
|
+
debounce_seconds: 30,
|
|
112
|
+
},
|
|
113
|
+
headers: {},
|
|
114
|
+
idle_refresh: {
|
|
115
|
+
enabled: true,
|
|
116
|
+
window_minutes: 60,
|
|
117
|
+
min_interval_minutes: 30,
|
|
118
|
+
},
|
|
119
|
+
cc_credential_reuse: {
|
|
120
|
+
enabled: true,
|
|
121
|
+
auto_detect: true,
|
|
122
|
+
prefer_over_oauth: true,
|
|
123
|
+
},
|
|
124
124
|
};
|
|
125
125
|
|
|
126
126
|
export const VALID_STRATEGIES: AccountSelectionStrategy[] = ["sticky", "round-robin", "hybrid"];
|
|
@@ -129,311 +129,319 @@ export const VALID_STRATEGIES: AccountSelectionStrategy[] = ["sticky", "round-ro
|
|
|
129
129
|
export const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
130
130
|
|
|
131
131
|
function createDefaultConfig(): AnthropicAuthConfig {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
return {
|
|
133
|
+
...DEFAULT_CONFIG,
|
|
134
|
+
signature_emulation: { ...DEFAULT_CONFIG.signature_emulation },
|
|
135
|
+
override_model_limits: { ...DEFAULT_CONFIG.override_model_limits },
|
|
136
|
+
custom_betas: [...DEFAULT_CONFIG.custom_betas],
|
|
137
|
+
health_score: { ...DEFAULT_CONFIG.health_score },
|
|
138
|
+
token_bucket: { ...DEFAULT_CONFIG.token_bucket },
|
|
139
|
+
toasts: { ...DEFAULT_CONFIG.toasts },
|
|
140
|
+
headers: {},
|
|
141
|
+
idle_refresh: { ...DEFAULT_CONFIG.idle_refresh },
|
|
142
|
+
cc_credential_reuse: { ...DEFAULT_CONFIG.cc_credential_reuse },
|
|
143
|
+
};
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
147
|
* Get the OpenCode config directory (XDG-compliant).
|
|
148
148
|
*/
|
|
149
149
|
export function getConfigDir(): string {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
150
|
+
const platform = process.platform;
|
|
151
|
+
if (platform === "win32") {
|
|
152
|
+
return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "opencode");
|
|
153
|
+
}
|
|
154
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
155
|
+
return join(xdgConfig, "opencode");
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
159
|
* Get the path to the config file.
|
|
160
160
|
*/
|
|
161
161
|
export function getConfigPath(): string {
|
|
162
|
-
|
|
162
|
+
return join(getConfigDir(), "anthropic-auth.json");
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
function clampNumber(value: unknown, min: number, max: number, fallback: number): number {
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
167
|
+
return Math.max(min, Math.min(max, value));
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
function validateConfig(raw: Record<string, unknown>): AnthropicAuthConfig {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
typeof raw.account_selection_strategy === "string" &&
|
|
175
|
-
VALID_STRATEGIES.includes(raw.account_selection_strategy as AccountSelectionStrategy)
|
|
176
|
-
) {
|
|
177
|
-
config.account_selection_strategy = raw.account_selection_strategy as AccountSelectionStrategy;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
config.failure_ttl_seconds = clampNumber(raw.failure_ttl_seconds, 60, 7200, DEFAULT_CONFIG.failure_ttl_seconds);
|
|
181
|
-
|
|
182
|
-
if (typeof raw.debug === "boolean") {
|
|
183
|
-
config.debug = raw.debug;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (raw.signature_emulation && typeof raw.signature_emulation === "object") {
|
|
187
|
-
const se = raw.signature_emulation as Record<string, unknown>;
|
|
188
|
-
config.signature_emulation = {
|
|
189
|
-
enabled: typeof se.enabled === "boolean" ? se.enabled : DEFAULT_CONFIG.signature_emulation.enabled,
|
|
190
|
-
fetch_claude_code_version_on_startup:
|
|
191
|
-
typeof se.fetch_claude_code_version_on_startup === "boolean"
|
|
192
|
-
? se.fetch_claude_code_version_on_startup
|
|
193
|
-
: DEFAULT_CONFIG.signature_emulation.fetch_claude_code_version_on_startup,
|
|
194
|
-
prompt_compaction:
|
|
195
|
-
se.prompt_compaction === "off" || se.prompt_compaction === "minimal"
|
|
196
|
-
? se.prompt_compaction
|
|
197
|
-
: DEFAULT_CONFIG.signature_emulation.prompt_compaction,
|
|
198
|
-
sanitize_system_prompt:
|
|
199
|
-
typeof se.sanitize_system_prompt === "boolean"
|
|
200
|
-
? se.sanitize_system_prompt
|
|
201
|
-
: DEFAULT_CONFIG.signature_emulation.sanitize_system_prompt,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Top-level alias: `sanitize_system_prompt` is honored as a convenience so
|
|
206
|
-
// users can flip it on/off without learning the nested signature_emulation
|
|
207
|
-
// schema. The top-level value, when set, takes precedence over the nested
|
|
208
|
-
// one because it's the more specific user intent.
|
|
209
|
-
if (typeof raw.sanitize_system_prompt === "boolean") {
|
|
210
|
-
config.signature_emulation.sanitize_system_prompt = raw.sanitize_system_prompt;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (raw.override_model_limits && typeof raw.override_model_limits === "object") {
|
|
214
|
-
const oml = raw.override_model_limits as Record<string, unknown>;
|
|
215
|
-
config.override_model_limits = {
|
|
216
|
-
enabled: typeof oml.enabled === "boolean" ? oml.enabled : DEFAULT_CONFIG.override_model_limits.enabled,
|
|
217
|
-
context: clampNumber(oml.context, 200_000, 2_000_000, DEFAULT_CONFIG.override_model_limits.context),
|
|
218
|
-
output: clampNumber(oml.output, 0, 128_000, DEFAULT_CONFIG.override_model_limits.output),
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (Array.isArray(raw.custom_betas)) {
|
|
223
|
-
config.custom_betas = (raw.custom_betas as unknown[])
|
|
224
|
-
.filter((b): b is string => typeof b === "string" && b.trim().length > 0)
|
|
225
|
-
.map((b) => b.trim());
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (raw.health_score && typeof raw.health_score === "object") {
|
|
229
|
-
const hs = raw.health_score as Record<string, unknown>;
|
|
230
|
-
config.health_score = {
|
|
231
|
-
initial: clampNumber(hs.initial, 0, 100, DEFAULT_CONFIG.health_score.initial),
|
|
232
|
-
success_reward: clampNumber(hs.success_reward, 0, 10, DEFAULT_CONFIG.health_score.success_reward),
|
|
233
|
-
rate_limit_penalty: clampNumber(hs.rate_limit_penalty, -50, 0, DEFAULT_CONFIG.health_score.rate_limit_penalty),
|
|
234
|
-
failure_penalty: clampNumber(hs.failure_penalty, -100, 0, DEFAULT_CONFIG.health_score.failure_penalty),
|
|
235
|
-
recovery_rate_per_hour: clampNumber(
|
|
236
|
-
hs.recovery_rate_per_hour,
|
|
237
|
-
0,
|
|
238
|
-
20,
|
|
239
|
-
DEFAULT_CONFIG.health_score.recovery_rate_per_hour,
|
|
240
|
-
),
|
|
241
|
-
min_usable: clampNumber(hs.min_usable, 0, 100, DEFAULT_CONFIG.health_score.min_usable),
|
|
242
|
-
max_score: clampNumber(hs.max_score, 50, 100, DEFAULT_CONFIG.health_score.max_score),
|
|
243
|
-
};
|
|
244
|
-
}
|
|
171
|
+
const config = createDefaultConfig();
|
|
245
172
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (raw.token_bucket && typeof raw.token_bucket === "object") {
|
|
255
|
-
const tb = raw.token_bucket as Record<string, unknown>;
|
|
256
|
-
config.token_bucket = {
|
|
257
|
-
max_tokens: clampNumber(tb.max_tokens, 1, 1000, DEFAULT_CONFIG.token_bucket.max_tokens),
|
|
258
|
-
regeneration_rate_per_minute: clampNumber(
|
|
259
|
-
tb.regeneration_rate_per_minute,
|
|
260
|
-
0.1,
|
|
261
|
-
60,
|
|
262
|
-
DEFAULT_CONFIG.token_bucket.regeneration_rate_per_minute,
|
|
263
|
-
),
|
|
264
|
-
initial_tokens: clampNumber(tb.initial_tokens, 1, 1000, DEFAULT_CONFIG.token_bucket.initial_tokens),
|
|
265
|
-
};
|
|
266
|
-
}
|
|
173
|
+
if (
|
|
174
|
+
typeof raw.account_selection_strategy === "string" &&
|
|
175
|
+
VALID_STRATEGIES.includes(raw.account_selection_strategy as AccountSelectionStrategy)
|
|
176
|
+
) {
|
|
177
|
+
config.account_selection_strategy = raw.account_selection_strategy as AccountSelectionStrategy;
|
|
178
|
+
}
|
|
267
179
|
|
|
268
|
-
|
|
269
|
-
const h = raw.headers as Record<string, unknown>;
|
|
180
|
+
config.failure_ttl_seconds = clampNumber(raw.failure_ttl_seconds, 60, 7200, DEFAULT_CONFIG.failure_ttl_seconds);
|
|
270
181
|
|
|
271
|
-
if (typeof
|
|
272
|
-
|
|
182
|
+
if (typeof raw.debug === "boolean") {
|
|
183
|
+
config.debug = raw.debug;
|
|
273
184
|
}
|
|
274
185
|
|
|
275
|
-
if (
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
186
|
+
if (raw.signature_emulation && typeof raw.signature_emulation === "object") {
|
|
187
|
+
const se = raw.signature_emulation as Record<string, unknown>;
|
|
188
|
+
config.signature_emulation = {
|
|
189
|
+
enabled: typeof se.enabled === "boolean" ? se.enabled : DEFAULT_CONFIG.signature_emulation.enabled,
|
|
190
|
+
fetch_claude_code_version_on_startup:
|
|
191
|
+
typeof se.fetch_claude_code_version_on_startup === "boolean"
|
|
192
|
+
? se.fetch_claude_code_version_on_startup
|
|
193
|
+
: DEFAULT_CONFIG.signature_emulation.fetch_claude_code_version_on_startup,
|
|
194
|
+
prompt_compaction:
|
|
195
|
+
se.prompt_compaction === "off" || se.prompt_compaction === "minimal"
|
|
196
|
+
? se.prompt_compaction
|
|
197
|
+
: DEFAULT_CONFIG.signature_emulation.prompt_compaction,
|
|
198
|
+
sanitize_system_prompt:
|
|
199
|
+
typeof se.sanitize_system_prompt === "boolean"
|
|
200
|
+
? se.sanitize_system_prompt
|
|
201
|
+
: DEFAULT_CONFIG.signature_emulation.sanitize_system_prompt,
|
|
202
|
+
};
|
|
284
203
|
}
|
|
285
204
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
205
|
+
// Top-level alias: `sanitize_system_prompt` is honored as a convenience so
|
|
206
|
+
// users can flip it on/off without learning the nested signature_emulation
|
|
207
|
+
// schema. The top-level value, when set, takes precedence over the nested
|
|
208
|
+
// one because it's the more specific user intent.
|
|
209
|
+
if (typeof raw.sanitize_system_prompt === "boolean") {
|
|
210
|
+
config.signature_emulation.sanitize_system_prompt = raw.sanitize_system_prompt;
|
|
291
211
|
}
|
|
292
212
|
|
|
293
|
-
if (typeof
|
|
294
|
-
|
|
213
|
+
if (raw.override_model_limits && typeof raw.override_model_limits === "object") {
|
|
214
|
+
const oml = raw.override_model_limits as Record<string, unknown>;
|
|
215
|
+
config.override_model_limits = {
|
|
216
|
+
enabled: typeof oml.enabled === "boolean" ? oml.enabled : DEFAULT_CONFIG.override_model_limits.enabled,
|
|
217
|
+
context: clampNumber(oml.context, 200_000, 2_000_000, DEFAULT_CONFIG.override_model_limits.context),
|
|
218
|
+
output: clampNumber(oml.output, 0, 128_000, DEFAULT_CONFIG.override_model_limits.output),
|
|
219
|
+
};
|
|
295
220
|
}
|
|
296
|
-
}
|
|
297
221
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
222
|
+
if (Array.isArray(raw.custom_betas)) {
|
|
223
|
+
config.custom_betas = (raw.custom_betas as unknown[])
|
|
224
|
+
.filter((b): b is string => typeof b === "string" && b.trim().length > 0)
|
|
225
|
+
.map((b) => b.trim());
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (raw.health_score && typeof raw.health_score === "object") {
|
|
229
|
+
const hs = raw.health_score as Record<string, unknown>;
|
|
230
|
+
config.health_score = {
|
|
231
|
+
initial: clampNumber(hs.initial, 0, 100, DEFAULT_CONFIG.health_score.initial),
|
|
232
|
+
success_reward: clampNumber(hs.success_reward, 0, 10, DEFAULT_CONFIG.health_score.success_reward),
|
|
233
|
+
rate_limit_penalty: clampNumber(
|
|
234
|
+
hs.rate_limit_penalty,
|
|
235
|
+
-50,
|
|
236
|
+
0,
|
|
237
|
+
DEFAULT_CONFIG.health_score.rate_limit_penalty,
|
|
238
|
+
),
|
|
239
|
+
failure_penalty: clampNumber(hs.failure_penalty, -100, 0, DEFAULT_CONFIG.health_score.failure_penalty),
|
|
240
|
+
recovery_rate_per_hour: clampNumber(
|
|
241
|
+
hs.recovery_rate_per_hour,
|
|
242
|
+
0,
|
|
243
|
+
20,
|
|
244
|
+
DEFAULT_CONFIG.health_score.recovery_rate_per_hour,
|
|
245
|
+
),
|
|
246
|
+
min_usable: clampNumber(hs.min_usable, 0, 100, DEFAULT_CONFIG.health_score.min_usable),
|
|
247
|
+
max_score: clampNumber(hs.max_score, 50, 100, DEFAULT_CONFIG.health_score.max_score),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
324
250
|
|
|
325
|
-
|
|
251
|
+
if (raw.toasts && typeof raw.toasts === "object") {
|
|
252
|
+
const t = raw.toasts as Record<string, unknown>;
|
|
253
|
+
config.toasts = {
|
|
254
|
+
quiet: typeof t.quiet === "boolean" ? t.quiet : DEFAULT_CONFIG.toasts.quiet,
|
|
255
|
+
debounce_seconds: clampNumber(t.debounce_seconds, 0, 300, DEFAULT_CONFIG.toasts.debounce_seconds),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (raw.token_bucket && typeof raw.token_bucket === "object") {
|
|
260
|
+
const tb = raw.token_bucket as Record<string, unknown>;
|
|
261
|
+
config.token_bucket = {
|
|
262
|
+
max_tokens: clampNumber(tb.max_tokens, 1, 1000, DEFAULT_CONFIG.token_bucket.max_tokens),
|
|
263
|
+
regeneration_rate_per_minute: clampNumber(
|
|
264
|
+
tb.regeneration_rate_per_minute,
|
|
265
|
+
0.1,
|
|
266
|
+
60,
|
|
267
|
+
DEFAULT_CONFIG.token_bucket.regeneration_rate_per_minute,
|
|
268
|
+
),
|
|
269
|
+
initial_tokens: clampNumber(tb.initial_tokens, 1, 1000, DEFAULT_CONFIG.token_bucket.initial_tokens),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (raw.headers && typeof raw.headers === "object") {
|
|
274
|
+
const h = raw.headers as Record<string, unknown>;
|
|
275
|
+
|
|
276
|
+
if (typeof h.emulation_profile === "string" && h.emulation_profile.trim()) {
|
|
277
|
+
config.headers.emulation_profile = h.emulation_profile.trim();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (h.overrides && typeof h.overrides === "object" && !Array.isArray(h.overrides)) {
|
|
281
|
+
const overrides: Record<string, string> = {};
|
|
282
|
+
for (const [key, value] of Object.entries(h.overrides as Record<string, unknown>)) {
|
|
283
|
+
if (!key) continue;
|
|
284
|
+
if (typeof value === "string") {
|
|
285
|
+
overrides[key] = value;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
config.headers.overrides = overrides;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (Array.isArray(h.disable)) {
|
|
292
|
+
config.headers.disable = (h.disable as unknown[])
|
|
293
|
+
.filter((v): v is string => typeof v === "string")
|
|
294
|
+
.map((v) => v.trim().toLowerCase())
|
|
295
|
+
.filter(Boolean);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (typeof h.billing_header === "boolean") {
|
|
299
|
+
config.headers.billing_header = h.billing_header;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (raw.idle_refresh && typeof raw.idle_refresh === "object") {
|
|
304
|
+
const ir = raw.idle_refresh as Record<string, unknown>;
|
|
305
|
+
config.idle_refresh = {
|
|
306
|
+
enabled: typeof ir.enabled === "boolean" ? ir.enabled : DEFAULT_CONFIG.idle_refresh.enabled,
|
|
307
|
+
window_minutes: clampNumber(ir.window_minutes, 1, 24 * 60, DEFAULT_CONFIG.idle_refresh.window_minutes),
|
|
308
|
+
min_interval_minutes: clampNumber(
|
|
309
|
+
ir.min_interval_minutes,
|
|
310
|
+
1,
|
|
311
|
+
24 * 60,
|
|
312
|
+
DEFAULT_CONFIG.idle_refresh.min_interval_minutes,
|
|
313
|
+
),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (raw.cc_credential_reuse && typeof raw.cc_credential_reuse === "object") {
|
|
318
|
+
const ccr = raw.cc_credential_reuse as Record<string, unknown>;
|
|
319
|
+
config.cc_credential_reuse = {
|
|
320
|
+
enabled: typeof ccr.enabled === "boolean" ? ccr.enabled : DEFAULT_CONFIG.cc_credential_reuse.enabled,
|
|
321
|
+
auto_detect:
|
|
322
|
+
typeof ccr.auto_detect === "boolean" ? ccr.auto_detect : DEFAULT_CONFIG.cc_credential_reuse.auto_detect,
|
|
323
|
+
prefer_over_oauth:
|
|
324
|
+
typeof ccr.prefer_over_oauth === "boolean"
|
|
325
|
+
? ccr.prefer_over_oauth
|
|
326
|
+
: DEFAULT_CONFIG.cc_credential_reuse.prefer_over_oauth,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return config;
|
|
326
331
|
}
|
|
327
332
|
|
|
328
333
|
function applyEnvOverrides(config: AnthropicAuthConfig): AnthropicAuthConfig {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
334
|
+
const env = process.env;
|
|
335
|
+
|
|
336
|
+
if (
|
|
337
|
+
env.OPENCODE_ANTHROPIC_STRATEGY &&
|
|
338
|
+
VALID_STRATEGIES.includes(env.OPENCODE_ANTHROPIC_STRATEGY as AccountSelectionStrategy)
|
|
339
|
+
) {
|
|
340
|
+
config.account_selection_strategy = env.OPENCODE_ANTHROPIC_STRATEGY as AccountSelectionStrategy;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (env.OPENCODE_ANTHROPIC_DEBUG === "1" || env.OPENCODE_ANTHROPIC_DEBUG === "true") {
|
|
344
|
+
config.debug = true;
|
|
345
|
+
}
|
|
346
|
+
if (env.OPENCODE_ANTHROPIC_DEBUG === "0" || env.OPENCODE_ANTHROPIC_DEBUG === "false") {
|
|
347
|
+
config.debug = false;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (env.OPENCODE_ANTHROPIC_QUIET === "1" || env.OPENCODE_ANTHROPIC_QUIET === "true") {
|
|
351
|
+
config.toasts.quiet = true;
|
|
352
|
+
}
|
|
353
|
+
if (env.OPENCODE_ANTHROPIC_QUIET === "0" || env.OPENCODE_ANTHROPIC_QUIET === "false") {
|
|
354
|
+
config.toasts.quiet = false;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (
|
|
358
|
+
env.OPENCODE_ANTHROPIC_EMULATE_CLAUDE_CODE_SIGNATURE === "1" ||
|
|
359
|
+
env.OPENCODE_ANTHROPIC_EMULATE_CLAUDE_CODE_SIGNATURE === "true"
|
|
360
|
+
) {
|
|
361
|
+
config.signature_emulation.enabled = true;
|
|
362
|
+
}
|
|
363
|
+
if (
|
|
364
|
+
env.OPENCODE_ANTHROPIC_EMULATE_CLAUDE_CODE_SIGNATURE === "0" ||
|
|
365
|
+
env.OPENCODE_ANTHROPIC_EMULATE_CLAUDE_CODE_SIGNATURE === "false"
|
|
366
|
+
) {
|
|
367
|
+
config.signature_emulation.enabled = false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (
|
|
371
|
+
env.OPENCODE_ANTHROPIC_FETCH_CLAUDE_CODE_VERSION === "1" ||
|
|
372
|
+
env.OPENCODE_ANTHROPIC_FETCH_CLAUDE_CODE_VERSION === "true"
|
|
373
|
+
) {
|
|
374
|
+
config.signature_emulation.fetch_claude_code_version_on_startup = true;
|
|
375
|
+
}
|
|
376
|
+
if (
|
|
377
|
+
env.OPENCODE_ANTHROPIC_FETCH_CLAUDE_CODE_VERSION === "0" ||
|
|
378
|
+
env.OPENCODE_ANTHROPIC_FETCH_CLAUDE_CODE_VERSION === "false"
|
|
379
|
+
) {
|
|
380
|
+
config.signature_emulation.fetch_claude_code_version_on_startup = false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (env.OPENCODE_ANTHROPIC_PROMPT_COMPACTION === "off") {
|
|
384
|
+
config.signature_emulation.prompt_compaction = "off";
|
|
385
|
+
}
|
|
386
|
+
if (env.OPENCODE_ANTHROPIC_PROMPT_COMPACTION === "minimal") {
|
|
387
|
+
config.signature_emulation.prompt_compaction = "minimal";
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (
|
|
391
|
+
env.OPENCODE_ANTHROPIC_SANITIZE_SYSTEM_PROMPT === "1" ||
|
|
392
|
+
env.OPENCODE_ANTHROPIC_SANITIZE_SYSTEM_PROMPT === "true"
|
|
393
|
+
) {
|
|
394
|
+
config.signature_emulation.sanitize_system_prompt = true;
|
|
395
|
+
}
|
|
396
|
+
if (
|
|
397
|
+
env.OPENCODE_ANTHROPIC_SANITIZE_SYSTEM_PROMPT === "0" ||
|
|
398
|
+
env.OPENCODE_ANTHROPIC_SANITIZE_SYSTEM_PROMPT === "false"
|
|
399
|
+
) {
|
|
400
|
+
config.signature_emulation.sanitize_system_prompt = false;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (
|
|
404
|
+
env.OPENCODE_ANTHROPIC_OVERRIDE_MODEL_LIMITS === "1" ||
|
|
405
|
+
env.OPENCODE_ANTHROPIC_OVERRIDE_MODEL_LIMITS === "true"
|
|
406
|
+
) {
|
|
407
|
+
config.override_model_limits.enabled = true;
|
|
408
|
+
}
|
|
409
|
+
if (
|
|
410
|
+
env.OPENCODE_ANTHROPIC_OVERRIDE_MODEL_LIMITS === "0" ||
|
|
411
|
+
env.OPENCODE_ANTHROPIC_OVERRIDE_MODEL_LIMITS === "false"
|
|
412
|
+
) {
|
|
413
|
+
config.override_model_limits.enabled = false;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (env.OPENCODE_ANTHROPIC_CC_REUSE_ENABLED === "0" || env.OPENCODE_ANTHROPIC_CC_REUSE_ENABLED === "false") {
|
|
417
|
+
config.cc_credential_reuse.enabled = false;
|
|
418
|
+
config.cc_credential_reuse.auto_detect = false;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return config;
|
|
414
422
|
}
|
|
415
423
|
|
|
416
424
|
/**
|
|
417
425
|
* Load config from disk, validate, apply env overrides.
|
|
418
426
|
*/
|
|
419
427
|
export function loadConfig(): AnthropicAuthConfig {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
428
|
+
const configPath = getConfigPath();
|
|
429
|
+
|
|
430
|
+
if (!existsSync(configPath)) {
|
|
431
|
+
return applyEnvOverrides(createDefaultConfig());
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const content = readFileSync(configPath, "utf-8");
|
|
436
|
+
const raw = JSON.parse(content);
|
|
437
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
438
|
+
return applyEnvOverrides(createDefaultConfig());
|
|
439
|
+
}
|
|
440
|
+
const config = validateConfig(raw as Record<string, unknown>);
|
|
441
|
+
return applyEnvOverrides(config);
|
|
442
|
+
} catch {
|
|
443
|
+
return applyEnvOverrides(createDefaultConfig());
|
|
444
|
+
}
|
|
437
445
|
}
|
|
438
446
|
|
|
439
447
|
/**
|
|
@@ -441,38 +449,38 @@ export function loadConfig(): AnthropicAuthConfig {
|
|
|
441
449
|
* Returns an empty object if the file doesn't exist or is invalid.
|
|
442
450
|
*/
|
|
443
451
|
export function loadRawConfig(): Record<string, unknown> {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
452
|
+
const configPath = getConfigPath();
|
|
453
|
+
if (!existsSync(configPath)) return {};
|
|
454
|
+
try {
|
|
455
|
+
const content = readFileSync(configPath, "utf-8");
|
|
456
|
+
const raw = JSON.parse(content);
|
|
457
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
458
|
+
return raw as Record<string, unknown>;
|
|
459
|
+
} catch {
|
|
460
|
+
return {};
|
|
461
|
+
}
|
|
454
462
|
}
|
|
455
463
|
|
|
456
464
|
function deepMergeConfig(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
465
|
+
const result = { ...target };
|
|
466
|
+
for (const [key, value] of Object.entries(source)) {
|
|
467
|
+
if (
|
|
468
|
+
value &&
|
|
469
|
+
typeof value === "object" &&
|
|
470
|
+
!Array.isArray(value) &&
|
|
471
|
+
typeof result[key] === "object" &&
|
|
472
|
+
result[key] &&
|
|
473
|
+
!Array.isArray(result[key])
|
|
474
|
+
) {
|
|
475
|
+
result[key] = {
|
|
476
|
+
...(result[key] as Record<string, unknown>),
|
|
477
|
+
...(value as Record<string, unknown>),
|
|
478
|
+
};
|
|
479
|
+
} else {
|
|
480
|
+
result[key] = value;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return result;
|
|
476
484
|
}
|
|
477
485
|
|
|
478
486
|
/**
|
|
@@ -481,24 +489,24 @@ function deepMergeConfig(target: Record<string, unknown>, source: Record<string,
|
|
|
481
489
|
* Uses atomic write (temp + rename) for safety.
|
|
482
490
|
*/
|
|
483
491
|
export function saveConfig(updates: Record<string, unknown>): void {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
492
|
+
const configPath = getConfigPath();
|
|
493
|
+
const dir = dirname(configPath);
|
|
494
|
+
mkdirSync(dir, { recursive: true });
|
|
495
|
+
|
|
496
|
+
const existing = loadRawConfig();
|
|
497
|
+
const merged = deepMergeConfig(existing, updates);
|
|
498
|
+
|
|
499
|
+
const tmpPath = configPath + `.tmp.${process.pid}`;
|
|
500
|
+
writeFileSync(tmpPath, JSON.stringify(merged, null, 2) + "\n", {
|
|
501
|
+
encoding: "utf-8",
|
|
502
|
+
mode: 0o600,
|
|
503
|
+
});
|
|
504
|
+
renameSync(tmpPath, configPath);
|
|
497
505
|
}
|
|
498
506
|
|
|
499
507
|
/**
|
|
500
508
|
* Load config fresh from disk (bypassing any startup cache).
|
|
501
509
|
*/
|
|
502
510
|
export function loadConfigFresh(): AnthropicAuthConfig {
|
|
503
|
-
|
|
511
|
+
return loadConfig();
|
|
504
512
|
}
|