@xtruder/opencode-claude-max-plugin 0.2.0 → 0.2.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAgGjD,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEhC;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IAE/B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAAA;CAChD;AA4BD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CAkItB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAoBhC,CAAA;AAID,eAAe,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAiGjD,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEhC;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IAE/B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAAA;CAChD;AA4BD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CAkItB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAoBhC,CAAA;AAID,eAAe,kBAAkB,CAAA"}
package/build/index.js CHANGED
@@ -1,10 +1,213 @@
1
+ // src/usage.ts
2
+ import {
3
+ closeSync,
4
+ constants as fsConstants,
5
+ mkdirSync,
6
+ openSync,
7
+ readFileSync as readFileSync2,
8
+ renameSync,
9
+ unlinkSync,
10
+ writeFileSync
11
+ } from "node:fs";
12
+ import { homedir as homedir2 } from "node:os";
13
+ import { join as join2 } from "node:path";
14
+
15
+ // src/credentials.ts
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ import { execSync } from "node:child_process";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+ var CREDENTIAL_CACHE_TTL_MS = 30000;
21
+ var EXPIRY_BUFFER_MS = 60000;
22
+ var cachedCreds = null;
23
+ var cachedAt = 0;
24
+ var cachedPath;
25
+ function getCredentialsPath() {
26
+ return join(homedir(), ".claude", ".credentials.json");
27
+ }
28
+ function readClaudeCredentials(path) {
29
+ const credPath = path ?? getCredentialsPath();
30
+ if (!existsSync(credPath))
31
+ return null;
32
+ try {
33
+ const raw = readFileSync(credPath, "utf-8");
34
+ const parsed = JSON.parse(raw);
35
+ if (!parsed.claudeAiOauth?.accessToken)
36
+ return null;
37
+ return parsed.claudeAiOauth;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ function isExpired(creds) {
43
+ return Date.now() > creds.expiresAt - EXPIRY_BUFFER_MS;
44
+ }
45
+ function refreshViaCli() {
46
+ const maxAttempts = 2;
47
+ for (let i = 0;i < maxAttempts; i++) {
48
+ try {
49
+ execSync("claude -p . --model haiku", {
50
+ timeout: 60000,
51
+ encoding: "utf-8",
52
+ env: { ...process.env, TERM: "dumb" },
53
+ stdio: "ignore"
54
+ });
55
+ return true;
56
+ } catch {}
57
+ }
58
+ return false;
59
+ }
60
+ function refreshIfNeeded(path) {
61
+ const creds = readClaudeCredentials(path);
62
+ if (!creds)
63
+ return null;
64
+ if (!isExpired(creds))
65
+ return creds;
66
+ console.warn("[anthropic-sdk-provider] OAuth token near expiry, refreshing via Claude CLI...");
67
+ const refreshed = refreshViaCli();
68
+ if (!refreshed) {
69
+ console.warn("[anthropic-sdk-provider] CLI refresh failed. Token may be expired.");
70
+ return creds;
71
+ }
72
+ const fresh = readClaudeCredentials(path);
73
+ if (fresh && !isExpired(fresh)) {
74
+ console.warn("[anthropic-sdk-provider] Token refreshed successfully.");
75
+ return fresh;
76
+ }
77
+ return fresh ?? creds;
78
+ }
79
+ function getCachedCredentials(path) {
80
+ const now = Date.now();
81
+ const resolvedPath = path ?? cachedPath;
82
+ if (cachedCreds && now - cachedAt < CREDENTIAL_CACHE_TTL_MS && !isExpired(cachedCreds)) {
83
+ return cachedCreds;
84
+ }
85
+ const fresh = refreshIfNeeded(resolvedPath);
86
+ if (fresh) {
87
+ cachedCreds = fresh;
88
+ cachedAt = now;
89
+ cachedPath = resolvedPath;
90
+ } else {
91
+ cachedCreds = null;
92
+ }
93
+ return cachedCreds;
94
+ }
95
+
96
+ // src/usage.ts
97
+ var cachedUsage = {};
98
+ var XDG_STATE = process.env.XDG_STATE_HOME || join2(homedir2(), ".local", "state");
99
+ var USAGE_CACHE_DIR = join2(XDG_STATE, "opencode");
100
+ var USAGE_CACHE_FILE = join2(USAGE_CACHE_DIR, "usage-cache.json");
101
+ function emptyCache() {
102
+ return { api: null, headers: null, updatedAt: 0, apiFetchedAt: 0, apiRateLimitUntil: 0 };
103
+ }
104
+ var USAGE_LOCK_FILE = USAGE_CACHE_FILE + ".lock";
105
+ var USAGE_TMP_FILE = USAGE_CACHE_FILE + ".tmp";
106
+ var LOCK_STALE_MS = 5000;
107
+ function acquireLock() {
108
+ try {
109
+ mkdirSync(USAGE_CACHE_DIR, { recursive: true });
110
+ const fd = openSync(USAGE_LOCK_FILE, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY);
111
+ writeFileSync(fd, `${process.pid}
112
+ ${Date.now()}`);
113
+ closeSync(fd);
114
+ return true;
115
+ } catch (err) {
116
+ if (err?.code !== "EEXIST")
117
+ return false;
118
+ try {
119
+ const text = readFileSync2(USAGE_LOCK_FILE, "utf-8");
120
+ const ts = parseInt(text.split(`
121
+ `)[1] ?? "0");
122
+ if (Date.now() - ts > LOCK_STALE_MS) {
123
+ unlinkSync(USAGE_LOCK_FILE);
124
+ return acquireLock();
125
+ }
126
+ } catch {
127
+ try {
128
+ unlinkSync(USAGE_LOCK_FILE);
129
+ } catch {}
130
+ return acquireLock();
131
+ }
132
+ return false;
133
+ }
134
+ }
135
+ function releaseLock() {
136
+ try {
137
+ unlinkSync(USAGE_LOCK_FILE);
138
+ } catch {}
139
+ }
140
+ function readUsageCache() {
141
+ try {
142
+ const text = readFileSync2(USAGE_CACHE_FILE, "utf-8");
143
+ const data = JSON.parse(text);
144
+ if (data && typeof data.updatedAt === "number")
145
+ return data;
146
+ } catch {}
147
+ return emptyCache();
148
+ }
149
+ function writeUsageCache(update) {
150
+ if (!acquireLock())
151
+ return;
152
+ try {
153
+ const existing = readUsageCache();
154
+ const merged = {
155
+ ...existing,
156
+ ...update,
157
+ updatedAt: Date.now()
158
+ };
159
+ mkdirSync(USAGE_CACHE_DIR, { recursive: true });
160
+ writeFileSync(USAGE_TMP_FILE, JSON.stringify(merged));
161
+ renameSync(USAGE_TMP_FILE, USAGE_CACHE_FILE);
162
+ } catch {
163
+ try {
164
+ unlinkSync(USAGE_TMP_FILE);
165
+ } catch {}
166
+ } finally {
167
+ releaseLock();
168
+ }
169
+ }
170
+ function persistCachedUsage() {
171
+ if (cachedUsage.fiveHourUtil == null && cachedUsage.sevenDayUtil == null)
172
+ return;
173
+ writeUsageCache({ headers: { ...cachedUsage } });
174
+ }
175
+ var API_TTL_MS = 5 * 60000;
176
+
177
+ // src/cch.ts
178
+ import xxhashInit from "xxhash-wasm";
179
+ var CCH_SEED = 0x6e52736ac806831en;
180
+ var CCH_PLACEHOLDER = "cch=00000";
181
+ var CCH_MASK = 0xfffffn;
182
+ var hasherPromise = null;
183
+ function getHasher() {
184
+ if (!hasherPromise) {
185
+ hasherPromise = xxhashInit();
186
+ }
187
+ return hasherPromise;
188
+ }
189
+ async function computeCch(body) {
190
+ const hasher = await getHasher();
191
+ const hash = hasher.h64Raw(new TextEncoder().encode(body), CCH_SEED);
192
+ return (hash & CCH_MASK).toString(16).padStart(5, "0");
193
+ }
194
+ function replaceCchPlaceholder(body, cch) {
195
+ return body.replace(CCH_PLACEHOLDER, `cch=${cch}`);
196
+ }
197
+ function hasCchPlaceholder(body) {
198
+ return body.includes(CCH_PLACEHOLDER);
199
+ }
200
+
1
201
  // src/index.ts
2
202
  import Anthropic from "@anthropic-ai/sdk";
3
203
 
204
+ // src/model.ts
205
+ import { APIError, RateLimitError } from "@anthropic-ai/sdk";
206
+
4
207
  // src/tool-names.ts
5
- import { readFileSync, existsSync } from "node:fs";
6
- import { join } from "node:path";
7
- import { homedir } from "node:os";
208
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
209
+ import { homedir as homedir3 } from "node:os";
210
+ import { join as join3 } from "node:path";
8
211
  var BUILTIN_OPENCODE_TO_CLAUDE = {
9
212
  task: "Agent",
10
213
  question: "AskUserQuestion",
@@ -36,15 +239,15 @@ var mcpServerNames = null;
36
239
  function detectMcpServers() {
37
240
  const servers = new Set;
38
241
  const paths = [
39
- join(process.cwd(), ".opencode", "opencode.json"),
40
- join(homedir(), ".config", "opencode", "opencode.json"),
41
- join(homedir(), ".config", "opencode", "opencode.jsonc")
242
+ join3(process.cwd(), ".opencode", "opencode.json"),
243
+ join3(homedir3(), ".config", "opencode", "opencode.json"),
244
+ join3(homedir3(), ".config", "opencode", "opencode.jsonc")
42
245
  ];
43
246
  for (const p of paths) {
44
247
  try {
45
- if (!existsSync(p))
248
+ if (!existsSync2(p))
46
249
  continue;
47
- let raw = readFileSync(p, "utf-8");
250
+ let raw = readFileSync3(p, "utf-8");
48
251
  raw = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
49
252
  const config = JSON.parse(raw);
50
253
  if (config.mcp && typeof config.mcp === "object") {
@@ -130,6 +333,50 @@ function rewriteToolNamesInText(text) {
130
333
  return result;
131
334
  }
132
335
 
336
+ // src/tools.ts
337
+ function cleanSchema(schema) {
338
+ const cleaned = {};
339
+ for (const [key, value] of Object.entries(schema)) {
340
+ if (key === "custom")
341
+ continue;
342
+ if (value && typeof value === "object" && !Array.isArray(value)) {
343
+ cleaned[key] = cleanSchema(value);
344
+ } else {
345
+ cleaned[key] = value;
346
+ }
347
+ }
348
+ if (cleaned.properties && !cleaned.type) {
349
+ cleaned.type = "object";
350
+ }
351
+ return cleaned;
352
+ }
353
+ function convertTools(tools) {
354
+ if (!tools || tools.length === 0)
355
+ return;
356
+ return tools.filter((t) => t.type === "function").map((tool) => ({
357
+ name: toClaudeToolName(tool.name),
358
+ description: tool.description ?? "",
359
+ input_schema: cleanSchema(tool.inputSchema)
360
+ }));
361
+ }
362
+ function convertToolChoice(toolChoice) {
363
+ if (!toolChoice)
364
+ return;
365
+ switch (toolChoice.type) {
366
+ case "auto":
367
+ return { type: "auto" };
368
+ case "required":
369
+ return { type: "any" };
370
+ case "none":
371
+ return;
372
+ case "tool":
373
+ return { type: "tool", name: toClaudeToolName(toolChoice.toolName) };
374
+ }
375
+ }
376
+
377
+ // src/model.ts
378
+ import { createHash, randomBytes } from "node:crypto";
379
+
133
380
  // src/prompt.ts
134
381
  function convertPrompt(prompt) {
135
382
  const systemParts = [];
@@ -272,47 +519,6 @@ function formatToolResultContent(output) {
272
519
  }
273
520
  }
274
521
 
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
522
  // src/stream.ts
317
523
  var idCounter = 0;
318
524
  function generateId() {
@@ -493,8 +699,6 @@ function processEvent(event, blockStates, modelId) {
493
699
  }
494
700
 
495
701
  // src/model.ts
496
- import { randomBytes, createHash } from "node:crypto";
497
- import { APIError, RateLimitError } from "@anthropic-ai/sdk";
498
702
  function handleApiError(error) {
499
703
  if (error instanceof RateLimitError || error instanceof APIError && error.status === 429) {
500
704
  const h = error.headers;
@@ -818,204 +1022,6 @@ class AnthropicSDKModel {
818
1022
  }
819
1023
  }
820
1024
 
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
- // src/usage.ts
903
- import {
904
- readFileSync as readFileSync3,
905
- writeFileSync,
906
- mkdirSync,
907
- unlinkSync,
908
- openSync,
909
- closeSync,
910
- renameSync,
911
- constants as fsConstants
912
- } from "node:fs";
913
- import { join as join3 } from "node:path";
914
- import { homedir as homedir3 } from "node:os";
915
- var cachedUsage = {};
916
- var XDG_STATE = process.env.XDG_STATE_HOME || join3(homedir3(), ".local", "state");
917
- var USAGE_CACHE_DIR = join3(XDG_STATE, "opencode");
918
- var USAGE_CACHE_FILE = join3(USAGE_CACHE_DIR, "usage-cache.json");
919
- function emptyCache() {
920
- return { api: null, headers: null, updatedAt: 0, apiFetchedAt: 0, apiRateLimitUntil: 0 };
921
- }
922
- var USAGE_LOCK_FILE = USAGE_CACHE_FILE + ".lock";
923
- var USAGE_TMP_FILE = USAGE_CACHE_FILE + ".tmp";
924
- var LOCK_STALE_MS = 5000;
925
- function acquireLock() {
926
- try {
927
- mkdirSync(USAGE_CACHE_DIR, { recursive: true });
928
- const fd = openSync(USAGE_LOCK_FILE, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY);
929
- writeFileSync(fd, `${process.pid}
930
- ${Date.now()}`);
931
- closeSync(fd);
932
- return true;
933
- } catch (err) {
934
- if (err?.code !== "EEXIST")
935
- return false;
936
- try {
937
- const text = readFileSync3(USAGE_LOCK_FILE, "utf-8");
938
- const ts = parseInt(text.split(`
939
- `)[1] ?? "0");
940
- if (Date.now() - ts > LOCK_STALE_MS) {
941
- unlinkSync(USAGE_LOCK_FILE);
942
- return acquireLock();
943
- }
944
- } catch {
945
- try {
946
- unlinkSync(USAGE_LOCK_FILE);
947
- } catch {}
948
- return acquireLock();
949
- }
950
- return false;
951
- }
952
- }
953
- function releaseLock() {
954
- try {
955
- unlinkSync(USAGE_LOCK_FILE);
956
- } catch {}
957
- }
958
- function readUsageCache() {
959
- try {
960
- const text = readFileSync3(USAGE_CACHE_FILE, "utf-8");
961
- const data = JSON.parse(text);
962
- if (data && typeof data.updatedAt === "number")
963
- return data;
964
- } catch {}
965
- return emptyCache();
966
- }
967
- function writeUsageCache(update) {
968
- if (!acquireLock())
969
- return;
970
- try {
971
- const existing = readUsageCache();
972
- const merged = {
973
- ...existing,
974
- ...update,
975
- updatedAt: Date.now()
976
- };
977
- mkdirSync(USAGE_CACHE_DIR, { recursive: true });
978
- writeFileSync(USAGE_TMP_FILE, JSON.stringify(merged));
979
- renameSync(USAGE_TMP_FILE, USAGE_CACHE_FILE);
980
- } catch {
981
- try {
982
- unlinkSync(USAGE_TMP_FILE);
983
- } catch {}
984
- } finally {
985
- releaseLock();
986
- }
987
- }
988
- function persistCachedUsage() {
989
- if (cachedUsage.fiveHourUtil == null && cachedUsage.sevenDayUtil == null)
990
- return;
991
- writeUsageCache({ headers: { ...cachedUsage } });
992
- }
993
- var API_TTL_MS = 5 * 60000;
994
-
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
1025
  // src/index.ts
1020
1026
  var CLAUDE_CODE_VERSION = "2.1.81";
1021
1027
  var OAUTH_BETAS = [
@@ -1 +1 @@
1
- {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAI1B,6BAA6B,EAC7B,2BAA2B,EAE5B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAQ9C,KAAK,gBAAgB,GAAG,6BAA6B,CAAA;AACrD,KAAK,cAAc,GAAG,2BAA2B,CAAA;AAkGjD,qBAAa,iBAAkB,YAAW,eAAe;IAQrD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IATjB,QAAQ,CAAC,oBAAoB,EAAG,IAAI,CAAS;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAK;gBAGnD,OAAO,EAAE,MAAM,EACP,MAAM,EAAE,SAAS,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAe;IAMlC,OAAO,CAAC,WAAW;IA2Lb,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsF1E,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;CA4E7E"}
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAG1B,6BAA6B,EAE7B,2BAA2B,EAE5B,MAAM,kBAAkB,CAAA;AAIzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAI9C,KAAK,gBAAgB,GAAG,6BAA6B,CAAA;AACrD,KAAK,cAAc,GAAG,2BAA2B,CAAA;AAkGjD,qBAAa,iBAAkB,YAAW,eAAe;IAQrD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IATjB,QAAQ,CAAC,oBAAoB,EAAG,IAAI,CAAS;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAK;gBAGnD,OAAO,EAAE,MAAM,EACP,MAAM,EAAE,SAAS,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAe;IAMlC,OAAO,CAAC,WAAW;IA2Lb,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsF1E,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;CA4E7E"}
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAA0B,MAAM,kBAAkB,CAAA;AACrF,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAG9C,KAAK,gBAAgB,GAAG,SAAS,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzE,KAAK,oBAAoB,GAAG,SAAS,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAA;AAEnE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,oBAAoB,CAAA;IAC5B,QAAQ,EAAE,gBAAgB,EAAE,CAAA;CAC7B;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAiC5E"}
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA0B,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACrF,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAG9C,KAAK,gBAAgB,GAAG,SAAS,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzE,KAAK,oBAAoB,GAAG,SAAS,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAA;AAEnE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,oBAAoB,CAAA;IAC5B,QAAQ,EAAE,gBAAgB,EAAE,CAAA;CAC7B;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAiC5E"}
package/build/server.js CHANGED
@@ -1,10 +1,213 @@
1
+ // src/usage.ts
2
+ import {
3
+ closeSync,
4
+ constants as fsConstants,
5
+ mkdirSync,
6
+ openSync,
7
+ readFileSync as readFileSync2,
8
+ renameSync,
9
+ unlinkSync,
10
+ writeFileSync
11
+ } from "node:fs";
12
+ import { homedir as homedir2 } from "node:os";
13
+ import { join as join2 } from "node:path";
14
+
15
+ // src/credentials.ts
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ import { execSync } from "node:child_process";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+ var CREDENTIAL_CACHE_TTL_MS = 30000;
21
+ var EXPIRY_BUFFER_MS = 60000;
22
+ var cachedCreds = null;
23
+ var cachedAt = 0;
24
+ var cachedPath;
25
+ function getCredentialsPath() {
26
+ return join(homedir(), ".claude", ".credentials.json");
27
+ }
28
+ function readClaudeCredentials(path) {
29
+ const credPath = path ?? getCredentialsPath();
30
+ if (!existsSync(credPath))
31
+ return null;
32
+ try {
33
+ const raw = readFileSync(credPath, "utf-8");
34
+ const parsed = JSON.parse(raw);
35
+ if (!parsed.claudeAiOauth?.accessToken)
36
+ return null;
37
+ return parsed.claudeAiOauth;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ function isExpired(creds) {
43
+ return Date.now() > creds.expiresAt - EXPIRY_BUFFER_MS;
44
+ }
45
+ function refreshViaCli() {
46
+ const maxAttempts = 2;
47
+ for (let i = 0;i < maxAttempts; i++) {
48
+ try {
49
+ execSync("claude -p . --model haiku", {
50
+ timeout: 60000,
51
+ encoding: "utf-8",
52
+ env: { ...process.env, TERM: "dumb" },
53
+ stdio: "ignore"
54
+ });
55
+ return true;
56
+ } catch {}
57
+ }
58
+ return false;
59
+ }
60
+ function refreshIfNeeded(path) {
61
+ const creds = readClaudeCredentials(path);
62
+ if (!creds)
63
+ return null;
64
+ if (!isExpired(creds))
65
+ return creds;
66
+ console.warn("[anthropic-sdk-provider] OAuth token near expiry, refreshing via Claude CLI...");
67
+ const refreshed = refreshViaCli();
68
+ if (!refreshed) {
69
+ console.warn("[anthropic-sdk-provider] CLI refresh failed. Token may be expired.");
70
+ return creds;
71
+ }
72
+ const fresh = readClaudeCredentials(path);
73
+ if (fresh && !isExpired(fresh)) {
74
+ console.warn("[anthropic-sdk-provider] Token refreshed successfully.");
75
+ return fresh;
76
+ }
77
+ return fresh ?? creds;
78
+ }
79
+ function getCachedCredentials(path) {
80
+ const now = Date.now();
81
+ const resolvedPath = path ?? cachedPath;
82
+ if (cachedCreds && now - cachedAt < CREDENTIAL_CACHE_TTL_MS && !isExpired(cachedCreds)) {
83
+ return cachedCreds;
84
+ }
85
+ const fresh = refreshIfNeeded(resolvedPath);
86
+ if (fresh) {
87
+ cachedCreds = fresh;
88
+ cachedAt = now;
89
+ cachedPath = resolvedPath;
90
+ } else {
91
+ cachedCreds = null;
92
+ }
93
+ return cachedCreds;
94
+ }
95
+
96
+ // src/usage.ts
97
+ var cachedUsage = {};
98
+ var XDG_STATE = process.env.XDG_STATE_HOME || join2(homedir2(), ".local", "state");
99
+ var USAGE_CACHE_DIR = join2(XDG_STATE, "opencode");
100
+ var USAGE_CACHE_FILE = join2(USAGE_CACHE_DIR, "usage-cache.json");
101
+ function emptyCache() {
102
+ return { api: null, headers: null, updatedAt: 0, apiFetchedAt: 0, apiRateLimitUntil: 0 };
103
+ }
104
+ var USAGE_LOCK_FILE = USAGE_CACHE_FILE + ".lock";
105
+ var USAGE_TMP_FILE = USAGE_CACHE_FILE + ".tmp";
106
+ var LOCK_STALE_MS = 5000;
107
+ function acquireLock() {
108
+ try {
109
+ mkdirSync(USAGE_CACHE_DIR, { recursive: true });
110
+ const fd = openSync(USAGE_LOCK_FILE, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY);
111
+ writeFileSync(fd, `${process.pid}
112
+ ${Date.now()}`);
113
+ closeSync(fd);
114
+ return true;
115
+ } catch (err) {
116
+ if (err?.code !== "EEXIST")
117
+ return false;
118
+ try {
119
+ const text = readFileSync2(USAGE_LOCK_FILE, "utf-8");
120
+ const ts = parseInt(text.split(`
121
+ `)[1] ?? "0");
122
+ if (Date.now() - ts > LOCK_STALE_MS) {
123
+ unlinkSync(USAGE_LOCK_FILE);
124
+ return acquireLock();
125
+ }
126
+ } catch {
127
+ try {
128
+ unlinkSync(USAGE_LOCK_FILE);
129
+ } catch {}
130
+ return acquireLock();
131
+ }
132
+ return false;
133
+ }
134
+ }
135
+ function releaseLock() {
136
+ try {
137
+ unlinkSync(USAGE_LOCK_FILE);
138
+ } catch {}
139
+ }
140
+ function readUsageCache() {
141
+ try {
142
+ const text = readFileSync2(USAGE_CACHE_FILE, "utf-8");
143
+ const data = JSON.parse(text);
144
+ if (data && typeof data.updatedAt === "number")
145
+ return data;
146
+ } catch {}
147
+ return emptyCache();
148
+ }
149
+ function writeUsageCache(update) {
150
+ if (!acquireLock())
151
+ return;
152
+ try {
153
+ const existing = readUsageCache();
154
+ const merged = {
155
+ ...existing,
156
+ ...update,
157
+ updatedAt: Date.now()
158
+ };
159
+ mkdirSync(USAGE_CACHE_DIR, { recursive: true });
160
+ writeFileSync(USAGE_TMP_FILE, JSON.stringify(merged));
161
+ renameSync(USAGE_TMP_FILE, USAGE_CACHE_FILE);
162
+ } catch {
163
+ try {
164
+ unlinkSync(USAGE_TMP_FILE);
165
+ } catch {}
166
+ } finally {
167
+ releaseLock();
168
+ }
169
+ }
170
+ function persistCachedUsage() {
171
+ if (cachedUsage.fiveHourUtil == null && cachedUsage.sevenDayUtil == null)
172
+ return;
173
+ writeUsageCache({ headers: { ...cachedUsage } });
174
+ }
175
+ var API_TTL_MS = 5 * 60000;
176
+
177
+ // src/cch.ts
178
+ import xxhashInit from "xxhash-wasm";
179
+ var CCH_SEED = 0x6e52736ac806831en;
180
+ var CCH_PLACEHOLDER = "cch=00000";
181
+ var CCH_MASK = 0xfffffn;
182
+ var hasherPromise = null;
183
+ function getHasher() {
184
+ if (!hasherPromise) {
185
+ hasherPromise = xxhashInit();
186
+ }
187
+ return hasherPromise;
188
+ }
189
+ async function computeCch(body) {
190
+ const hasher = await getHasher();
191
+ const hash = hasher.h64Raw(new TextEncoder().encode(body), CCH_SEED);
192
+ return (hash & CCH_MASK).toString(16).padStart(5, "0");
193
+ }
194
+ function replaceCchPlaceholder(body, cch) {
195
+ return body.replace(CCH_PLACEHOLDER, `cch=${cch}`);
196
+ }
197
+ function hasCchPlaceholder(body) {
198
+ return body.includes(CCH_PLACEHOLDER);
199
+ }
200
+
1
201
  // src/index.ts
2
202
  import Anthropic from "@anthropic-ai/sdk";
3
203
 
204
+ // src/model.ts
205
+ import { APIError, RateLimitError } from "@anthropic-ai/sdk";
206
+
4
207
  // src/tool-names.ts
5
- import { readFileSync, existsSync } from "node:fs";
6
- import { join } from "node:path";
7
- import { homedir } from "node:os";
208
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
209
+ import { homedir as homedir3 } from "node:os";
210
+ import { join as join3 } from "node:path";
8
211
  var BUILTIN_OPENCODE_TO_CLAUDE = {
9
212
  task: "Agent",
10
213
  question: "AskUserQuestion",
@@ -36,15 +239,15 @@ var mcpServerNames = null;
36
239
  function detectMcpServers() {
37
240
  const servers = new Set;
38
241
  const paths = [
39
- join(process.cwd(), ".opencode", "opencode.json"),
40
- join(homedir(), ".config", "opencode", "opencode.json"),
41
- join(homedir(), ".config", "opencode", "opencode.jsonc")
242
+ join3(process.cwd(), ".opencode", "opencode.json"),
243
+ join3(homedir3(), ".config", "opencode", "opencode.json"),
244
+ join3(homedir3(), ".config", "opencode", "opencode.jsonc")
42
245
  ];
43
246
  for (const p of paths) {
44
247
  try {
45
- if (!existsSync(p))
248
+ if (!existsSync2(p))
46
249
  continue;
47
- let raw = readFileSync(p, "utf-8");
250
+ let raw = readFileSync3(p, "utf-8");
48
251
  raw = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
49
252
  const config = JSON.parse(raw);
50
253
  if (config.mcp && typeof config.mcp === "object") {
@@ -130,6 +333,50 @@ function rewriteToolNamesInText(text) {
130
333
  return result;
131
334
  }
132
335
 
336
+ // src/tools.ts
337
+ function cleanSchema(schema) {
338
+ const cleaned = {};
339
+ for (const [key, value] of Object.entries(schema)) {
340
+ if (key === "custom")
341
+ continue;
342
+ if (value && typeof value === "object" && !Array.isArray(value)) {
343
+ cleaned[key] = cleanSchema(value);
344
+ } else {
345
+ cleaned[key] = value;
346
+ }
347
+ }
348
+ if (cleaned.properties && !cleaned.type) {
349
+ cleaned.type = "object";
350
+ }
351
+ return cleaned;
352
+ }
353
+ function convertTools(tools) {
354
+ if (!tools || tools.length === 0)
355
+ return;
356
+ return tools.filter((t) => t.type === "function").map((tool) => ({
357
+ name: toClaudeToolName(tool.name),
358
+ description: tool.description ?? "",
359
+ input_schema: cleanSchema(tool.inputSchema)
360
+ }));
361
+ }
362
+ function convertToolChoice(toolChoice) {
363
+ if (!toolChoice)
364
+ return;
365
+ switch (toolChoice.type) {
366
+ case "auto":
367
+ return { type: "auto" };
368
+ case "required":
369
+ return { type: "any" };
370
+ case "none":
371
+ return;
372
+ case "tool":
373
+ return { type: "tool", name: toClaudeToolName(toolChoice.toolName) };
374
+ }
375
+ }
376
+
377
+ // src/model.ts
378
+ import { createHash, randomBytes } from "node:crypto";
379
+
133
380
  // src/prompt.ts
134
381
  function convertPrompt(prompt) {
135
382
  const systemParts = [];
@@ -272,47 +519,6 @@ function formatToolResultContent(output) {
272
519
  }
273
520
  }
274
521
 
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
522
  // src/stream.ts
317
523
  var idCounter = 0;
318
524
  function generateId() {
@@ -493,8 +699,6 @@ function processEvent(event, blockStates, modelId) {
493
699
  }
494
700
 
495
701
  // src/model.ts
496
- import { randomBytes, createHash } from "node:crypto";
497
- import { APIError, RateLimitError } from "@anthropic-ai/sdk";
498
702
  function handleApiError(error) {
499
703
  if (error instanceof RateLimitError || error instanceof APIError && error.status === 429) {
500
704
  const h = error.headers;
@@ -818,204 +1022,6 @@ class AnthropicSDKModel {
818
1022
  }
819
1023
  }
820
1024
 
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
- // src/usage.ts
903
- import {
904
- readFileSync as readFileSync3,
905
- writeFileSync,
906
- mkdirSync,
907
- unlinkSync,
908
- openSync,
909
- closeSync,
910
- renameSync,
911
- constants as fsConstants
912
- } from "node:fs";
913
- import { join as join3 } from "node:path";
914
- import { homedir as homedir3 } from "node:os";
915
- var cachedUsage = {};
916
- var XDG_STATE = process.env.XDG_STATE_HOME || join3(homedir3(), ".local", "state");
917
- var USAGE_CACHE_DIR = join3(XDG_STATE, "opencode");
918
- var USAGE_CACHE_FILE = join3(USAGE_CACHE_DIR, "usage-cache.json");
919
- function emptyCache() {
920
- return { api: null, headers: null, updatedAt: 0, apiFetchedAt: 0, apiRateLimitUntil: 0 };
921
- }
922
- var USAGE_LOCK_FILE = USAGE_CACHE_FILE + ".lock";
923
- var USAGE_TMP_FILE = USAGE_CACHE_FILE + ".tmp";
924
- var LOCK_STALE_MS = 5000;
925
- function acquireLock() {
926
- try {
927
- mkdirSync(USAGE_CACHE_DIR, { recursive: true });
928
- const fd = openSync(USAGE_LOCK_FILE, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY);
929
- writeFileSync(fd, `${process.pid}
930
- ${Date.now()}`);
931
- closeSync(fd);
932
- return true;
933
- } catch (err) {
934
- if (err?.code !== "EEXIST")
935
- return false;
936
- try {
937
- const text = readFileSync3(USAGE_LOCK_FILE, "utf-8");
938
- const ts = parseInt(text.split(`
939
- `)[1] ?? "0");
940
- if (Date.now() - ts > LOCK_STALE_MS) {
941
- unlinkSync(USAGE_LOCK_FILE);
942
- return acquireLock();
943
- }
944
- } catch {
945
- try {
946
- unlinkSync(USAGE_LOCK_FILE);
947
- } catch {}
948
- return acquireLock();
949
- }
950
- return false;
951
- }
952
- }
953
- function releaseLock() {
954
- try {
955
- unlinkSync(USAGE_LOCK_FILE);
956
- } catch {}
957
- }
958
- function readUsageCache() {
959
- try {
960
- const text = readFileSync3(USAGE_CACHE_FILE, "utf-8");
961
- const data = JSON.parse(text);
962
- if (data && typeof data.updatedAt === "number")
963
- return data;
964
- } catch {}
965
- return emptyCache();
966
- }
967
- function writeUsageCache(update) {
968
- if (!acquireLock())
969
- return;
970
- try {
971
- const existing = readUsageCache();
972
- const merged = {
973
- ...existing,
974
- ...update,
975
- updatedAt: Date.now()
976
- };
977
- mkdirSync(USAGE_CACHE_DIR, { recursive: true });
978
- writeFileSync(USAGE_TMP_FILE, JSON.stringify(merged));
979
- renameSync(USAGE_TMP_FILE, USAGE_CACHE_FILE);
980
- } catch {
981
- try {
982
- unlinkSync(USAGE_TMP_FILE);
983
- } catch {}
984
- } finally {
985
- releaseLock();
986
- }
987
- }
988
- function persistCachedUsage() {
989
- if (cachedUsage.fiveHourUtil == null && cachedUsage.sevenDayUtil == null)
990
- return;
991
- writeUsageCache({ headers: { ...cachedUsage } });
992
- }
993
- var API_TTL_MS = 5 * 60000;
994
-
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
1025
  // src/index.ts
1020
1026
  var CLAUDE_CODE_VERSION = "2.1.81";
1021
1027
  var OAUTH_BETAS = [
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAA+B,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"}
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtruder/opencode-claude-max-plugin",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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",
@@ -1,7 +1,7 @@
1
- import { readFileSync, existsSync } from "node:fs"
1
+ import { existsSync, readFileSync } from "node:fs"
2
2
  import { execSync } from "node:child_process"
3
- import { join } from "node:path"
4
3
  import { homedir } from "node:os"
4
+ import { join } from "node:path"
5
5
 
6
6
  export interface ClaudeOAuthCredentials {
7
7
  accessToken: string
package/src/tui.tsx CHANGED
@@ -1,20 +1,20 @@
1
1
  // @ts-nocheck
2
2
  /** @jsxImportSource @opentui/solid */
3
+ import { Show, createMemo, createSignal } from "solid-js"
3
4
  import type {
4
5
  TuiPlugin,
5
6
  TuiPluginApi,
6
7
  TuiPluginModule,
7
8
  TuiThemeCurrent,
8
9
  } from "@opencode-ai/plugin/tui"
9
- import { createMemo, createSignal, Show } from "solid-js"
10
10
  import {
11
+ type UsageData,
12
+ bestUsageFromCache,
11
13
  fetchUsage,
12
14
  formatReset,
13
15
  readUsageCache,
14
- writeUsageCache,
15
- bestUsageFromCache,
16
16
  shouldFetchApi,
17
- type UsageData,
17
+ writeUsageCache,
18
18
  } from "./usage.ts"
19
19
 
20
20
  const id = "anthropic-sdk-usage"
@@ -178,15 +178,6 @@ function UsageSidebar(props: {
178
178
  }
179
179
  })
180
180
 
181
- const sevenDaySonnet = createMemo(() => {
182
- const d = props.usage()
183
- if (!d?.seven_day_sonnet) return null
184
- return {
185
- ratio: clamp01(d.seven_day_sonnet.utilization / 100),
186
- reset: d.seven_day_sonnet.resets_at,
187
- }
188
- })
189
-
190
181
  const hasData = createMemo(() => fiveHour() !== null || sevenDay() !== null)
191
182
 
192
183
  return (
@@ -196,7 +187,6 @@ function UsageSidebar(props: {
196
187
  const next = Math.max(1, this.width)
197
188
  setBarWidth((prev) => (prev === next ? prev : next))
198
189
  }}
199
- paddingTop={1}
200
190
  width="100%"
201
191
  flexDirection="column"
202
192
  >
@@ -243,26 +233,6 @@ function UsageSidebar(props: {
243
233
  )
244
234
  }}
245
235
  </Show>
246
- <Show when={sevenDaySonnet()}>
247
- {(data) => {
248
- const label = "7D Sonnet "
249
- const suffix = createMemo(() => ` ${pct(data().ratio)}`)
250
- return (
251
- <>
252
- <text>
253
- <span style={{ fg: props.theme.textMuted }}>{label}</span>
254
- <span style={{ fg: barColor(data().ratio, props.theme) }}>
255
- [{bar(data().ratio, fillWidth(barWidth(), label, suffix()))}]
256
- </span>
257
- <span style={{ fg: props.theme.text }}>{suffix()}</span>
258
- </text>
259
- <Show when={data().reset}>
260
- <text fg={props.theme.textMuted}>{formatReset(data().reset)}</text>
261
- </Show>
262
- </>
263
- )
264
- }}
265
- </Show>
266
236
  </box>
267
237
  </Show>
268
238
  )
package/src/usage.ts CHANGED
@@ -9,17 +9,17 @@
9
9
  * so the TUI plugin (main process) can read data written by the server plugin (worker process).
10
10
  */
11
11
  import {
12
- readFileSync,
13
- writeFileSync,
12
+ closeSync,
13
+ constants as fsConstants,
14
14
  mkdirSync,
15
- unlinkSync,
16
15
  openSync,
17
- closeSync,
16
+ readFileSync,
18
17
  renameSync,
19
- constants as fsConstants,
18
+ unlinkSync,
19
+ writeFileSync,
20
20
  } from "node:fs"
21
- import { join } from "node:path"
22
21
  import { homedir } from "node:os"
22
+ import { join } from "node:path"
23
23
  import { readClaudeCredentials } from "./credentials.ts"
24
24
 
25
25
  // ─── Types ───────────────────────────────────────────────────────────