@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.
- package/build/index.d.ts.map +1 -1
- package/build/index.js +255 -249
- package/build/model.d.ts.map +1 -1
- package/build/prompt.d.ts.map +1 -1
- package/build/server.js +255 -249
- package/build/stream.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/credentials.ts +2 -2
- package/src/tui.tsx +4 -34
- package/src/usage.ts +6 -6
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
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 {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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 (!
|
|
248
|
+
if (!existsSync2(p))
|
|
46
249
|
continue;
|
|
47
|
-
let raw =
|
|
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 = [
|
package/build/model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"
|
|
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"}
|
package/build/prompt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
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 {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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 (!
|
|
248
|
+
if (!existsSync2(p))
|
|
46
249
|
continue;
|
|
47
|
-
let raw =
|
|
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 = [
|
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/package.json
CHANGED
package/src/credentials.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
12
|
+
closeSync,
|
|
13
|
+
constants as fsConstants,
|
|
14
14
|
mkdirSync,
|
|
15
|
-
unlinkSync,
|
|
16
15
|
openSync,
|
|
17
|
-
|
|
16
|
+
readFileSync,
|
|
18
17
|
renameSync,
|
|
19
|
-
|
|
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 ───────────────────────────────────────────────────────────
|