@xtruder/opencode-claude-max-plugin 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -42
- package/build/credentials.d.ts +24 -1
- package/build/credentials.d.ts.map +1 -1
- package/build/index.d.ts +22 -32
- package/build/index.d.ts.map +1 -1
- package/build/index.js +165 -43
- package/build/model.d.ts.map +1 -1
- package/build/stream.d.ts.map +1 -1
- package/build/tool-names.d.ts.map +1 -1
- package/build/tools.d.ts.map +1 -1
- package/build/usage-cache.d.ts +15 -0
- package/build/usage-cache.d.ts.map +1 -0
- package/build/usage.d.ts.map +1 -1
- package/package.json +31 -25
- package/build/agent-index.d.ts +0 -24
- package/build/agent-index.d.ts.map +0 -1
- package/build/agent-index.js +0 -302
- package/build/agent-model.d.ts +0 -34
- package/build/agent-model.d.ts.map +0 -1
- package/build/agent-test.d.ts +0 -2
- package/build/agent-test.d.ts.map +0 -1
- package/build/usage-cli.d.ts +0 -3
- package/build/usage-cli.d.ts.map +0 -1
- package/build/usage-cli.js +0 -148
package/README.md
CHANGED
|
@@ -10,58 +10,42 @@ An [OpenCode](https://opencode.ai/) plugin that enables Claude Pro/Max subscript
|
|
|
10
10
|
- **All Claude models** — Opus 4.6, Sonnet 4.6, Haiku 4.5
|
|
11
11
|
- **Extended thinking** — Full reasoning support with thinking variants (high/max)
|
|
12
12
|
- **Usage tracking** — Built-in `/usage` command shows subscription utilization
|
|
13
|
+
- **Self-registering** — Models are registered automatically, no manual provider config needed
|
|
13
14
|
|
|
14
15
|
## Installation
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
Add the plugin to your `opencode.json` (project-level or `~/.config/opencode/opencode.json` globally):
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"$schema": "https://opencode.ai/config.json",
|
|
22
|
+
"plugin": ["@xtruder/opencode-claude-max-plugin"]
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That's it. The plugin self-registers the `anthropic-sdk` provider and its models (Haiku 4.5, Sonnet 4.6, Opus 4.6) at startup via the OpenCode config hook. No separate `provider` block is needed.
|
|
27
|
+
|
|
28
|
+
Then open OpenCode and run `/connect` → Other → `anthropic-sdk`. If Claude Code is installed and you're logged in, credentials are read automatically from `~/.claude/.credentials.json` — no API key needed.
|
|
29
|
+
|
|
30
|
+
### Custom model options
|
|
31
|
+
|
|
32
|
+
If you want to override model settings (e.g. thinking budgets, variants), you can add a `provider` block alongside the plugin:
|
|
19
33
|
|
|
20
34
|
```json
|
|
21
35
|
{
|
|
22
36
|
"$schema": "https://opencode.ai/config.json",
|
|
37
|
+
"plugin": ["@xtruder/opencode-claude-max-plugin"],
|
|
23
38
|
"provider": {
|
|
24
39
|
"anthropic-sdk": {
|
|
25
|
-
"npm": "@xtruder/opencode-claude-max-plugin",
|
|
26
|
-
"name": "Anthropic SDK",
|
|
27
40
|
"models": {
|
|
28
41
|
"claude-sonnet-4-6": {
|
|
29
|
-
"name": "Claude Sonnet 4.6",
|
|
30
|
-
"attachment": false,
|
|
31
|
-
"reasoning": true,
|
|
32
|
-
"tool_call": true,
|
|
33
|
-
"temperature": true,
|
|
34
|
-
"limit": { "context": 200000, "output": 64000 },
|
|
35
42
|
"options": {
|
|
36
43
|
"thinking": { "type": "enabled", "budgetTokens": 1024 }
|
|
37
44
|
},
|
|
38
45
|
"variants": {
|
|
39
46
|
"high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
|
|
40
|
-
"max":
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
"claude-opus-4-6": {
|
|
44
|
-
"name": "Claude Opus 4.6",
|
|
45
|
-
"attachment": false,
|
|
46
|
-
"reasoning": true,
|
|
47
|
-
"tool_call": true,
|
|
48
|
-
"temperature": true,
|
|
49
|
-
"limit": { "context": 1000000, "output": 64000 },
|
|
50
|
-
"options": {
|
|
51
|
-
"thinking": { "type": "adaptive" }
|
|
52
|
-
},
|
|
53
|
-
"variants": {
|
|
54
|
-
"high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
|
|
55
|
-
"max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
|
|
47
|
+
"max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
|
|
56
48
|
}
|
|
57
|
-
},
|
|
58
|
-
"claude-haiku-4-5-20251001": {
|
|
59
|
-
"name": "Claude Haiku 4.5",
|
|
60
|
-
"attachment": false,
|
|
61
|
-
"reasoning": false,
|
|
62
|
-
"tool_call": true,
|
|
63
|
-
"temperature": true,
|
|
64
|
-
"limit": { "context": 200000, "output": 8192 }
|
|
65
49
|
}
|
|
66
50
|
}
|
|
67
51
|
}
|
|
@@ -69,7 +53,7 @@ Just add the following to `.opencode/opencode.json` in your project (or `~/.conf
|
|
|
69
53
|
}
|
|
70
54
|
```
|
|
71
55
|
|
|
72
|
-
|
|
56
|
+
Config-level settings are merged with plugin defaults — you only need to specify what you want to override.
|
|
73
57
|
|
|
74
58
|
## Authentication
|
|
75
59
|
|
|
@@ -112,16 +96,16 @@ After adding the config, run `/usage` inside OpenCode to see your subscription u
|
|
|
112
96
|
The plugin also works as a standalone Vercel AI SDK provider:
|
|
113
97
|
|
|
114
98
|
```typescript
|
|
115
|
-
import { createAnthropicSDK } from "@xtruder/opencode-claude-max-plugin"
|
|
116
|
-
import { streamText } from "ai"
|
|
99
|
+
import { createAnthropicSDK } from "@xtruder/opencode-claude-max-plugin"
|
|
100
|
+
import { streamText } from "ai"
|
|
117
101
|
|
|
118
102
|
// Uses ~/.claude/.credentials.json automatically
|
|
119
|
-
const provider = createAnthropicSDK()
|
|
120
|
-
const model = provider.languageModel("claude-sonnet-4-6")
|
|
103
|
+
const provider = createAnthropicSDK()
|
|
104
|
+
const model = provider.languageModel("claude-sonnet-4-6")
|
|
121
105
|
|
|
122
|
-
const result = streamText({ model, prompt: "Hello!" })
|
|
106
|
+
const result = streamText({ model, prompt: "Hello!" })
|
|
123
107
|
for await (const chunk of result.textStream) {
|
|
124
|
-
process.stdout.write(chunk)
|
|
108
|
+
process.stdout.write(chunk)
|
|
125
109
|
}
|
|
126
110
|
```
|
|
127
111
|
|
|
@@ -130,7 +114,7 @@ for await (const chunk of result.textStream) {
|
|
|
130
114
|
```bash
|
|
131
115
|
bun install
|
|
132
116
|
bun run build
|
|
133
|
-
bun run test #
|
|
117
|
+
bun run test # integration tests (requires ANTHROPIC_API_KEY or Claude Code credentials)
|
|
134
118
|
```
|
|
135
119
|
|
|
136
120
|
See [RESEARCH.md](RESEARCH.md) for detailed reverse-engineering findings on how we matched Claude Code's request format.
|
package/build/credentials.d.ts
CHANGED
|
@@ -15,7 +15,30 @@ export declare function getCredentialsPath(): string;
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function readClaudeCredentials(path?: string): ClaudeOAuthCredentials | null;
|
|
17
17
|
/**
|
|
18
|
-
* Check if credentials are expired (with
|
|
18
|
+
* Check if credentials are expired (with 60s buffer).
|
|
19
19
|
*/
|
|
20
20
|
export declare function isExpired(creds: ClaudeOAuthCredentials): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Run the Claude CLI to trigger an OAuth token refresh.
|
|
23
|
+
* The CLI writes refreshed credentials to ~/.claude/.credentials.json.
|
|
24
|
+
* Retries once on failure (matching opencode-claude-auth behavior).
|
|
25
|
+
*/
|
|
26
|
+
export declare function refreshViaCli(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Read credentials, refreshing via Claude CLI if the token is near expiry.
|
|
29
|
+
* Returns null only if credentials are completely unavailable.
|
|
30
|
+
*/
|
|
31
|
+
export declare function refreshIfNeeded(path?: string): ClaudeOAuthCredentials | null;
|
|
32
|
+
/**
|
|
33
|
+
* Get cached credentials with automatic refresh.
|
|
34
|
+
* Uses in-memory caching with a 30-second TTL to avoid
|
|
35
|
+
* hitting the filesystem on every request.
|
|
36
|
+
* When the cached token is near expiry, triggers a CLI refresh.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getCachedCredentials(path?: string): ClaudeOAuthCredentials | null;
|
|
39
|
+
/**
|
|
40
|
+
* Clear the in-memory credential cache.
|
|
41
|
+
* Forces the next getCachedCredentials() call to re-read from disk.
|
|
42
|
+
*/
|
|
43
|
+
export declare function clearCredentialCache(): void;
|
|
21
44
|
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC;AAqBD;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,sBAAsB,GAAG,IAAI,CAalF;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAEhE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAgBvC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,sBAAsB,GAAG,IAAI,CAyB5E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,sBAAsB,GAAG,IAAI,CAoBjF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAI3C"}
|
package/build/index.d.ts
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
import type { LanguageModelV2 } from "@ai-sdk/provider";
|
|
2
|
-
|
|
3
|
-
* Beta flags that Claude Code sends on every OAuth request.
|
|
4
|
-
* Order and exact values must match what Claude Code sends.
|
|
5
|
-
*/
|
|
6
|
-
export declare const OAUTH_BETAS: string[];
|
|
7
|
-
/**
|
|
8
|
-
* Beta flag that enables 1M context window. Only sent when model ID
|
|
9
|
-
* includes [1m] suffix — e.g. "claude-sonnet-4-6[1m]".
|
|
10
|
-
* Claude Code checks: /\[1m\]/i.test(modelId) before including this.
|
|
11
|
-
*/
|
|
12
|
-
export declare const CONTEXT_1M_BETA = "context-1m-2025-08-07";
|
|
13
|
-
/**
|
|
14
|
-
* Beta flags for regular API key auth (no OAuth).
|
|
15
|
-
*/
|
|
16
|
-
export declare const API_KEY_BETAS: string[];
|
|
2
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
17
3
|
export interface AnthropicSDKProviderOptions {
|
|
18
4
|
/**
|
|
19
5
|
* Anthropic API key. Defaults to ANTHROPIC_API_KEY env var.
|
|
@@ -51,30 +37,34 @@ export interface AnthropicSDKProvider {
|
|
|
51
37
|
*/
|
|
52
38
|
languageModel(modelId: string): LanguageModelV2;
|
|
53
39
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Cached ratelimit usage from the most recent API response headers.
|
|
56
|
-
* Updated on every successful inference call — same data Claude Code
|
|
57
|
-
* reads for its /usage display. No extra API call needed.
|
|
58
|
-
*/
|
|
59
|
-
export interface CachedUsage {
|
|
60
|
-
fiveHourUtil?: number;
|
|
61
|
-
sevenDayUtil?: number;
|
|
62
|
-
fiveHourReset?: number;
|
|
63
|
-
sevenDayReset?: number;
|
|
64
|
-
overageStatus?: string;
|
|
65
|
-
timestamp?: number;
|
|
66
|
-
}
|
|
67
|
-
export declare const cachedUsage: CachedUsage;
|
|
68
40
|
/**
|
|
69
41
|
* Create an Anthropic SDK provider for use with OpenCode / Vercel AI SDK.
|
|
70
42
|
*
|
|
71
43
|
* When using OAuth credentials from Claude Code, requests are made to
|
|
72
44
|
* look identical to Claude Code CLI requests — same headers, user-agent,
|
|
73
45
|
* beta flags, billing system block, and body structure.
|
|
46
|
+
*
|
|
47
|
+
* OpenCode discovers this function by looking for an export whose name
|
|
48
|
+
* starts with "create" when loading the npm package as a provider.
|
|
74
49
|
*/
|
|
75
50
|
export declare function createAnthropicSDK(options?: AnthropicSDKProviderOptions): AnthropicSDKProvider;
|
|
51
|
+
/**
|
|
52
|
+
* OpenCode plugin that self-registers the anthropic-sdk provider and its
|
|
53
|
+
* supported models into the running config on startup.
|
|
54
|
+
*
|
|
55
|
+
* Add this package to the `plugin` array in your opencode.json:
|
|
56
|
+
*
|
|
57
|
+
* { "plugin": ["@xtruder/opencode-claude-max-plugin"] }
|
|
58
|
+
*
|
|
59
|
+
* The plugin uses the `config` hook which is called after config is loaded
|
|
60
|
+
* but before providers are initialized. We mutate the Config object in-place
|
|
61
|
+
* to inject the provider definition and model list.
|
|
62
|
+
*
|
|
63
|
+
* OpenCode's plugin loader iterates all module exports and calls each one
|
|
64
|
+
* as a Plugin function. `createAnthropicSDK` is also called — it returns
|
|
65
|
+
* { languageModel } which is harmlessly pushed to the hooks list with all
|
|
66
|
+
* hook slots undefined (no-ops).
|
|
67
|
+
*/
|
|
68
|
+
export declare const anthropicSDKPlugin: Plugin;
|
|
76
69
|
export default createAnthropicSDK;
|
|
77
|
-
export { AnthropicSDKModel } from "./model.js";
|
|
78
|
-
export type { ConvertedPrompt } from "./prompt.js";
|
|
79
|
-
export { readClaudeCredentials, isExpired, getCredentialsPath, type ClaudeOAuthCredentials, } from "./credentials.js";
|
|
80
70
|
//# sourceMappingURL=index.d.ts.map
|
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;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,CAiHtB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAoBhC,CAAA;AAID,eAAe,kBAAkB,CAAA"}
|
package/build/index.js
CHANGED
|
@@ -28,7 +28,6 @@ var BUILTIN_OPENCODE_TO_CLAUDE = {
|
|
|
28
28
|
codesearch: "CodeSearch",
|
|
29
29
|
lsp: "LSP"
|
|
30
30
|
};
|
|
31
|
-
var BUILTIN_OPENCODE_NAMES = new Set(Object.keys(BUILTIN_OPENCODE_TO_CLAUDE));
|
|
32
31
|
var BUILTIN_CLAUDE_TO_OPENCODE = {};
|
|
33
32
|
for (const [oc, cc] of Object.entries(BUILTIN_OPENCODE_TO_CLAUDE)) {
|
|
34
33
|
BUILTIN_CLAUDE_TO_OPENCODE[cc] = oc;
|
|
@@ -55,7 +54,7 @@ function detectMcpServers() {
|
|
|
55
54
|
}
|
|
56
55
|
} catch {}
|
|
57
56
|
}
|
|
58
|
-
return [...servers].
|
|
57
|
+
return [...servers].toSorted((a, b) => b.length - a.length);
|
|
59
58
|
}
|
|
60
59
|
function getMcpServers() {
|
|
61
60
|
if (mcpServerNames === null) {
|
|
@@ -265,10 +264,19 @@ function formatToolResultContent(output) {
|
|
|
265
264
|
}
|
|
266
265
|
}
|
|
267
266
|
|
|
267
|
+
// src/usage-cache.ts
|
|
268
|
+
var cachedUsage = {};
|
|
269
|
+
|
|
268
270
|
// src/credentials.ts
|
|
269
271
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
272
|
+
import { execSync } from "node:child_process";
|
|
270
273
|
import { join as join2 } from "node:path";
|
|
271
274
|
import { homedir as homedir2 } from "node:os";
|
|
275
|
+
var CREDENTIAL_CACHE_TTL_MS = 30000;
|
|
276
|
+
var EXPIRY_BUFFER_MS = 60000;
|
|
277
|
+
var cachedCreds = null;
|
|
278
|
+
var cachedAt = 0;
|
|
279
|
+
var cachedPath;
|
|
272
280
|
function getCredentialsPath() {
|
|
273
281
|
return join2(homedir2(), ".claude", ".credentials.json");
|
|
274
282
|
}
|
|
@@ -287,8 +295,57 @@ function readClaudeCredentials(path) {
|
|
|
287
295
|
}
|
|
288
296
|
}
|
|
289
297
|
function isExpired(creds) {
|
|
290
|
-
|
|
291
|
-
|
|
298
|
+
return Date.now() > creds.expiresAt - EXPIRY_BUFFER_MS;
|
|
299
|
+
}
|
|
300
|
+
function refreshViaCli() {
|
|
301
|
+
const maxAttempts = 2;
|
|
302
|
+
for (let i = 0;i < maxAttempts; i++) {
|
|
303
|
+
try {
|
|
304
|
+
execSync("claude -p . --model haiku", {
|
|
305
|
+
timeout: 60000,
|
|
306
|
+
encoding: "utf-8",
|
|
307
|
+
env: { ...process.env, TERM: "dumb" },
|
|
308
|
+
stdio: "ignore"
|
|
309
|
+
});
|
|
310
|
+
return true;
|
|
311
|
+
} catch {}
|
|
312
|
+
}
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
function refreshIfNeeded(path) {
|
|
316
|
+
const creds = readClaudeCredentials(path);
|
|
317
|
+
if (!creds)
|
|
318
|
+
return null;
|
|
319
|
+
if (!isExpired(creds))
|
|
320
|
+
return creds;
|
|
321
|
+
console.warn("[anthropic-sdk-provider] OAuth token near expiry, refreshing via Claude CLI...");
|
|
322
|
+
const refreshed = refreshViaCli();
|
|
323
|
+
if (!refreshed) {
|
|
324
|
+
console.warn("[anthropic-sdk-provider] CLI refresh failed. Token may be expired.");
|
|
325
|
+
return creds;
|
|
326
|
+
}
|
|
327
|
+
const fresh = readClaudeCredentials(path);
|
|
328
|
+
if (fresh && !isExpired(fresh)) {
|
|
329
|
+
console.warn("[anthropic-sdk-provider] Token refreshed successfully.");
|
|
330
|
+
return fresh;
|
|
331
|
+
}
|
|
332
|
+
return fresh ?? creds;
|
|
333
|
+
}
|
|
334
|
+
function getCachedCredentials(path) {
|
|
335
|
+
const now = Date.now();
|
|
336
|
+
const resolvedPath = path ?? cachedPath;
|
|
337
|
+
if (cachedCreds && now - cachedAt < CREDENTIAL_CACHE_TTL_MS && !isExpired(cachedCreds)) {
|
|
338
|
+
return cachedCreds;
|
|
339
|
+
}
|
|
340
|
+
const fresh = refreshIfNeeded(resolvedPath);
|
|
341
|
+
if (fresh) {
|
|
342
|
+
cachedCreds = fresh;
|
|
343
|
+
cachedAt = now;
|
|
344
|
+
cachedPath = resolvedPath;
|
|
345
|
+
} else {
|
|
346
|
+
cachedCreds = null;
|
|
347
|
+
}
|
|
348
|
+
return cachedCreds;
|
|
292
349
|
}
|
|
293
350
|
|
|
294
351
|
// src/usage.ts
|
|
@@ -682,8 +739,8 @@ function formatReset2(epoch) {
|
|
|
682
739
|
const dateStr = reset.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
683
740
|
return diffMs < 86400000 ? `Resets ${timeStr}` : `Resets ${dateStr}, ${timeStr}`;
|
|
684
741
|
}
|
|
742
|
+
var bar = (pct, width = 50) => "█".repeat(Math.round(pct / 100 * width)) + " ".repeat(width - Math.round(pct / 100 * width));
|
|
685
743
|
function formatCachedUsage(usage) {
|
|
686
|
-
const bar = (pct, width = 50) => "█".repeat(Math.round(pct / 100 * width)) + " ".repeat(width - Math.round(pct / 100 * width));
|
|
687
744
|
const lines = [];
|
|
688
745
|
const h5 = Math.round((usage.fiveHourUtil ?? 0) * 100);
|
|
689
746
|
lines.push(" Current session");
|
|
@@ -763,7 +820,11 @@ class AnthropicSDKModel {
|
|
|
763
820
|
const rewrite = (s) => rewriteToolNamesInText(s);
|
|
764
821
|
const SYSTEM_CACHE = { type: "ephemeral", ttl: "1h", scope: "global" };
|
|
765
822
|
if (system) {
|
|
766
|
-
const contentBlocks = typeof system === "string" ? [{ type: "text", text: rewrite(system), cache_control: SYSTEM_CACHE }] : Array.isArray(system) ? system.map((b, i) => b.type === "text" ? {
|
|
823
|
+
const contentBlocks = typeof system === "string" ? [{ type: "text", text: rewrite(system), cache_control: SYSTEM_CACHE }] : Array.isArray(system) ? system.map((b, i) => b.type === "text" ? {
|
|
824
|
+
...b,
|
|
825
|
+
text: rewrite(b.text),
|
|
826
|
+
...i === system.length - 1 ? { cache_control: SYSTEM_CACHE } : {}
|
|
827
|
+
} : b) : [{ ...system, cache_control: SYSTEM_CACHE }];
|
|
767
828
|
params.system = [BILLING_SYSTEM_BLOCK, IDENTITY_SYSTEM_BLOCK, ...contentBlocks];
|
|
768
829
|
} else {
|
|
769
830
|
params.system = [BILLING_SYSTEM_BLOCK, IDENTITY_SYSTEM_BLOCK];
|
|
@@ -774,7 +835,9 @@ class AnthropicSDKModel {
|
|
|
774
835
|
if (options.temperature == null) {
|
|
775
836
|
params.temperature = 1;
|
|
776
837
|
}
|
|
777
|
-
|
|
838
|
+
const providerOpts = options.providerOptions?.["anthropic-sdk"] ?? options.providerOptions?.anthropic;
|
|
839
|
+
const effort = providerOpts?.effort ?? "medium";
|
|
840
|
+
params.output_config = { effort };
|
|
778
841
|
}
|
|
779
842
|
} else if (system) {
|
|
780
843
|
const CACHE = { type: "ephemeral" };
|
|
@@ -787,7 +850,10 @@ class AnthropicSDKModel {
|
|
|
787
850
|
const blocks = system.map((b, i) => b.type === "text" && i === system.length - 1 ? { ...b, cache_control: CACHE } : b);
|
|
788
851
|
params.system = [{ ...IDENTITY_SYSTEM_BLOCK, cache_control: CACHE }, ...blocks];
|
|
789
852
|
} else {
|
|
790
|
-
params.system = [
|
|
853
|
+
params.system = [
|
|
854
|
+
{ ...IDENTITY_SYSTEM_BLOCK, cache_control: CACHE },
|
|
855
|
+
{ ...system, cache_control: CACHE }
|
|
856
|
+
];
|
|
791
857
|
}
|
|
792
858
|
}
|
|
793
859
|
if (tools && tools.length > 0)
|
|
@@ -973,11 +1039,62 @@ var OAUTH_BETAS = [
|
|
|
973
1039
|
"effort-2025-11-24"
|
|
974
1040
|
];
|
|
975
1041
|
var CONTEXT_1M_BETA = "context-1m-2025-08-07";
|
|
976
|
-
var API_KEY_BETAS = [
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1042
|
+
var API_KEY_BETAS = ["interleaved-thinking-2025-05-14", "fine-grained-tool-streaming-2025-05-14"];
|
|
1043
|
+
var PACKAGE_NAME = "@xtruder/opencode-claude-max-plugin";
|
|
1044
|
+
var PROVIDER_ID = "anthropic-sdk";
|
|
1045
|
+
var PLUGIN_MODELS = {
|
|
1046
|
+
"claude-haiku-4-5": {
|
|
1047
|
+
name: "Claude Haiku 4.5",
|
|
1048
|
+
reasoning: false,
|
|
1049
|
+
tool_call: true,
|
|
1050
|
+
attachment: true,
|
|
1051
|
+
temperature: true,
|
|
1052
|
+
limit: { context: 200000, output: 8192 },
|
|
1053
|
+
cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 },
|
|
1054
|
+
modalities: {
|
|
1055
|
+
input: ["text", "image"],
|
|
1056
|
+
output: ["text"]
|
|
1057
|
+
}
|
|
1058
|
+
},
|
|
1059
|
+
"claude-sonnet-4-6": {
|
|
1060
|
+
name: "Claude Sonnet 4.6",
|
|
1061
|
+
reasoning: true,
|
|
1062
|
+
tool_call: true,
|
|
1063
|
+
attachment: true,
|
|
1064
|
+
temperature: true,
|
|
1065
|
+
limit: { context: 200000, output: 16000 },
|
|
1066
|
+
cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
1067
|
+
modalities: {
|
|
1068
|
+
input: ["text", "image", "pdf"],
|
|
1069
|
+
output: ["text"]
|
|
1070
|
+
},
|
|
1071
|
+
options: { effort: "medium" },
|
|
1072
|
+
variants: {
|
|
1073
|
+
low: { effort: "low" },
|
|
1074
|
+
medium: { effort: "medium" },
|
|
1075
|
+
high: { effort: "high" }
|
|
1076
|
+
}
|
|
1077
|
+
},
|
|
1078
|
+
"claude-opus-4-6": {
|
|
1079
|
+
name: "Claude Opus 4.6",
|
|
1080
|
+
reasoning: true,
|
|
1081
|
+
tool_call: true,
|
|
1082
|
+
attachment: true,
|
|
1083
|
+
temperature: true,
|
|
1084
|
+
limit: { context: 200000, output: 32000 },
|
|
1085
|
+
cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 },
|
|
1086
|
+
modalities: {
|
|
1087
|
+
input: ["text", "image", "pdf"],
|
|
1088
|
+
output: ["text"]
|
|
1089
|
+
},
|
|
1090
|
+
options: { effort: "medium" },
|
|
1091
|
+
variants: {
|
|
1092
|
+
low: { effort: "low" },
|
|
1093
|
+
medium: { effort: "medium" },
|
|
1094
|
+
high: { effort: "high" }
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
981
1098
|
function resolveAuth(options) {
|
|
982
1099
|
if (options.apiKey) {
|
|
983
1100
|
return { apiKey: options.apiKey, isOAuth: false };
|
|
@@ -985,25 +1102,16 @@ function resolveAuth(options) {
|
|
|
985
1102
|
if (process.env.ANTHROPIC_API_KEY) {
|
|
986
1103
|
return { apiKey: process.env.ANTHROPIC_API_KEY, isOAuth: false };
|
|
987
1104
|
}
|
|
988
|
-
const
|
|
1105
|
+
const credentialsPath = typeof options.credentialsPath === "string" ? options.credentialsPath : undefined;
|
|
1106
|
+
const creds = getCachedCredentials(credentialsPath);
|
|
989
1107
|
if (creds) {
|
|
990
|
-
if (isExpired(creds)) {
|
|
991
|
-
console.warn("[anthropic-sdk-provider] Claude Code OAuth token is expired. " + "Run 'claude' to re-authenticate, then restart.");
|
|
992
|
-
}
|
|
993
1108
|
return { apiKey: null, authToken: creds.accessToken, isOAuth: true };
|
|
994
1109
|
}
|
|
995
1110
|
return { isOAuth: false };
|
|
996
1111
|
}
|
|
997
1112
|
function createAnthropicSDK(options = {}) {
|
|
998
|
-
const {
|
|
999
|
-
|
|
1000
|
-
baseURL,
|
|
1001
|
-
headers,
|
|
1002
|
-
fetch: customFetch,
|
|
1003
|
-
name = "anthropic-sdk",
|
|
1004
|
-
credentialsPath,
|
|
1005
|
-
...rest
|
|
1006
|
-
} = options;
|
|
1113
|
+
const { baseURL, headers, fetch: customFetch, name = "anthropic-sdk" } = options;
|
|
1114
|
+
const credentialsPath = typeof options.credentialsPath === "string" ? options.credentialsPath : undefined;
|
|
1007
1115
|
const auth = resolveAuth(options);
|
|
1008
1116
|
const defaultHeaders = {
|
|
1009
1117
|
...headers,
|
|
@@ -1018,12 +1126,20 @@ function createAnthropicSDK(options = {}) {
|
|
|
1018
1126
|
const CONTEXT_1M_BODY_THRESHOLD = 600000;
|
|
1019
1127
|
const baseFetch = customFetch ?? globalThis.fetch;
|
|
1020
1128
|
const wrappedFetch = async (url, init) => {
|
|
1129
|
+
if (auth.isOAuth && init) {
|
|
1130
|
+
const freshCreds = getCachedCredentials(credentialsPath);
|
|
1131
|
+
if (freshCreds) {
|
|
1132
|
+
const reqHeaders = new Headers(init.headers);
|
|
1133
|
+
reqHeaders.set("authorization", `Bearer ${freshCreds.accessToken}`);
|
|
1134
|
+
init = { ...init, headers: reqHeaders };
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1021
1137
|
if (init?.body && typeof init.body === "string" && init.body.length > CONTEXT_1M_BODY_THRESHOLD) {
|
|
1022
|
-
const
|
|
1023
|
-
const existingBetas =
|
|
1138
|
+
const betaHeaders = new Headers(init.headers);
|
|
1139
|
+
const existingBetas = betaHeaders.get("anthropic-beta") ?? "";
|
|
1024
1140
|
if (!existingBetas.includes(CONTEXT_1M_BETA)) {
|
|
1025
|
-
|
|
1026
|
-
init = { ...init, headers:
|
|
1141
|
+
betaHeaders.set("anthropic-beta", existingBetas + "," + CONTEXT_1M_BETA);
|
|
1142
|
+
init = { ...init, headers: betaHeaders };
|
|
1027
1143
|
}
|
|
1028
1144
|
}
|
|
1029
1145
|
const resp = await baseFetch(url, init);
|
|
@@ -1043,13 +1159,12 @@ function createAnthropicSDK(options = {}) {
|
|
|
1043
1159
|
const isLongContextLimit = bodyText.includes("Extra usage is required for long context");
|
|
1044
1160
|
const isSubscriptionLimit = isLongContextLimit || unifiedStatus === "over_limit" || retryAfter > 120;
|
|
1045
1161
|
if (isSubscriptionLimit) {
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
return new Response(body, {
|
|
1162
|
+
const retryHeaders = new Headers(resp.headers);
|
|
1163
|
+
retryHeaders.set("x-should-retry", "false");
|
|
1164
|
+
return new Response(bodyText, {
|
|
1050
1165
|
status: resp.status,
|
|
1051
1166
|
statusText: resp.statusText,
|
|
1052
|
-
headers:
|
|
1167
|
+
headers: retryHeaders
|
|
1053
1168
|
});
|
|
1054
1169
|
}
|
|
1055
1170
|
}
|
|
@@ -1068,16 +1183,23 @@ function createAnthropicSDK(options = {}) {
|
|
|
1068
1183
|
}
|
|
1069
1184
|
};
|
|
1070
1185
|
}
|
|
1186
|
+
var anthropicSDKPlugin = async () => {
|
|
1187
|
+
return {
|
|
1188
|
+
config: async (cfg) => {
|
|
1189
|
+
if (!cfg.provider) {
|
|
1190
|
+
cfg.provider = {};
|
|
1191
|
+
}
|
|
1192
|
+
cfg.provider[PROVIDER_ID] = {
|
|
1193
|
+
npm: PACKAGE_NAME,
|
|
1194
|
+
models: PLUGIN_MODELS,
|
|
1195
|
+
...cfg.provider[PROVIDER_ID]
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
};
|
|
1071
1200
|
var src_default = createAnthropicSDK;
|
|
1072
1201
|
export {
|
|
1073
|
-
readClaudeCredentials,
|
|
1074
|
-
isExpired,
|
|
1075
|
-
getCredentialsPath,
|
|
1076
1202
|
src_default as default,
|
|
1077
1203
|
createAnthropicSDK,
|
|
1078
|
-
|
|
1079
|
-
OAUTH_BETAS,
|
|
1080
|
-
CONTEXT_1M_BETA,
|
|
1081
|
-
AnthropicSDKModel,
|
|
1082
|
-
API_KEY_BETAS
|
|
1204
|
+
anthropicSDKPlugin
|
|
1083
1205
|
};
|
package/build/model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAK3B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAU9C,KAAK,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;AAC1E,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAK3B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAU9C,KAAK,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;AAC1E,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;AAsLtE,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;IAoF1E,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;CA6C7E"}
|
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,EAAE,yBAAyB,EAA+B,MAAM,kBAAkB,CAAA;AAC9F,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAG9C,KAAK,kBAAkB,GAAG,SAAS,CAAC,kBAAkB,CAAA;AAiCtD,wBAAgB,aAAa,CAC3B,eAAe,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAClD,OAAO,EAAE,MAAM,GACd,cAAc,CAAC,yBAAyB,CAAC,CAqD3C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-names.d.ts","sourceRoot":"","sources":["../src/tool-names.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool-names.d.ts","sourceRoot":"","sources":["../src/tool-names.ts"],"names":[],"mappings":"AAiDA;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAE1D;AAgED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAY7D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAiB7D;AAqCD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM3D"}
|
package/build/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,kCAAkC,EAClC,yBAAyB,EAC1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAG9C,KAAK,aAAa,GAAG,SAAS,CAAC,IAAI,CAAA;AACnC,KAAK,mBAAmB,GAAG,SAAS,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAA;AAwBvE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,KAAK,CAAC,2BAA2B,GAAG,kCAAkC,CAAC,GAAG,SAAS,GACzF,aAAa,EAAE,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,kCAAkC,EAClC,yBAAyB,EAC1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAG9C,KAAK,aAAa,GAAG,SAAS,CAAC,IAAI,CAAA;AACnC,KAAK,mBAAmB,GAAG,SAAS,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAA;AAwBvE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,KAAK,CAAC,2BAA2B,GAAG,kCAAkC,CAAC,GAAG,SAAS,GACzF,aAAa,EAAE,GAAG,SAAS,CAY7B;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,yBAAyB,GAAG,SAAS,GAChD,mBAAmB,GAAG,SAAS,CAcjC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cached ratelimit usage from the most recent API response headers.
|
|
3
|
+
* Updated on every successful inference call — same data Claude Code
|
|
4
|
+
* reads for its /usage display. No extra API call needed.
|
|
5
|
+
*/
|
|
6
|
+
export interface CachedUsage {
|
|
7
|
+
fiveHourUtil?: number;
|
|
8
|
+
sevenDayUtil?: number;
|
|
9
|
+
fiveHourReset?: number;
|
|
10
|
+
sevenDayReset?: number;
|
|
11
|
+
overageStatus?: string;
|
|
12
|
+
timestamp?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const cachedUsage: CachedUsage;
|
|
15
|
+
//# sourceMappingURL=usage-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-cache.d.ts","sourceRoot":"","sources":["../src/usage-cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,WAAW,EAAE,WAAgB,CAAA"}
|
package/build/usage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,gBAAgB,EAAE,WAAW,GAAG,IAAI,CAAA;IACpC,cAAc,EAAE,WAAW,GAAG,IAAI,CAAA;IAClC,oBAAoB,EAAE,WAAW,GAAG,IAAI,CAAA;IACxC,gBAAgB,EAAE,WAAW,GAAG,IAAI,CAAA;IACpC,WAAW,EAAE;QACX,UAAU,EAAE,OAAO,CAAA;QACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;QAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;QAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAC3B,GAAG,IAAI,CAAA;CACT;AAID;;;GAGG;AACH,wBAAsB,UAAU,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAuBpF;AA0CD;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,gBAAgB,EAAE,WAAW,GAAG,IAAI,CAAA;IACpC,cAAc,EAAE,WAAW,GAAG,IAAI,CAAA;IAClC,oBAAoB,EAAE,WAAW,GAAG,IAAI,CAAA;IACxC,gBAAgB,EAAE,WAAW,GAAG,IAAI,CAAA;IACpC,WAAW,EAAE;QACX,UAAU,EAAE,OAAO,CAAA;QACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;QAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;QAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAC3B,GAAG,IAAI,CAAA;CACT;AAID;;;GAGG;AACH,wBAAsB,UAAU,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAuBpF;AA0CD;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAwDnD"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtruder/opencode-claude-max-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "OpenCode plugin for Claude Max/Pro subscription via @anthropic-ai/sdk - uses Claude Code OAuth credentials",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai-sdk",
|
|
7
|
+
"anthropic",
|
|
8
|
+
"claude",
|
|
9
|
+
"opencode",
|
|
10
|
+
"provider"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"files": [
|
|
14
|
+
"build"
|
|
15
|
+
],
|
|
5
16
|
"type": "module",
|
|
6
17
|
"main": "build/index.js",
|
|
7
18
|
"types": "build/index.d.ts",
|
|
@@ -9,42 +20,37 @@
|
|
|
9
20
|
".": {
|
|
10
21
|
"import": "./build/index.js",
|
|
11
22
|
"types": "./build/index.d.ts"
|
|
12
|
-
},
|
|
13
|
-
"./agent": {
|
|
14
|
-
"import": "./build/agent-index.js",
|
|
15
|
-
"types": "./build/agent-index.d.ts"
|
|
16
23
|
}
|
|
17
24
|
},
|
|
18
|
-
"bin": {
|
|
19
|
-
"claude-usage": "build/usage-cli.js"
|
|
20
|
-
},
|
|
21
|
-
"files": [
|
|
22
|
-
"build"
|
|
23
|
-
],
|
|
24
25
|
"scripts": {
|
|
25
|
-
"build": "bun build ./src/index.ts
|
|
26
|
-
"
|
|
26
|
+
"build": "bun build ./src/index.ts --outdir ./build --target node --format esm --packages external && bun x tsc --emitDeclarationOnly --declaration --outDir build",
|
|
27
|
+
"dev": "bun run dev:config && bun build ./src/index.ts --outdir ./build --target node --format esm --packages external --watch",
|
|
28
|
+
"dev:config": "bun scripts/dev-config.ts",
|
|
29
|
+
"test": "bun test --concurrency 1 src/*.test.ts",
|
|
30
|
+
"lint": "oxlint src",
|
|
31
|
+
"lint:fix": "oxlint --fix src",
|
|
32
|
+
"format": "oxfmt --write .",
|
|
33
|
+
"format:check": "oxfmt --check .",
|
|
34
|
+
"prepare": "simple-git-hooks"
|
|
27
35
|
},
|
|
28
36
|
"dependencies": {
|
|
29
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.81",
|
|
30
37
|
"@anthropic-ai/sdk": "0.74.0"
|
|
31
38
|
},
|
|
32
|
-
"peerDependencies": {
|
|
33
|
-
"@ai-sdk/provider": ">=2.0.0"
|
|
34
|
-
},
|
|
35
39
|
"devDependencies": {
|
|
36
40
|
"@ai-sdk/provider": "2.0.1",
|
|
37
41
|
"@ai-sdk/provider-utils": "3.0.2",
|
|
42
|
+
"@opencode-ai/plugin": "^1.3.0",
|
|
38
43
|
"ai": "5.0.124",
|
|
39
44
|
"bun-types": "^1.3.11",
|
|
45
|
+
"oxfmt": "^0.41.0",
|
|
46
|
+
"oxlint": "^1.56.0",
|
|
47
|
+
"simple-git-hooks": "^2.13.1",
|
|
40
48
|
"typescript": "^5.7.0"
|
|
41
49
|
},
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
],
|
|
49
|
-
"license": "MIT"
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@ai-sdk/provider": ">=2.0.0"
|
|
52
|
+
},
|
|
53
|
+
"simple-git-hooks": {
|
|
54
|
+
"pre-commit": "bun run lint && bun run format:check"
|
|
55
|
+
}
|
|
50
56
|
}
|
package/build/agent-index.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { LanguageModelV2 } from "@ai-sdk/provider";
|
|
2
|
-
export interface AgentSDKProviderOptions {
|
|
3
|
-
/**
|
|
4
|
-
* Provider name for logging. Defaults to "anthropic-agent-sdk".
|
|
5
|
-
*/
|
|
6
|
-
name?: string;
|
|
7
|
-
/**
|
|
8
|
-
* Any additional options (passed through by OpenCode).
|
|
9
|
-
*/
|
|
10
|
-
[key: string]: unknown;
|
|
11
|
-
}
|
|
12
|
-
export interface AgentSDKProvider {
|
|
13
|
-
languageModel(modelId: string): LanguageModelV2;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Create an Anthropic Agent SDK provider for use with OpenCode / Vercel AI SDK.
|
|
17
|
-
*
|
|
18
|
-
* Uses @anthropic-ai/claude-agent-sdk which handles authentication via
|
|
19
|
-
* Claude Code's built-in OAuth flow — no API keys or billing headers needed.
|
|
20
|
-
*/
|
|
21
|
-
export declare function createAgentSDK(options?: AgentSDKProviderOptions): AgentSDKProvider;
|
|
22
|
-
export default createAgentSDK;
|
|
23
|
-
export { AgentSDKModel } from "./agent-model.js";
|
|
24
|
-
//# sourceMappingURL=agent-index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agent-index.d.ts","sourceRoot":"","sources":["../src/agent-index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEvD,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAAA;CAChD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,OAAO,GAAE,uBAA4B,GACpC,gBAAgB,CAQlB;AAED,eAAe,cAAc,CAAA;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA"}
|
package/build/agent-index.js
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
// src/agent-model.ts
|
|
2
|
-
import {
|
|
3
|
-
unstable_v2_createSession
|
|
4
|
-
} from "@anthropic-ai/claude-agent-sdk";
|
|
5
|
-
function mapFinishReason(reason) {
|
|
6
|
-
switch (reason) {
|
|
7
|
-
case "end_turn":
|
|
8
|
-
return "stop";
|
|
9
|
-
case "stop_sequence":
|
|
10
|
-
return "stop";
|
|
11
|
-
case "max_tokens":
|
|
12
|
-
return "length";
|
|
13
|
-
case "tool_use":
|
|
14
|
-
return "tool-calls";
|
|
15
|
-
default:
|
|
16
|
-
return "unknown";
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function extractSystem(prompt) {
|
|
20
|
-
const parts = [];
|
|
21
|
-
for (const msg of prompt) {
|
|
22
|
-
if (msg.role === "system")
|
|
23
|
-
parts.push(msg.content);
|
|
24
|
-
}
|
|
25
|
-
return parts.length > 0 ? parts.join(`
|
|
26
|
-
|
|
27
|
-
`) : undefined;
|
|
28
|
-
}
|
|
29
|
-
function extractMessages(prompt) {
|
|
30
|
-
return prompt.filter((m) => m.role !== "system");
|
|
31
|
-
}
|
|
32
|
-
function formatLastMessage(msg) {
|
|
33
|
-
if (msg.role === "user") {
|
|
34
|
-
return msg.content.map((p) => {
|
|
35
|
-
if (p.type === "text")
|
|
36
|
-
return p.text;
|
|
37
|
-
return "";
|
|
38
|
-
}).filter(Boolean).join(`
|
|
39
|
-
`);
|
|
40
|
-
}
|
|
41
|
-
if (msg.role === "tool") {
|
|
42
|
-
return msg.content.map((p) => {
|
|
43
|
-
const output = p.output;
|
|
44
|
-
if (output.type === "text" || output.type === "error-text")
|
|
45
|
-
return output.value;
|
|
46
|
-
if (output.type === "json" || output.type === "error-json")
|
|
47
|
-
return JSON.stringify(output.value);
|
|
48
|
-
return String(output.value);
|
|
49
|
-
}).join(`
|
|
50
|
-
`);
|
|
51
|
-
}
|
|
52
|
-
return "";
|
|
53
|
-
}
|
|
54
|
-
function convertAgentStream(stream, modelId) {
|
|
55
|
-
let idCounter = 0;
|
|
56
|
-
return new ReadableStream({
|
|
57
|
-
async start(controller) {
|
|
58
|
-
try {
|
|
59
|
-
let emittedMetadata = false;
|
|
60
|
-
let firstAssistantTurnDone = false;
|
|
61
|
-
for await (const msg of stream) {
|
|
62
|
-
if (firstAssistantTurnDone) {
|
|
63
|
-
if (msg.type === "result")
|
|
64
|
-
break;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (msg.type === "assistant") {
|
|
68
|
-
const message = msg.message;
|
|
69
|
-
const content = message.content ?? [];
|
|
70
|
-
if (!emittedMetadata && message.id) {
|
|
71
|
-
emittedMetadata = true;
|
|
72
|
-
controller.enqueue({
|
|
73
|
-
type: "response-metadata",
|
|
74
|
-
id: message.id,
|
|
75
|
-
modelId: message.model ?? modelId,
|
|
76
|
-
timestamp: new Date
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
for (const block of content) {
|
|
80
|
-
if (block.type === "text") {
|
|
81
|
-
const id = `block-${idCounter++}`;
|
|
82
|
-
controller.enqueue({ type: "text-start", id });
|
|
83
|
-
controller.enqueue({ type: "text-delta", id, delta: block.text });
|
|
84
|
-
controller.enqueue({ type: "text-end", id });
|
|
85
|
-
} else if (block.type === "thinking") {
|
|
86
|
-
const id = `block-${idCounter++}`;
|
|
87
|
-
controller.enqueue({ type: "reasoning-start", id });
|
|
88
|
-
controller.enqueue({ type: "reasoning-delta", id, delta: block.thinking });
|
|
89
|
-
controller.enqueue({ type: "reasoning-end", id });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (msg.type === "result") {
|
|
94
|
-
const result = msg;
|
|
95
|
-
const cacheRead = result.usage?.cache_read_input_tokens ?? 0;
|
|
96
|
-
const cacheCreate = result.usage?.cache_creation_input_tokens ?? 0;
|
|
97
|
-
controller.enqueue({
|
|
98
|
-
type: "finish",
|
|
99
|
-
finishReason: mapFinishReason(result.stop_reason),
|
|
100
|
-
usage: {
|
|
101
|
-
inputTokens: result.usage?.input_tokens ?? 0,
|
|
102
|
-
outputTokens: result.usage?.output_tokens ?? 0,
|
|
103
|
-
totalTokens: (result.usage?.input_tokens ?? 0) + (result.usage?.output_tokens ?? 0) + cacheRead + cacheCreate,
|
|
104
|
-
cachedInputTokens: cacheRead
|
|
105
|
-
},
|
|
106
|
-
providerMetadata: {
|
|
107
|
-
anthropic: {
|
|
108
|
-
cacheCreationInputTokens: cacheCreate
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
firstAssistantTurnDone = true;
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
} catch (error) {
|
|
117
|
-
controller.enqueue({ type: "error", error });
|
|
118
|
-
} finally {
|
|
119
|
-
controller.close();
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
class AgentSDKModel {
|
|
126
|
-
providerName;
|
|
127
|
-
specificationVersion = "v2";
|
|
128
|
-
provider;
|
|
129
|
-
modelId;
|
|
130
|
-
supportedUrls = {};
|
|
131
|
-
session = null;
|
|
132
|
-
processedMessageCount = 0;
|
|
133
|
-
lastSystemPrompt = undefined;
|
|
134
|
-
constructor(modelId, providerName) {
|
|
135
|
-
this.providerName = providerName;
|
|
136
|
-
this.modelId = modelId;
|
|
137
|
-
this.provider = providerName;
|
|
138
|
-
}
|
|
139
|
-
needsNewSession(system, messages) {
|
|
140
|
-
if (!this.session)
|
|
141
|
-
return true;
|
|
142
|
-
if (system !== this.lastSystemPrompt)
|
|
143
|
-
return true;
|
|
144
|
-
if (messages.length < this.processedMessageCount)
|
|
145
|
-
return true;
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
createSession(system, tools) {
|
|
149
|
-
const opts = {
|
|
150
|
-
model: this.modelId,
|
|
151
|
-
maxTurns: 100,
|
|
152
|
-
includePartialMessages: true,
|
|
153
|
-
permissionMode: "bypassPermissions",
|
|
154
|
-
allowDangerouslySkipPermissions: true,
|
|
155
|
-
persistSession: false,
|
|
156
|
-
systemPrompt: system ?? "You are a helpful assistant.",
|
|
157
|
-
tools: []
|
|
158
|
-
};
|
|
159
|
-
return unstable_v2_createSession(opts);
|
|
160
|
-
}
|
|
161
|
-
async doStream(options) {
|
|
162
|
-
const system = extractSystem(options.prompt);
|
|
163
|
-
const messages = extractMessages(options.prompt);
|
|
164
|
-
const warnings = [];
|
|
165
|
-
if (this.needsNewSession(system, messages)) {
|
|
166
|
-
if (this.session) {
|
|
167
|
-
try {
|
|
168
|
-
this.session.close();
|
|
169
|
-
} catch {}
|
|
170
|
-
}
|
|
171
|
-
this.session = this.createSession(system, options.tools);
|
|
172
|
-
this.lastSystemPrompt = system;
|
|
173
|
-
this.processedMessageCount = 0;
|
|
174
|
-
}
|
|
175
|
-
let messageToSend;
|
|
176
|
-
if (this.processedMessageCount === 0) {
|
|
177
|
-
const firstUser = messages.find((m) => m.role === "user");
|
|
178
|
-
if (firstUser) {
|
|
179
|
-
messageToSend = formatLastMessage(firstUser);
|
|
180
|
-
this.processedMessageCount = messages.indexOf(firstUser) + 1;
|
|
181
|
-
}
|
|
182
|
-
} else {
|
|
183
|
-
const newMessages = messages.slice(this.processedMessageCount);
|
|
184
|
-
const toolMsg = newMessages.find((m) => m.role === "tool");
|
|
185
|
-
if (toolMsg) {
|
|
186
|
-
messageToSend = formatLastMessage(toolMsg);
|
|
187
|
-
} else {
|
|
188
|
-
const userMsg = [...newMessages].reverse().find((m) => m.role === "user");
|
|
189
|
-
if (userMsg) {
|
|
190
|
-
messageToSend = formatLastMessage(userMsg);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
this.processedMessageCount = messages.length;
|
|
194
|
-
}
|
|
195
|
-
if (!messageToSend) {
|
|
196
|
-
messageToSend = "Continue.";
|
|
197
|
-
}
|
|
198
|
-
await this.session.send(messageToSend);
|
|
199
|
-
const stream = convertAgentStream(this.session.stream(), this.modelId);
|
|
200
|
-
const wrappedStream = new ReadableStream({
|
|
201
|
-
async start(controller) {
|
|
202
|
-
controller.enqueue({ type: "stream-start", warnings });
|
|
203
|
-
const reader = stream.getReader();
|
|
204
|
-
try {
|
|
205
|
-
while (true) {
|
|
206
|
-
const { done, value } = await reader.read();
|
|
207
|
-
if (done)
|
|
208
|
-
break;
|
|
209
|
-
controller.enqueue(value);
|
|
210
|
-
}
|
|
211
|
-
} finally {
|
|
212
|
-
reader.releaseLock();
|
|
213
|
-
controller.close();
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
return {
|
|
218
|
-
stream: wrappedStream,
|
|
219
|
-
request: { body: messageToSend }
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
async doGenerate(options) {
|
|
223
|
-
const result = await this.doStream(options);
|
|
224
|
-
const reader = result.stream.getReader();
|
|
225
|
-
const content = [];
|
|
226
|
-
let finishReason = "unknown";
|
|
227
|
-
let usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
228
|
-
let responseId = "";
|
|
229
|
-
let responseModel = this.modelId;
|
|
230
|
-
let currentText = "";
|
|
231
|
-
while (true) {
|
|
232
|
-
const { done, value } = await reader.read();
|
|
233
|
-
if (done)
|
|
234
|
-
break;
|
|
235
|
-
switch (value.type) {
|
|
236
|
-
case "text-delta":
|
|
237
|
-
currentText += value.delta ?? "";
|
|
238
|
-
break;
|
|
239
|
-
case "text-end":
|
|
240
|
-
if (currentText) {
|
|
241
|
-
content.push({ type: "text", text: currentText });
|
|
242
|
-
currentText = "";
|
|
243
|
-
}
|
|
244
|
-
break;
|
|
245
|
-
case "tool-call":
|
|
246
|
-
content.push({
|
|
247
|
-
type: "tool-call",
|
|
248
|
-
toolCallId: value.toolCallId,
|
|
249
|
-
toolName: value.toolName,
|
|
250
|
-
input: value.input
|
|
251
|
-
});
|
|
252
|
-
break;
|
|
253
|
-
case "finish":
|
|
254
|
-
finishReason = value.finishReason;
|
|
255
|
-
usage = value.usage ?? usage;
|
|
256
|
-
break;
|
|
257
|
-
case "response-metadata":
|
|
258
|
-
responseId = value.id ?? "";
|
|
259
|
-
responseModel = value.modelId ?? this.modelId;
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
if (currentText) {
|
|
264
|
-
content.push({ type: "text", text: currentText });
|
|
265
|
-
}
|
|
266
|
-
return {
|
|
267
|
-
content,
|
|
268
|
-
finishReason,
|
|
269
|
-
usage,
|
|
270
|
-
response: {
|
|
271
|
-
id: responseId,
|
|
272
|
-
modelId: responseModel,
|
|
273
|
-
timestamp: new Date
|
|
274
|
-
},
|
|
275
|
-
warnings: []
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
close() {
|
|
279
|
-
if (this.session) {
|
|
280
|
-
try {
|
|
281
|
-
this.session.close();
|
|
282
|
-
} catch {}
|
|
283
|
-
this.session = null;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// src/agent-index.ts
|
|
289
|
-
function createAgentSDK(options = {}) {
|
|
290
|
-
const { name = "anthropic-agent-sdk", ...rest } = options;
|
|
291
|
-
return {
|
|
292
|
-
languageModel(modelId) {
|
|
293
|
-
return new AgentSDKModel(modelId, name);
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
var agent_index_default = createAgentSDK;
|
|
298
|
-
export {
|
|
299
|
-
agent_index_default as default,
|
|
300
|
-
createAgentSDK,
|
|
301
|
-
AgentSDKModel
|
|
302
|
-
};
|
package/build/agent-model.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { LanguageModelV2, LanguageModelV2CallOptions } from "@ai-sdk/provider";
|
|
2
|
-
type DoStreamResult = Awaited<ReturnType<LanguageModelV2["doStream"]>>;
|
|
3
|
-
type DoGenerateResult = Awaited<ReturnType<LanguageModelV2["doGenerate"]>>;
|
|
4
|
-
export declare class AgentSDKModel implements LanguageModelV2 {
|
|
5
|
-
private providerName;
|
|
6
|
-
readonly specificationVersion: "v2";
|
|
7
|
-
readonly provider: string;
|
|
8
|
-
readonly modelId: string;
|
|
9
|
-
readonly supportedUrls: Record<string, RegExp[]>;
|
|
10
|
-
private session;
|
|
11
|
-
private processedMessageCount;
|
|
12
|
-
private lastSystemPrompt;
|
|
13
|
-
constructor(modelId: string, providerName: string);
|
|
14
|
-
/**
|
|
15
|
-
* Determine if we need a new session based on the prompt.
|
|
16
|
-
* A new session is needed when:
|
|
17
|
-
* - No session exists
|
|
18
|
-
* - System prompt changed
|
|
19
|
-
* - Message count decreased (new conversation)
|
|
20
|
-
*
|
|
21
|
-
* The session is REUSED across doStream() calls for the same conversation
|
|
22
|
-
* (tool result continuations and multi-turn).
|
|
23
|
-
*/
|
|
24
|
-
private needsNewSession;
|
|
25
|
-
private createSession;
|
|
26
|
-
doStream(options: LanguageModelV2CallOptions): Promise<DoStreamResult>;
|
|
27
|
-
doGenerate(options: LanguageModelV2CallOptions): Promise<DoGenerateResult>;
|
|
28
|
-
/**
|
|
29
|
-
* Close the session and clean up resources.
|
|
30
|
-
*/
|
|
31
|
-
close(): void;
|
|
32
|
-
}
|
|
33
|
-
export {};
|
|
34
|
-
//# sourceMappingURL=agent-model.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agent-model.d.ts","sourceRoot":"","sources":["../src/agent-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAO3B,MAAM,kBAAkB,CAAA;AAMzB,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;AACtE,KAAK,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;AA6J1E,qBAAa,aAAc,YAAW,eAAe;IAajD,OAAO,CAAC,YAAY;IAZtB,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;IAErD,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,qBAAqB,CAAI;IACjC,OAAO,CAAC,gBAAgB,CAAgC;gBAItD,OAAO,EAAE,MAAM,EACP,YAAY,EAAE,MAAM;IAM9B;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,aAAa;IAoBf,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;IA2EtE,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+DhF;;OAEG;IACH,KAAK;CAMN"}
|
package/build/agent-test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agent-test.d.ts","sourceRoot":"","sources":["../src/agent-test.ts"],"names":[],"mappings":""}
|
package/build/usage-cli.d.ts
DELETED
package/build/usage-cli.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"usage-cli.d.ts","sourceRoot":"","sources":["../src/usage-cli.ts"],"names":[],"mappings":""}
|
package/build/usage-cli.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
// src/credentials.ts
|
|
4
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
|
-
function getCredentialsPath() {
|
|
8
|
-
return join(homedir(), ".claude", ".credentials.json");
|
|
9
|
-
}
|
|
10
|
-
function readClaudeCredentials(path) {
|
|
11
|
-
const credPath = path ?? getCredentialsPath();
|
|
12
|
-
if (!existsSync(credPath))
|
|
13
|
-
return null;
|
|
14
|
-
try {
|
|
15
|
-
const raw = readFileSync(credPath, "utf-8");
|
|
16
|
-
const parsed = JSON.parse(raw);
|
|
17
|
-
if (!parsed.claudeAiOauth?.accessToken)
|
|
18
|
-
return null;
|
|
19
|
-
return parsed.claudeAiOauth;
|
|
20
|
-
} catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function isExpired(creds) {
|
|
25
|
-
const buffer = 5 * 60 * 1000;
|
|
26
|
-
return Date.now() > creds.expiresAt - buffer;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// src/usage.ts
|
|
30
|
-
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
31
|
-
async function fetchUsage(credentialsPath) {
|
|
32
|
-
const creds = readClaudeCredentials(credentialsPath);
|
|
33
|
-
if (!creds)
|
|
34
|
-
return null;
|
|
35
|
-
if (isExpired(creds))
|
|
36
|
-
return null;
|
|
37
|
-
const resp = await fetch(USAGE_URL, {
|
|
38
|
-
headers: {
|
|
39
|
-
authorization: `Bearer ${creds.accessToken}`,
|
|
40
|
-
"anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
|
|
41
|
-
"content-type": "application/json",
|
|
42
|
-
"user-agent": "claude-cli/2.1.81 (external, sdk-cli)",
|
|
43
|
-
"x-app": "cli"
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
if (!resp.ok) {
|
|
47
|
-
const body = await resp.text().catch(() => "");
|
|
48
|
-
const msg = body.includes("message") ? JSON.parse(body)?.error?.message ?? body : `HTTP ${resp.status}`;
|
|
49
|
-
throw new Error(`Usage API error: ${msg}`);
|
|
50
|
-
}
|
|
51
|
-
return resp.json();
|
|
52
|
-
}
|
|
53
|
-
function progressBar(utilization, width = 50) {
|
|
54
|
-
const filled = Math.round(utilization / 100 * width);
|
|
55
|
-
const bar = "█".repeat(filled) + " ".repeat(width - filled);
|
|
56
|
-
return bar;
|
|
57
|
-
}
|
|
58
|
-
function formatReset(resetsAt) {
|
|
59
|
-
if (!resetsAt)
|
|
60
|
-
return "";
|
|
61
|
-
const reset = new Date(resetsAt);
|
|
62
|
-
const now = new Date;
|
|
63
|
-
const diffMs = reset.getTime() - now.getTime();
|
|
64
|
-
if (diffMs <= 0)
|
|
65
|
-
return "Reset now";
|
|
66
|
-
const diffMin = Math.floor(diffMs / 60000);
|
|
67
|
-
const diffHour = Math.floor(diffMin / 60);
|
|
68
|
-
const timeStr = reset.toLocaleTimeString("en-US", {
|
|
69
|
-
hour: "numeric",
|
|
70
|
-
minute: "2-digit",
|
|
71
|
-
timeZoneName: "short"
|
|
72
|
-
});
|
|
73
|
-
const dateStr = reset.toLocaleDateString("en-US", {
|
|
74
|
-
month: "short",
|
|
75
|
-
day: "numeric"
|
|
76
|
-
});
|
|
77
|
-
if (diffHour < 24) {
|
|
78
|
-
return `Resets ${timeStr}`;
|
|
79
|
-
}
|
|
80
|
-
return `Resets ${dateStr}, ${timeStr}`;
|
|
81
|
-
}
|
|
82
|
-
function formatUsage(data) {
|
|
83
|
-
const lines = [];
|
|
84
|
-
if (data.five_hour) {
|
|
85
|
-
const pct = Math.round(data.five_hour.utilization);
|
|
86
|
-
lines.push(" Current session");
|
|
87
|
-
lines.push(` ${progressBar(pct)} ${pct}% used`);
|
|
88
|
-
lines.push(` ${formatReset(data.five_hour.resets_at)}`);
|
|
89
|
-
lines.push("");
|
|
90
|
-
}
|
|
91
|
-
if (data.seven_day) {
|
|
92
|
-
const pct = Math.round(data.seven_day.utilization);
|
|
93
|
-
lines.push(" Current week (all models)");
|
|
94
|
-
lines.push(` ${progressBar(pct)} ${pct}% used`);
|
|
95
|
-
lines.push(` ${formatReset(data.seven_day.resets_at)}`);
|
|
96
|
-
lines.push("");
|
|
97
|
-
}
|
|
98
|
-
if (data.seven_day_sonnet) {
|
|
99
|
-
const pct = Math.round(data.seven_day_sonnet.utilization);
|
|
100
|
-
lines.push(" Current week (Sonnet only)");
|
|
101
|
-
lines.push(` ${progressBar(pct)} ${pct}% used`);
|
|
102
|
-
if (data.seven_day_sonnet.resets_at) {
|
|
103
|
-
lines.push(` ${formatReset(data.seven_day_sonnet.resets_at)}`);
|
|
104
|
-
}
|
|
105
|
-
lines.push("");
|
|
106
|
-
}
|
|
107
|
-
if (data.seven_day_opus) {
|
|
108
|
-
const pct = Math.round(data.seven_day_opus.utilization);
|
|
109
|
-
lines.push(" Current week (Opus only)");
|
|
110
|
-
lines.push(` ${progressBar(pct)} ${pct}% used`);
|
|
111
|
-
if (data.seven_day_opus.resets_at) {
|
|
112
|
-
lines.push(` ${formatReset(data.seven_day_opus.resets_at)}`);
|
|
113
|
-
}
|
|
114
|
-
lines.push("");
|
|
115
|
-
}
|
|
116
|
-
if (data.extra_usage) {
|
|
117
|
-
if (data.extra_usage.is_enabled) {
|
|
118
|
-
const pct = Math.round(data.extra_usage.utilization ?? 0);
|
|
119
|
-
lines.push(" Extra usage");
|
|
120
|
-
lines.push(` ${progressBar(pct)} ${pct}% used`);
|
|
121
|
-
if (data.extra_usage.monthly_limit != null) {
|
|
122
|
-
lines.push(` $${data.extra_usage.used_credits?.toFixed(2) ?? "0.00"} / $${data.extra_usage.monthly_limit.toFixed(2)}`);
|
|
123
|
-
}
|
|
124
|
-
} else {
|
|
125
|
-
lines.push(" Extra usage: disabled");
|
|
126
|
-
}
|
|
127
|
-
lines.push("");
|
|
128
|
-
}
|
|
129
|
-
return lines.join(`
|
|
130
|
-
`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// src/usage-cli.ts
|
|
134
|
-
var data;
|
|
135
|
-
try {
|
|
136
|
-
data = await fetchUsage();
|
|
137
|
-
} catch (e) {
|
|
138
|
-
console.error(e.message ?? "Could not fetch usage.");
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
if (!data) {
|
|
142
|
-
console.error("Could not fetch usage. Make sure you're logged into Claude Code (~/.claude/.credentials.json).");
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
|
-
console.log("");
|
|
146
|
-
console.log(" Claude Subscription Usage");
|
|
147
|
-
console.log(" " + "─".repeat(52));
|
|
148
|
-
console.log(formatUsage(data));
|