@xtruder/opencode-claude-max-plugin 0.2.0 → 0.2.3
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/build/index.d.ts.map +1 -1
- package/build/index.js +271 -224
- package/build/model.d.ts +10 -0
- package/build/model.d.ts.map +1 -1
- package/build/prompt.d.ts.map +1 -1
- package/build/server.js +271 -224
- package/build/stream.d.ts.map +1 -1
- package/build/tui.d.ts +7 -0
- package/build/tui.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/credentials.ts +2 -2
- package/src/tui.tsx +7 -38
- package/src/usage.ts +6 -6
package/build/server.js
CHANGED
|
@@ -1,10 +1,119 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import Anthropic from "@anthropic-ai/sdk";
|
|
3
3
|
|
|
4
|
-
// src/
|
|
5
|
-
import
|
|
6
|
-
|
|
4
|
+
// src/cch.ts
|
|
5
|
+
import xxhashInit from "xxhash-wasm";
|
|
6
|
+
var CCH_SEED = 0x6e52736ac806831en;
|
|
7
|
+
var CCH_PLACEHOLDER = "cch=00000";
|
|
8
|
+
var CCH_MASK = 0xfffffn;
|
|
9
|
+
var hasherPromise = null;
|
|
10
|
+
function getHasher() {
|
|
11
|
+
if (!hasherPromise) {
|
|
12
|
+
hasherPromise = xxhashInit();
|
|
13
|
+
}
|
|
14
|
+
return hasherPromise;
|
|
15
|
+
}
|
|
16
|
+
async function computeCch(body) {
|
|
17
|
+
const hasher = await getHasher();
|
|
18
|
+
const hash = hasher.h64Raw(new TextEncoder().encode(body), CCH_SEED);
|
|
19
|
+
return (hash & CCH_MASK).toString(16).padStart(5, "0");
|
|
20
|
+
}
|
|
21
|
+
function replaceCchPlaceholder(body, cch) {
|
|
22
|
+
return body.replace(CCH_PLACEHOLDER, `cch=${cch}`);
|
|
23
|
+
}
|
|
24
|
+
function hasCchPlaceholder(body) {
|
|
25
|
+
return body.includes(CCH_PLACEHOLDER);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/credentials.ts
|
|
29
|
+
import { execSync } from "node:child_process";
|
|
30
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
31
|
import { homedir } from "node:os";
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
var CREDENTIAL_CACHE_TTL_MS = 30000;
|
|
34
|
+
var EXPIRY_BUFFER_MS = 60000;
|
|
35
|
+
var cachedCreds = null;
|
|
36
|
+
var cachedAt = 0;
|
|
37
|
+
var cachedPath;
|
|
38
|
+
function getCredentialsPath() {
|
|
39
|
+
return join(homedir(), ".claude", ".credentials.json");
|
|
40
|
+
}
|
|
41
|
+
function readClaudeCredentials(path) {
|
|
42
|
+
const credPath = path ?? getCredentialsPath();
|
|
43
|
+
if (!existsSync(credPath))
|
|
44
|
+
return null;
|
|
45
|
+
try {
|
|
46
|
+
const raw = readFileSync(credPath, "utf-8");
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
if (!parsed.claudeAiOauth?.accessToken)
|
|
49
|
+
return null;
|
|
50
|
+
return parsed.claudeAiOauth;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function isExpired(creds) {
|
|
56
|
+
return Date.now() > creds.expiresAt - EXPIRY_BUFFER_MS;
|
|
57
|
+
}
|
|
58
|
+
function refreshViaCli() {
|
|
59
|
+
const maxAttempts = 2;
|
|
60
|
+
for (let i = 0;i < maxAttempts; i++) {
|
|
61
|
+
try {
|
|
62
|
+
execSync("claude -p . --model haiku", {
|
|
63
|
+
timeout: 60000,
|
|
64
|
+
encoding: "utf-8",
|
|
65
|
+
env: { ...process.env, TERM: "dumb" },
|
|
66
|
+
stdio: "ignore"
|
|
67
|
+
});
|
|
68
|
+
return true;
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
function refreshIfNeeded(path) {
|
|
74
|
+
const creds = readClaudeCredentials(path);
|
|
75
|
+
if (!creds)
|
|
76
|
+
return null;
|
|
77
|
+
if (!isExpired(creds))
|
|
78
|
+
return creds;
|
|
79
|
+
console.warn("[anthropic-sdk-provider] OAuth token near expiry, refreshing via Claude CLI...");
|
|
80
|
+
const refreshed = refreshViaCli();
|
|
81
|
+
if (!refreshed) {
|
|
82
|
+
console.warn("[anthropic-sdk-provider] CLI refresh failed. Token may be expired.");
|
|
83
|
+
return creds;
|
|
84
|
+
}
|
|
85
|
+
const fresh = readClaudeCredentials(path);
|
|
86
|
+
if (fresh && !isExpired(fresh)) {
|
|
87
|
+
console.warn("[anthropic-sdk-provider] Token refreshed successfully.");
|
|
88
|
+
return fresh;
|
|
89
|
+
}
|
|
90
|
+
return fresh ?? creds;
|
|
91
|
+
}
|
|
92
|
+
function getCachedCredentials(path) {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const resolvedPath = path ?? cachedPath;
|
|
95
|
+
if (cachedCreds && now - cachedAt < CREDENTIAL_CACHE_TTL_MS && !isExpired(cachedCreds)) {
|
|
96
|
+
return cachedCreds;
|
|
97
|
+
}
|
|
98
|
+
const fresh = refreshIfNeeded(resolvedPath);
|
|
99
|
+
if (fresh) {
|
|
100
|
+
cachedCreds = fresh;
|
|
101
|
+
cachedAt = now;
|
|
102
|
+
cachedPath = resolvedPath;
|
|
103
|
+
} else {
|
|
104
|
+
cachedCreds = null;
|
|
105
|
+
}
|
|
106
|
+
return cachedCreds;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/model.ts
|
|
110
|
+
import { APIError, RateLimitError } from "@anthropic-ai/sdk";
|
|
111
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
112
|
+
|
|
113
|
+
// src/tool-names.ts
|
|
114
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
115
|
+
import { homedir as homedir2 } from "node:os";
|
|
116
|
+
import { join as join2 } from "node:path";
|
|
8
117
|
var BUILTIN_OPENCODE_TO_CLAUDE = {
|
|
9
118
|
task: "Agent",
|
|
10
119
|
question: "AskUserQuestion",
|
|
@@ -36,15 +145,15 @@ var mcpServerNames = null;
|
|
|
36
145
|
function detectMcpServers() {
|
|
37
146
|
const servers = new Set;
|
|
38
147
|
const paths = [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
148
|
+
join2(process.cwd(), ".opencode", "opencode.json"),
|
|
149
|
+
join2(homedir2(), ".config", "opencode", "opencode.json"),
|
|
150
|
+
join2(homedir2(), ".config", "opencode", "opencode.jsonc")
|
|
42
151
|
];
|
|
43
152
|
for (const p of paths) {
|
|
44
153
|
try {
|
|
45
|
-
if (!
|
|
154
|
+
if (!existsSync2(p))
|
|
46
155
|
continue;
|
|
47
|
-
let raw =
|
|
156
|
+
let raw = readFileSync2(p, "utf-8");
|
|
48
157
|
raw = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
49
158
|
const config = JSON.parse(raw);
|
|
50
159
|
if (config.mcp && typeof config.mcp === "object") {
|
|
@@ -272,47 +381,6 @@ function formatToolResultContent(output) {
|
|
|
272
381
|
}
|
|
273
382
|
}
|
|
274
383
|
|
|
275
|
-
// src/tools.ts
|
|
276
|
-
function cleanSchema(schema) {
|
|
277
|
-
const cleaned = {};
|
|
278
|
-
for (const [key, value] of Object.entries(schema)) {
|
|
279
|
-
if (key === "custom")
|
|
280
|
-
continue;
|
|
281
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
282
|
-
cleaned[key] = cleanSchema(value);
|
|
283
|
-
} else {
|
|
284
|
-
cleaned[key] = value;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
if (cleaned.properties && !cleaned.type) {
|
|
288
|
-
cleaned.type = "object";
|
|
289
|
-
}
|
|
290
|
-
return cleaned;
|
|
291
|
-
}
|
|
292
|
-
function convertTools(tools) {
|
|
293
|
-
if (!tools || tools.length === 0)
|
|
294
|
-
return;
|
|
295
|
-
return tools.filter((t) => t.type === "function").map((tool) => ({
|
|
296
|
-
name: toClaudeToolName(tool.name),
|
|
297
|
-
description: tool.description ?? "",
|
|
298
|
-
input_schema: cleanSchema(tool.inputSchema)
|
|
299
|
-
}));
|
|
300
|
-
}
|
|
301
|
-
function convertToolChoice(toolChoice) {
|
|
302
|
-
if (!toolChoice)
|
|
303
|
-
return;
|
|
304
|
-
switch (toolChoice.type) {
|
|
305
|
-
case "auto":
|
|
306
|
-
return { type: "auto" };
|
|
307
|
-
case "required":
|
|
308
|
-
return { type: "any" };
|
|
309
|
-
case "none":
|
|
310
|
-
return;
|
|
311
|
-
case "tool":
|
|
312
|
-
return { type: "tool", name: toClaudeToolName(toolChoice.toolName) };
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
384
|
// src/stream.ts
|
|
317
385
|
var idCounter = 0;
|
|
318
386
|
function generateId() {
|
|
@@ -492,9 +560,48 @@ function processEvent(event, blockStates, modelId) {
|
|
|
492
560
|
return parts;
|
|
493
561
|
}
|
|
494
562
|
|
|
563
|
+
// src/tools.ts
|
|
564
|
+
function cleanSchema(schema) {
|
|
565
|
+
const cleaned = {};
|
|
566
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
567
|
+
if (key === "custom")
|
|
568
|
+
continue;
|
|
569
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
570
|
+
cleaned[key] = cleanSchema(value);
|
|
571
|
+
} else {
|
|
572
|
+
cleaned[key] = value;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (cleaned.properties && !cleaned.type) {
|
|
576
|
+
cleaned.type = "object";
|
|
577
|
+
}
|
|
578
|
+
return cleaned;
|
|
579
|
+
}
|
|
580
|
+
function convertTools(tools) {
|
|
581
|
+
if (!tools || tools.length === 0)
|
|
582
|
+
return;
|
|
583
|
+
return tools.filter((t) => t.type === "function").map((tool) => ({
|
|
584
|
+
name: toClaudeToolName(tool.name),
|
|
585
|
+
description: tool.description ?? "",
|
|
586
|
+
input_schema: cleanSchema(tool.inputSchema)
|
|
587
|
+
}));
|
|
588
|
+
}
|
|
589
|
+
function convertToolChoice(toolChoice) {
|
|
590
|
+
if (!toolChoice)
|
|
591
|
+
return;
|
|
592
|
+
switch (toolChoice.type) {
|
|
593
|
+
case "auto":
|
|
594
|
+
return { type: "auto" };
|
|
595
|
+
case "required":
|
|
596
|
+
return { type: "any" };
|
|
597
|
+
case "none":
|
|
598
|
+
return;
|
|
599
|
+
case "tool":
|
|
600
|
+
return { type: "tool", name: toClaudeToolName(toolChoice.toolName) };
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
495
604
|
// src/model.ts
|
|
496
|
-
import { randomBytes, createHash } from "node:crypto";
|
|
497
|
-
import { APIError, RateLimitError } from "@anthropic-ai/sdk";
|
|
498
605
|
function handleApiError(error) {
|
|
499
606
|
if (error instanceof RateLimitError || error instanceof APIError && error.status === 429) {
|
|
500
607
|
const h = error.headers;
|
|
@@ -544,6 +651,9 @@ function buildMetadata() {
|
|
|
544
651
|
user_id: JSON.stringify({ device_id: DEVICE_ID })
|
|
545
652
|
};
|
|
546
653
|
}
|
|
654
|
+
function supportsContextManagement(apiModelId) {
|
|
655
|
+
return !apiModelId.includes("claude-3-");
|
|
656
|
+
}
|
|
547
657
|
|
|
548
658
|
class AnthropicSDKModel {
|
|
549
659
|
client;
|
|
@@ -553,12 +663,16 @@ class AnthropicSDKModel {
|
|
|
553
663
|
provider;
|
|
554
664
|
modelId;
|
|
555
665
|
supportedUrls = {};
|
|
666
|
+
apiModelId;
|
|
667
|
+
is1MContext;
|
|
556
668
|
constructor(modelId, client, providerName, isOAuth = false) {
|
|
557
669
|
this.client = client;
|
|
558
670
|
this.providerName = providerName;
|
|
559
671
|
this.isOAuth = isOAuth;
|
|
560
672
|
this.modelId = modelId;
|
|
561
673
|
this.provider = providerName;
|
|
674
|
+
this.is1MContext = modelId.endsWith("-1m");
|
|
675
|
+
this.apiModelId = this.is1MContext ? modelId.replace(/-1m$/, "") : modelId;
|
|
562
676
|
}
|
|
563
677
|
buildParams(options) {
|
|
564
678
|
const { system, messages } = convertPrompt(options.prompt);
|
|
@@ -575,7 +689,7 @@ class AnthropicSDKModel {
|
|
|
575
689
|
const tools = options.toolChoice?.type === "none" ? undefined : convertTools(options.tools);
|
|
576
690
|
const toolChoice = options.toolChoice?.type === "none" ? undefined : convertToolChoice(options.toolChoice);
|
|
577
691
|
const params = {
|
|
578
|
-
model: this.
|
|
692
|
+
model: this.apiModelId,
|
|
579
693
|
max_tokens: options.maxOutputTokens ?? 64000,
|
|
580
694
|
messages
|
|
581
695
|
};
|
|
@@ -593,7 +707,7 @@ class AnthropicSDKModel {
|
|
|
593
707
|
params.system = [BILLING_SYSTEM_BLOCK, IDENTITY_SYSTEM_BLOCK];
|
|
594
708
|
}
|
|
595
709
|
params.metadata = buildMetadata();
|
|
596
|
-
const supportsEffort = !this.
|
|
710
|
+
const supportsEffort = !this.apiModelId.includes("haiku") && !this.apiModelId.includes("claude-3-");
|
|
597
711
|
if (supportsEffort) {
|
|
598
712
|
if (options.temperature == null) {
|
|
599
713
|
params.temperature = 1;
|
|
@@ -681,8 +795,27 @@ class AnthropicSDKModel {
|
|
|
681
795
|
if (anthropicOptions.metadata)
|
|
682
796
|
params.metadata = anthropicOptions.metadata;
|
|
683
797
|
}
|
|
798
|
+
if (this.isOAuth && supportsContextManagement(this.apiModelId)) {
|
|
799
|
+
const edits = [];
|
|
800
|
+
const hasThinking = params.thinking?.type === "enabled";
|
|
801
|
+
if (hasThinking) {
|
|
802
|
+
edits.push({
|
|
803
|
+
type: "clear_thinking_20251015",
|
|
804
|
+
keep: "all"
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
if (edits.length > 0) {
|
|
808
|
+
params.context_management = { edits };
|
|
809
|
+
}
|
|
810
|
+
}
|
|
684
811
|
return { params, warnings };
|
|
685
812
|
}
|
|
813
|
+
buildRequestOptions(signal) {
|
|
814
|
+
const opts = {};
|
|
815
|
+
if (signal)
|
|
816
|
+
opts.signal = signal;
|
|
817
|
+
return opts;
|
|
818
|
+
}
|
|
686
819
|
async doGenerate(options) {
|
|
687
820
|
const { params, warnings } = this.buildParams(options);
|
|
688
821
|
if (!options.maxOutputTokens && params.max_tokens > 4096) {
|
|
@@ -693,7 +826,7 @@ class AnthropicSDKModel {
|
|
|
693
826
|
response = await this.client.messages.create({
|
|
694
827
|
...params,
|
|
695
828
|
stream: false
|
|
696
|
-
},
|
|
829
|
+
}, this.buildRequestOptions(options.abortSignal));
|
|
697
830
|
} catch (error) {
|
|
698
831
|
handleApiError(error);
|
|
699
832
|
}
|
|
@@ -762,7 +895,7 @@ class AnthropicSDKModel {
|
|
|
762
895
|
anthropicStream = await this.client.messages.create({
|
|
763
896
|
...params,
|
|
764
897
|
stream: true
|
|
765
|
-
},
|
|
898
|
+
}, this.buildRequestOptions(options.abortSignal));
|
|
766
899
|
} catch (error) {
|
|
767
900
|
if (error instanceof APIError && error.status === 400 && error.message?.includes("must end with a user message")) {
|
|
768
901
|
return {
|
|
@@ -818,100 +951,19 @@ class AnthropicSDKModel {
|
|
|
818
951
|
}
|
|
819
952
|
}
|
|
820
953
|
|
|
821
|
-
// src/credentials.ts
|
|
822
|
-
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
823
|
-
import { execSync } from "node:child_process";
|
|
824
|
-
import { join as join2 } from "node:path";
|
|
825
|
-
import { homedir as homedir2 } from "node:os";
|
|
826
|
-
var CREDENTIAL_CACHE_TTL_MS = 30000;
|
|
827
|
-
var EXPIRY_BUFFER_MS = 60000;
|
|
828
|
-
var cachedCreds = null;
|
|
829
|
-
var cachedAt = 0;
|
|
830
|
-
var cachedPath;
|
|
831
|
-
function getCredentialsPath() {
|
|
832
|
-
return join2(homedir2(), ".claude", ".credentials.json");
|
|
833
|
-
}
|
|
834
|
-
function readClaudeCredentials(path) {
|
|
835
|
-
const credPath = path ?? getCredentialsPath();
|
|
836
|
-
if (!existsSync2(credPath))
|
|
837
|
-
return null;
|
|
838
|
-
try {
|
|
839
|
-
const raw = readFileSync2(credPath, "utf-8");
|
|
840
|
-
const parsed = JSON.parse(raw);
|
|
841
|
-
if (!parsed.claudeAiOauth?.accessToken)
|
|
842
|
-
return null;
|
|
843
|
-
return parsed.claudeAiOauth;
|
|
844
|
-
} catch {
|
|
845
|
-
return null;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
function isExpired(creds) {
|
|
849
|
-
return Date.now() > creds.expiresAt - EXPIRY_BUFFER_MS;
|
|
850
|
-
}
|
|
851
|
-
function refreshViaCli() {
|
|
852
|
-
const maxAttempts = 2;
|
|
853
|
-
for (let i = 0;i < maxAttempts; i++) {
|
|
854
|
-
try {
|
|
855
|
-
execSync("claude -p . --model haiku", {
|
|
856
|
-
timeout: 60000,
|
|
857
|
-
encoding: "utf-8",
|
|
858
|
-
env: { ...process.env, TERM: "dumb" },
|
|
859
|
-
stdio: "ignore"
|
|
860
|
-
});
|
|
861
|
-
return true;
|
|
862
|
-
} catch {}
|
|
863
|
-
}
|
|
864
|
-
return false;
|
|
865
|
-
}
|
|
866
|
-
function refreshIfNeeded(path) {
|
|
867
|
-
const creds = readClaudeCredentials(path);
|
|
868
|
-
if (!creds)
|
|
869
|
-
return null;
|
|
870
|
-
if (!isExpired(creds))
|
|
871
|
-
return creds;
|
|
872
|
-
console.warn("[anthropic-sdk-provider] OAuth token near expiry, refreshing via Claude CLI...");
|
|
873
|
-
const refreshed = refreshViaCli();
|
|
874
|
-
if (!refreshed) {
|
|
875
|
-
console.warn("[anthropic-sdk-provider] CLI refresh failed. Token may be expired.");
|
|
876
|
-
return creds;
|
|
877
|
-
}
|
|
878
|
-
const fresh = readClaudeCredentials(path);
|
|
879
|
-
if (fresh && !isExpired(fresh)) {
|
|
880
|
-
console.warn("[anthropic-sdk-provider] Token refreshed successfully.");
|
|
881
|
-
return fresh;
|
|
882
|
-
}
|
|
883
|
-
return fresh ?? creds;
|
|
884
|
-
}
|
|
885
|
-
function getCachedCredentials(path) {
|
|
886
|
-
const now = Date.now();
|
|
887
|
-
const resolvedPath = path ?? cachedPath;
|
|
888
|
-
if (cachedCreds && now - cachedAt < CREDENTIAL_CACHE_TTL_MS && !isExpired(cachedCreds)) {
|
|
889
|
-
return cachedCreds;
|
|
890
|
-
}
|
|
891
|
-
const fresh = refreshIfNeeded(resolvedPath);
|
|
892
|
-
if (fresh) {
|
|
893
|
-
cachedCreds = fresh;
|
|
894
|
-
cachedAt = now;
|
|
895
|
-
cachedPath = resolvedPath;
|
|
896
|
-
} else {
|
|
897
|
-
cachedCreds = null;
|
|
898
|
-
}
|
|
899
|
-
return cachedCreds;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
954
|
// src/usage.ts
|
|
903
955
|
import {
|
|
904
|
-
|
|
905
|
-
|
|
956
|
+
closeSync,
|
|
957
|
+
constants as fsConstants,
|
|
906
958
|
mkdirSync,
|
|
907
|
-
unlinkSync,
|
|
908
959
|
openSync,
|
|
909
|
-
|
|
960
|
+
readFileSync as readFileSync3,
|
|
910
961
|
renameSync,
|
|
911
|
-
|
|
962
|
+
unlinkSync,
|
|
963
|
+
writeFileSync
|
|
912
964
|
} from "node:fs";
|
|
913
|
-
import { join as join3 } from "node:path";
|
|
914
965
|
import { homedir as homedir3 } from "node:os";
|
|
966
|
+
import { join as join3 } from "node:path";
|
|
915
967
|
var cachedUsage = {};
|
|
916
968
|
var XDG_STATE = process.env.XDG_STATE_HOME || join3(homedir3(), ".local", "state");
|
|
917
969
|
var USAGE_CACHE_DIR = join3(XDG_STATE, "opencode");
|
|
@@ -992,30 +1044,6 @@ function persistCachedUsage() {
|
|
|
992
1044
|
}
|
|
993
1045
|
var API_TTL_MS = 5 * 60000;
|
|
994
1046
|
|
|
995
|
-
// src/cch.ts
|
|
996
|
-
import xxhashInit from "xxhash-wasm";
|
|
997
|
-
var CCH_SEED = 0x6e52736ac806831en;
|
|
998
|
-
var CCH_PLACEHOLDER = "cch=00000";
|
|
999
|
-
var CCH_MASK = 0xfffffn;
|
|
1000
|
-
var hasherPromise = null;
|
|
1001
|
-
function getHasher() {
|
|
1002
|
-
if (!hasherPromise) {
|
|
1003
|
-
hasherPromise = xxhashInit();
|
|
1004
|
-
}
|
|
1005
|
-
return hasherPromise;
|
|
1006
|
-
}
|
|
1007
|
-
async function computeCch(body) {
|
|
1008
|
-
const hasher = await getHasher();
|
|
1009
|
-
const hash = hasher.h64Raw(new TextEncoder().encode(body), CCH_SEED);
|
|
1010
|
-
return (hash & CCH_MASK).toString(16).padStart(5, "0");
|
|
1011
|
-
}
|
|
1012
|
-
function replaceCchPlaceholder(body, cch) {
|
|
1013
|
-
return body.replace(CCH_PLACEHOLDER, `cch=${cch}`);
|
|
1014
|
-
}
|
|
1015
|
-
function hasCchPlaceholder(body) {
|
|
1016
|
-
return body.includes(CCH_PLACEHOLDER);
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
1047
|
// src/index.ts
|
|
1020
1048
|
var CLAUDE_CODE_VERSION = "2.1.81";
|
|
1021
1049
|
var OAUTH_BETAS = [
|
|
@@ -1026,63 +1054,90 @@ var OAUTH_BETAS = [
|
|
|
1026
1054
|
"prompt-caching-scope-2026-01-05",
|
|
1027
1055
|
"effort-2025-11-24"
|
|
1028
1056
|
];
|
|
1029
|
-
var CONTEXT_1M_BETA = "context-1m-2025-08-07";
|
|
1030
1057
|
var API_KEY_BETAS = ["interleaved-thinking-2025-05-14", "fine-grained-tool-streaming-2025-05-14"];
|
|
1031
1058
|
var PACKAGE_NAME = "@xtruder/opencode-claude-max-plugin";
|
|
1032
1059
|
var PROVIDER_ID = "anthropic-sdk";
|
|
1033
|
-
var
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
modalities: {
|
|
1043
|
-
input: ["text", "image"],
|
|
1044
|
-
output: ["text"]
|
|
1045
|
-
}
|
|
1046
|
-
},
|
|
1047
|
-
"claude-sonnet-4-6": {
|
|
1048
|
-
name: "Claude Sonnet 4.6",
|
|
1049
|
-
reasoning: true,
|
|
1050
|
-
tool_call: true,
|
|
1051
|
-
attachment: true,
|
|
1052
|
-
temperature: true,
|
|
1053
|
-
limit: { context: 200000, output: 16000 },
|
|
1054
|
-
cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
1055
|
-
modalities: {
|
|
1056
|
-
input: ["text", "image", "pdf"],
|
|
1057
|
-
output: ["text"]
|
|
1058
|
-
},
|
|
1059
|
-
options: { effort: "medium" },
|
|
1060
|
+
var API_KEY_COSTS = {
|
|
1061
|
+
haiku: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 },
|
|
1062
|
+
sonnet: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
1063
|
+
opus: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }
|
|
1064
|
+
};
|
|
1065
|
+
var ZERO_COST = { input: 0, output: 0, cache_read: 0, cache_write: 0 };
|
|
1066
|
+
function buildPluginModels(isOAuth) {
|
|
1067
|
+
const cost = (tier) => isOAuth ? ZERO_COST : API_KEY_COSTS[tier];
|
|
1068
|
+
const opusVariants = {
|
|
1060
1069
|
variants: {
|
|
1061
1070
|
low: { effort: "low" },
|
|
1062
1071
|
medium: { effort: "medium" },
|
|
1063
|
-
high: { effort: "high" }
|
|
1072
|
+
high: { effort: "high" },
|
|
1073
|
+
max: { effort: "max" }
|
|
1064
1074
|
}
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1075
|
+
};
|
|
1076
|
+
return {
|
|
1077
|
+
"claude-haiku-4-5": {
|
|
1078
|
+
name: "Claude Haiku 4.5",
|
|
1079
|
+
reasoning: false,
|
|
1080
|
+
tool_call: true,
|
|
1081
|
+
attachment: true,
|
|
1082
|
+
temperature: true,
|
|
1083
|
+
limit: { context: 200000, output: 64000 },
|
|
1084
|
+
cost: cost("haiku"),
|
|
1085
|
+
modalities: {
|
|
1086
|
+
input: ["text", "image"],
|
|
1087
|
+
output: ["text"]
|
|
1088
|
+
}
|
|
1077
1089
|
},
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1090
|
+
"claude-sonnet-4-6": {
|
|
1091
|
+
name: "Claude Sonnet 4.6",
|
|
1092
|
+
reasoning: true,
|
|
1093
|
+
tool_call: true,
|
|
1094
|
+
attachment: true,
|
|
1095
|
+
temperature: true,
|
|
1096
|
+
limit: { context: 200000, output: 64000 },
|
|
1097
|
+
cost: cost("sonnet"),
|
|
1098
|
+
modalities: {
|
|
1099
|
+
input: ["text", "image", "pdf"],
|
|
1100
|
+
output: ["text"]
|
|
1101
|
+
},
|
|
1102
|
+
options: { effort: "medium" },
|
|
1103
|
+
variants: {
|
|
1104
|
+
low: { effort: "low" },
|
|
1105
|
+
medium: { effort: "medium" },
|
|
1106
|
+
high: { effort: "high" }
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
"claude-opus-4-6": {
|
|
1110
|
+
name: "Claude Opus 4.6",
|
|
1111
|
+
reasoning: true,
|
|
1112
|
+
tool_call: true,
|
|
1113
|
+
attachment: true,
|
|
1114
|
+
temperature: true,
|
|
1115
|
+
limit: { context: 200000, output: 128000 },
|
|
1116
|
+
cost: cost("opus"),
|
|
1117
|
+
modalities: {
|
|
1118
|
+
input: ["text", "image", "pdf"],
|
|
1119
|
+
output: ["text"]
|
|
1120
|
+
},
|
|
1121
|
+
options: { effort: "medium" },
|
|
1122
|
+
...opusVariants
|
|
1123
|
+
},
|
|
1124
|
+
"claude-opus-4-6-1m": {
|
|
1125
|
+
name: "Claude Opus 4.6 (1M)",
|
|
1126
|
+
reasoning: true,
|
|
1127
|
+
tool_call: true,
|
|
1128
|
+
attachment: true,
|
|
1129
|
+
temperature: true,
|
|
1130
|
+
limit: { context: 1e6, output: 128000 },
|
|
1131
|
+
cost: cost("opus"),
|
|
1132
|
+
modalities: {
|
|
1133
|
+
input: ["text", "image", "pdf"],
|
|
1134
|
+
output: ["text"]
|
|
1135
|
+
},
|
|
1136
|
+
options: { effort: "medium" },
|
|
1137
|
+
...opusVariants
|
|
1083
1138
|
}
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1086
1141
|
function resolveAuth(options) {
|
|
1087
1142
|
if (options.apiKey) {
|
|
1088
1143
|
return { apiKey: options.apiKey, isOAuth: false };
|
|
@@ -1111,7 +1166,6 @@ function createAnthropicSDK(options = {}) {
|
|
|
1111
1166
|
defaultHeaders["anthropic-dangerous-direct-browser-access"] = "true";
|
|
1112
1167
|
defaultHeaders["x-stainless-package-version"] = "0.74.0";
|
|
1113
1168
|
}
|
|
1114
|
-
const CONTEXT_1M_BODY_THRESHOLD = 600000;
|
|
1115
1169
|
const baseFetch = customFetch ?? globalThis.fetch;
|
|
1116
1170
|
const wrappedFetch = async (url, init) => {
|
|
1117
1171
|
if (auth.isOAuth && init) {
|
|
@@ -1122,14 +1176,6 @@ function createAnthropicSDK(options = {}) {
|
|
|
1122
1176
|
init = { ...init, headers: reqHeaders };
|
|
1123
1177
|
}
|
|
1124
1178
|
}
|
|
1125
|
-
if (init?.body && typeof init.body === "string" && init.body.length > CONTEXT_1M_BODY_THRESHOLD) {
|
|
1126
|
-
const betaHeaders = new Headers(init.headers);
|
|
1127
|
-
const existingBetas = betaHeaders.get("anthropic-beta") ?? "";
|
|
1128
|
-
if (!existingBetas.includes(CONTEXT_1M_BETA)) {
|
|
1129
|
-
betaHeaders.set("anthropic-beta", existingBetas + "," + CONTEXT_1M_BETA);
|
|
1130
|
-
init = { ...init, headers: betaHeaders };
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
1179
|
if (auth.isOAuth && init?.body && typeof init.body === "string" && hasCchPlaceholder(init.body)) {
|
|
1134
1180
|
try {
|
|
1135
1181
|
const cch = await computeCch(init.body);
|
|
@@ -1178,6 +1224,7 @@ function createAnthropicSDK(options = {}) {
|
|
|
1178
1224
|
};
|
|
1179
1225
|
}
|
|
1180
1226
|
var anthropicSDKPlugin = async () => {
|
|
1227
|
+
const isOAuth = resolveAuth({}).isOAuth;
|
|
1181
1228
|
return {
|
|
1182
1229
|
config: async (cfg) => {
|
|
1183
1230
|
if (!cfg.provider) {
|
|
@@ -1185,7 +1232,7 @@ var anthropicSDKPlugin = async () => {
|
|
|
1185
1232
|
}
|
|
1186
1233
|
cfg.provider[PROVIDER_ID] = {
|
|
1187
1234
|
npm: PACKAGE_NAME,
|
|
1188
|
-
models:
|
|
1235
|
+
models: buildPluginModels(isOAuth),
|
|
1189
1236
|
...cfg.provider[PROVIDER_ID]
|
|
1190
1237
|
};
|
|
1191
1238
|
}
|
package/build/stream.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,yBAAyB,EAAE,MAAM,kBAAkB,CAAA;AAC9F,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAG9C,KAAK,kBAAkB,GAAG,SAAS,CAAC,kBAAkB,CAAA;AAmCtD,wBAAgB,aAAa,CAC3B,eAAe,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAClD,OAAO,EAAE,MAAM,GACd,cAAc,CAAC,yBAAyB,CAAC,CAwD3C"}
|
package/build/tui.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,EAGV,eAAe,EAEhB,MAAM,yBAAyB,CAAA;AA+XhC,QAAA,MAAM,MAAM,EAAE,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAG3C,CAAA;AAED,eAAe,MAAM,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtruder/opencode-claude-max-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "OpenCode plugin for Claude Max/Pro subscription via @anthropic-ai/sdk - uses Claude Code OAuth credentials",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-sdk",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"@ai-sdk/provider": "3.0.8",
|
|
57
57
|
"@ai-sdk/provider-utils": "3.0.2",
|
|
58
58
|
"@opencode-ai/plugin": "^1.3.0",
|
|
59
|
+
"@opentui/solid": "^0.1.96",
|
|
59
60
|
"ai": "5.0.124",
|
|
60
61
|
"bun-types": "^1.3.11",
|
|
61
62
|
"oxfmt": "^0.41.0",
|
package/src/credentials.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from "node:fs"
|
|
2
1
|
import { execSync } from "node:child_process"
|
|
3
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
4
3
|
import { homedir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
5
|
|
|
6
6
|
export interface ClaudeOAuthCredentials {
|
|
7
7
|
accessToken: string
|