oc-auth-switcher 0.9.2 → 0.9.4
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/dist/cli.js +8 -3
- package/dist/index.js +259 -17
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -49,6 +49,11 @@ var OAUTH_SCOPES = [
|
|
|
49
49
|
"user:mcp_servers",
|
|
50
50
|
"user:file_upload"
|
|
51
51
|
];
|
|
52
|
+
var CLAUDE_CODE_VERSION = "2.1.280";
|
|
53
|
+
function formatUserAgent(version) {
|
|
54
|
+
return `claude-cli/${version} (external, cli)`;
|
|
55
|
+
}
|
|
56
|
+
var USER_AGENT = formatUserAgent(CLAUDE_CODE_VERSION);
|
|
52
57
|
|
|
53
58
|
// src/accounts.ts
|
|
54
59
|
function isAccessTokenFresh(account, nowMs = Date.now()) {
|
|
@@ -1063,7 +1068,7 @@ var PROBE_MODELS = [
|
|
|
1063
1068
|
var PROBE_BETA_HEADERS = "oauth-2025-04-20,interleaved-thinking-2025-05-14";
|
|
1064
1069
|
var PROBE_USER_AGENT = "claude-cli/2.1.87 (external, cli)";
|
|
1065
1070
|
var PROBE_VERSION = "2023-06-01";
|
|
1066
|
-
var
|
|
1071
|
+
var CLAUDE_CODE_VERSION2 = "2.1.87";
|
|
1067
1072
|
var CLAUDE_CODE_IDENTITY = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
1068
1073
|
var CCH_SALT = "59cf53e54c78";
|
|
1069
1074
|
var CCH_POSITIONS = [4, 7, 20];
|
|
@@ -1072,12 +1077,12 @@ function computeCCH(messageText) {
|
|
|
1072
1077
|
}
|
|
1073
1078
|
function computeVersionSuffix(messageText) {
|
|
1074
1079
|
const chars = CCH_POSITIONS.map((i) => messageText[i] || "0").join("");
|
|
1075
|
-
return createHash("sha256").update(`${CCH_SALT}${chars}${
|
|
1080
|
+
return createHash("sha256").update(`${CCH_SALT}${chars}${CLAUDE_CODE_VERSION2}`).digest("hex").slice(0, 3);
|
|
1076
1081
|
}
|
|
1077
1082
|
function buildBillingHeader(messageText) {
|
|
1078
1083
|
const suffix = computeVersionSuffix(messageText);
|
|
1079
1084
|
const cch = computeCCH(messageText);
|
|
1080
|
-
return "x-anthropic-billing-header: " + `cc_version=${
|
|
1085
|
+
return "x-anthropic-billing-header: " + `cc_version=${CLAUDE_CODE_VERSION2}.${suffix}; ` + `cc_entrypoint=sdk-cli; ` + `cch=${cch};`;
|
|
1081
1086
|
}
|
|
1082
1087
|
async function fetchAndApplyOAuthUsage(account, state, result) {
|
|
1083
1088
|
const oauthResult = await fetchOAuthUsage(account.access);
|
package/dist/index.js
CHANGED
|
@@ -26,9 +26,12 @@ var OPENCODE_IDENTITY_PREFIX = "You are OpenCode";
|
|
|
26
26
|
var CLAUDE_CODE_IDENTITY = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
27
27
|
var CCH_SALT = "59cf53e54c78";
|
|
28
28
|
var CCH_POSITIONS = [4, 7, 20];
|
|
29
|
-
var CLAUDE_CODE_VERSION = "2.1.
|
|
29
|
+
var CLAUDE_CODE_VERSION = "2.1.280";
|
|
30
30
|
var CLAUDE_CODE_ENTRYPOINT = "sdk-cli";
|
|
31
|
-
|
|
31
|
+
function formatUserAgent(version) {
|
|
32
|
+
return `claude-cli/${version} (external, cli)`;
|
|
33
|
+
}
|
|
34
|
+
var USER_AGENT = formatUserAgent(CLAUDE_CODE_VERSION);
|
|
32
35
|
var PARAGRAPH_REMOVAL_ANCHORS = [
|
|
33
36
|
"github.com/anomalyco/opencode",
|
|
34
37
|
"opencode.ai/docs"
|
|
@@ -71,6 +74,193 @@ function buildBillingHeaderValue(messages, version = CLAUDE_CODE_VERSION, entryp
|
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
// node_modules/@ex-machina/opencode-anthropic-auth/dist/transform.js
|
|
77
|
+
var MAX_SSE_LINE_BYTES = 5 * 1024 * 1024;
|
|
78
|
+
function headersAfterBodyTransform(source) {
|
|
79
|
+
const headers = new Headers(source);
|
|
80
|
+
for (const name of [
|
|
81
|
+
"content-digest",
|
|
82
|
+
"content-encoding",
|
|
83
|
+
"content-length",
|
|
84
|
+
"content-md5",
|
|
85
|
+
"content-range",
|
|
86
|
+
"digest",
|
|
87
|
+
"etag"
|
|
88
|
+
]) {
|
|
89
|
+
headers.delete(name);
|
|
90
|
+
}
|
|
91
|
+
return headers;
|
|
92
|
+
}
|
|
93
|
+
var JSON_NAME_KEY_SUFFIX = new TextEncoder().encode('name"');
|
|
94
|
+
var JSON_TOOL_PREFIX = new TextEncoder().encode(TOOL_PREFIX);
|
|
95
|
+
var UTF8_ENCODER = new TextEncoder;
|
|
96
|
+
var UTF8_FATAL_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
97
|
+
var MAX_JSON_TOOL_NAME_BYTES = 1024;
|
|
98
|
+
function isJsonWhitespace(byte) {
|
|
99
|
+
return byte === 32 || byte === 9 || byte === 10 || byte === 13;
|
|
100
|
+
}
|
|
101
|
+
function createJsonToolNameStream(body) {
|
|
102
|
+
let state = "outside";
|
|
103
|
+
let held = [];
|
|
104
|
+
let candidateIndex = 0;
|
|
105
|
+
let escaped = false;
|
|
106
|
+
const enterStringAfter = (byte) => {
|
|
107
|
+
if (byte === 34) {
|
|
108
|
+
state = "outside";
|
|
109
|
+
escaped = false;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
state = "string";
|
|
113
|
+
escaped = byte === 92;
|
|
114
|
+
};
|
|
115
|
+
return body.pipeThrough(new TransformStream({
|
|
116
|
+
transform(chunk, controller) {
|
|
117
|
+
const output = new Uint8Array(chunk.byteLength + 32);
|
|
118
|
+
let outputLength = 0;
|
|
119
|
+
const write = (byte) => {
|
|
120
|
+
output[outputLength++] = byte;
|
|
121
|
+
};
|
|
122
|
+
const enqueueOutput = () => {
|
|
123
|
+
if (outputLength === 0)
|
|
124
|
+
return;
|
|
125
|
+
controller.enqueue(output.slice(0, outputLength));
|
|
126
|
+
outputLength = 0;
|
|
127
|
+
};
|
|
128
|
+
const writeHeld = () => {
|
|
129
|
+
for (const byte of held)
|
|
130
|
+
write(byte);
|
|
131
|
+
held = [];
|
|
132
|
+
};
|
|
133
|
+
const processOutside = (byte) => {
|
|
134
|
+
if (byte === 34) {
|
|
135
|
+
held = [byte];
|
|
136
|
+
candidateIndex = 0;
|
|
137
|
+
state = "key-candidate";
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
write(byte);
|
|
141
|
+
};
|
|
142
|
+
for (const byte of chunk) {
|
|
143
|
+
if (state === "outside") {
|
|
144
|
+
processOutside(byte);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (state === "key-candidate") {
|
|
148
|
+
if (byte === JSON_NAME_KEY_SUFFIX[candidateIndex]) {
|
|
149
|
+
held.push(byte);
|
|
150
|
+
candidateIndex++;
|
|
151
|
+
if (candidateIndex === JSON_NAME_KEY_SUFFIX.byteLength) {
|
|
152
|
+
writeHeld();
|
|
153
|
+
state = "after-name-key";
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
writeHeld();
|
|
158
|
+
write(byte);
|
|
159
|
+
enterStringAfter(byte);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (state === "string") {
|
|
163
|
+
write(byte);
|
|
164
|
+
if (escaped) {
|
|
165
|
+
escaped = false;
|
|
166
|
+
} else if (byte === 92) {
|
|
167
|
+
escaped = true;
|
|
168
|
+
} else if (byte === 34) {
|
|
169
|
+
state = "outside";
|
|
170
|
+
}
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (state === "after-name-key") {
|
|
174
|
+
if (isJsonWhitespace(byte)) {
|
|
175
|
+
write(byte);
|
|
176
|
+
} else if (byte === 58) {
|
|
177
|
+
write(byte);
|
|
178
|
+
state = "after-colon";
|
|
179
|
+
} else {
|
|
180
|
+
processOutside(byte);
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (state === "after-colon") {
|
|
185
|
+
if (isJsonWhitespace(byte)) {
|
|
186
|
+
write(byte);
|
|
187
|
+
} else if (byte === 34) {
|
|
188
|
+
write(byte);
|
|
189
|
+
held = [];
|
|
190
|
+
candidateIndex = 0;
|
|
191
|
+
state = "prefix-candidate";
|
|
192
|
+
} else {
|
|
193
|
+
processOutside(byte);
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (state === "prefix-candidate") {
|
|
198
|
+
if (byte === JSON_TOOL_PREFIX[candidateIndex]) {
|
|
199
|
+
held.push(byte);
|
|
200
|
+
candidateIndex++;
|
|
201
|
+
if (candidateIndex === JSON_TOOL_PREFIX.byteLength) {
|
|
202
|
+
held = [];
|
|
203
|
+
candidateIndex = 0;
|
|
204
|
+
escaped = false;
|
|
205
|
+
state = "tool-name-candidate";
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
writeHeld();
|
|
210
|
+
write(byte);
|
|
211
|
+
enterStringAfter(byte);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (escaped) {
|
|
215
|
+
held.push(byte);
|
|
216
|
+
escaped = false;
|
|
217
|
+
if (held.length > MAX_JSON_TOOL_NAME_BYTES) {
|
|
218
|
+
throw new Error(`JSON tool name exceeds ${MAX_JSON_TOOL_NAME_BYTES} byte limit`);
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (byte === 92) {
|
|
223
|
+
held.push(byte);
|
|
224
|
+
escaped = true;
|
|
225
|
+
if (held.length > MAX_JSON_TOOL_NAME_BYTES) {
|
|
226
|
+
throw new Error(`JSON tool name exceeds ${MAX_JSON_TOOL_NAME_BYTES} byte limit`);
|
|
227
|
+
}
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (byte === 34) {
|
|
231
|
+
let replacement;
|
|
232
|
+
if (held.length === 0) {
|
|
233
|
+
replacement = JSON_TOOL_PREFIX;
|
|
234
|
+
} else {
|
|
235
|
+
try {
|
|
236
|
+
replacement = UTF8_ENCODER.encode(unprefixName(UTF8_FATAL_DECODER.decode(Uint8Array.from(held))));
|
|
237
|
+
} catch {
|
|
238
|
+
replacement = Uint8Array.from([...JSON_TOOL_PREFIX, ...held]);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
enqueueOutput();
|
|
242
|
+
controller.enqueue(replacement);
|
|
243
|
+
write(byte);
|
|
244
|
+
held = [];
|
|
245
|
+
candidateIndex = 0;
|
|
246
|
+
state = "outside";
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
held.push(byte);
|
|
250
|
+
if (held.length > MAX_JSON_TOOL_NAME_BYTES) {
|
|
251
|
+
throw new Error(`JSON tool name exceeds ${MAX_JSON_TOOL_NAME_BYTES} byte limit`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
enqueueOutput();
|
|
255
|
+
},
|
|
256
|
+
flush(controller) {
|
|
257
|
+
const trailing = Uint8Array.from(state === "tool-name-candidate" ? [...JSON_TOOL_PREFIX, ...held] : held);
|
|
258
|
+
if (trailing.byteLength === 0)
|
|
259
|
+
return;
|
|
260
|
+
controller.enqueue(trailing);
|
|
261
|
+
}
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
74
264
|
function prefixName(name) {
|
|
75
265
|
return `${TOOL_PREFIX}${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
76
266
|
}
|
|
@@ -115,10 +305,10 @@ function mergeBetaHeaders(headers) {
|
|
|
115
305
|
const incomingBetasList = incomingBeta.split(",").map((b) => b.trim()).filter(Boolean);
|
|
116
306
|
return [...new Set([...REQUIRED_BETAS, ...incomingBetasList])].join(",");
|
|
117
307
|
}
|
|
118
|
-
function setOAuthHeaders(headers, accessToken) {
|
|
308
|
+
function setOAuthHeaders(headers, accessToken, version = CLAUDE_CODE_VERSION) {
|
|
119
309
|
headers.set("authorization", `Bearer ${accessToken}`);
|
|
120
310
|
headers.set("anthropic-beta", mergeBetaHeaders(headers));
|
|
121
|
-
headers.set("user-agent",
|
|
311
|
+
headers.set("user-agent", formatUserAgent(version));
|
|
122
312
|
headers.delete("x-api-key");
|
|
123
313
|
return headers;
|
|
124
314
|
}
|
|
@@ -256,10 +446,10 @@ function prependClaudeCodeIdentity(system) {
|
|
|
256
446
|
}
|
|
257
447
|
return [identityBlock, ...sanitized];
|
|
258
448
|
}
|
|
259
|
-
function rewriteRequestBody(body) {
|
|
449
|
+
function rewriteRequestBody(body, version = CLAUDE_CODE_VERSION) {
|
|
260
450
|
try {
|
|
261
451
|
const parsed = JSON.parse(body);
|
|
262
|
-
const billingHeader = Array.isArray(parsed.messages) && parsed.messages.some((message) => message.role === "user") ? buildBillingHeaderValue(parsed.messages,
|
|
452
|
+
const billingHeader = Array.isArray(parsed.messages) && parsed.messages.some((message) => message.role === "user") ? buildBillingHeaderValue(parsed.messages, version, CLAUDE_CODE_ENTRYPOINT) : null;
|
|
263
453
|
parsed.system = prependClaudeCodeIdentity(parsed.system);
|
|
264
454
|
if (billingHeader && Array.isArray(parsed.system)) {
|
|
265
455
|
parsed.system.unshift({ type: "text", text: billingHeader });
|
|
@@ -270,27 +460,79 @@ function rewriteRequestBody(body) {
|
|
|
270
460
|
}
|
|
271
461
|
}
|
|
272
462
|
function createStrippedStream(response) {
|
|
463
|
+
const mediaType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
273
464
|
if (!response.body)
|
|
274
465
|
return response;
|
|
275
|
-
|
|
466
|
+
if (mediaType === "application/json" || mediaType?.endsWith("+json")) {
|
|
467
|
+
const stream2 = createJsonToolNameStream(response.body);
|
|
468
|
+
const headers2 = headersAfterBodyTransform(response.headers);
|
|
469
|
+
return new Response(stream2, {
|
|
470
|
+
status: response.status,
|
|
471
|
+
statusText: response.statusText,
|
|
472
|
+
headers: headers2
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (mediaType !== "text/event-stream")
|
|
476
|
+
return response;
|
|
276
477
|
const decoder = new TextDecoder;
|
|
277
478
|
const encoder = new TextEncoder;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
479
|
+
let pending = new Uint8Array(0);
|
|
480
|
+
let pendingLength = 0;
|
|
481
|
+
const appendPending = (bytes) => {
|
|
482
|
+
const requiredLength = pendingLength + bytes.byteLength;
|
|
483
|
+
if (requiredLength > MAX_SSE_LINE_BYTES) {
|
|
484
|
+
throw new Error(`SSE line exceeds ${MAX_SSE_LINE_BYTES} byte limit`);
|
|
485
|
+
}
|
|
486
|
+
if (requiredLength > pending.byteLength) {
|
|
487
|
+
let capacity = Math.max(1024, pending.byteLength);
|
|
488
|
+
while (capacity < requiredLength) {
|
|
489
|
+
capacity = Math.min(MAX_SSE_LINE_BYTES, capacity * 2);
|
|
490
|
+
}
|
|
491
|
+
const expanded = new Uint8Array(capacity);
|
|
492
|
+
expanded.set(pending.subarray(0, pendingLength));
|
|
493
|
+
pending = expanded;
|
|
494
|
+
}
|
|
495
|
+
pending.set(bytes, pendingLength);
|
|
496
|
+
pendingLength = requiredLength;
|
|
497
|
+
};
|
|
498
|
+
const stream = response.body.pipeThrough(new TransformStream({
|
|
499
|
+
transform(chunk, controller) {
|
|
500
|
+
let lastLineBreak = -1;
|
|
501
|
+
let lineLength = pendingLength;
|
|
502
|
+
for (let index = 0;index < chunk.byteLength; index++) {
|
|
503
|
+
if (chunk[index] === 10 || chunk[index] === 13) {
|
|
504
|
+
lastLineBreak = index;
|
|
505
|
+
lineLength = 0;
|
|
506
|
+
} else {
|
|
507
|
+
lineLength++;
|
|
508
|
+
if (lineLength > MAX_SSE_LINE_BYTES) {
|
|
509
|
+
throw new Error(`SSE line exceeds ${MAX_SSE_LINE_BYTES} byte limit`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (lastLineBreak < 0) {
|
|
514
|
+
appendPending(chunk);
|
|
283
515
|
return;
|
|
284
516
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
517
|
+
const completeLines = decoder.decode(pending.subarray(0, pendingLength), {
|
|
518
|
+
stream: true
|
|
519
|
+
}) + decoder.decode(chunk.subarray(0, lastLineBreak + 1));
|
|
520
|
+
pendingLength = 0;
|
|
521
|
+
appendPending(chunk.subarray(lastLineBreak + 1));
|
|
522
|
+
controller.enqueue(encoder.encode(stripToolPrefix(completeLines)));
|
|
523
|
+
},
|
|
524
|
+
flush(controller) {
|
|
525
|
+
const trailing = decoder.decode(pending.subarray(0, pendingLength));
|
|
526
|
+
if (trailing) {
|
|
527
|
+
controller.enqueue(encoder.encode(stripToolPrefix(trailing)));
|
|
528
|
+
}
|
|
288
529
|
}
|
|
289
|
-
});
|
|
530
|
+
}));
|
|
531
|
+
const headers = headersAfterBodyTransform(response.headers);
|
|
290
532
|
return new Response(stream, {
|
|
291
533
|
status: response.status,
|
|
292
534
|
statusText: response.statusText,
|
|
293
|
-
headers
|
|
535
|
+
headers
|
|
294
536
|
});
|
|
295
537
|
}
|
|
296
538
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oc-auth-switcher",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "OpenCode auth plugin for multi-account Anthropic Claude Max rotation with automatic failover.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@opencode-ai/plugin": "*"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ex-machina/opencode-anthropic-auth": "1.8.
|
|
20
|
+
"@ex-machina/opencode-anthropic-auth": "1.8.5"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@opencode-ai/plugin": "latest",
|