@xtruder/opencode-claude-max-plugin 0.1.8 → 0.1.9

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.
@@ -92,13 +92,21 @@ function convertAgentStream(stream, modelId) {
92
92
  }
93
93
  if (msg.type === "result") {
94
94
  const result = msg;
95
+ const cacheRead = result.usage?.cache_read_input_tokens ?? 0;
96
+ const cacheCreate = result.usage?.cache_creation_input_tokens ?? 0;
95
97
  controller.enqueue({
96
98
  type: "finish",
97
99
  finishReason: mapFinishReason(result.stop_reason),
98
100
  usage: {
99
101
  inputTokens: result.usage?.input_tokens ?? 0,
100
102
  outputTokens: result.usage?.output_tokens ?? 0,
101
- totalTokens: (result.usage?.input_tokens ?? 0) + (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
+ }
102
110
  }
103
111
  });
104
112
  firstAssistantTurnDone = true;
@@ -1 +1 @@
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;AAqJ1E,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"}
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/index.d.ts CHANGED
@@ -51,6 +51,20 @@ export interface AnthropicSDKProvider {
51
51
  */
52
52
  languageModel(modelId: string): LanguageModelV2;
53
53
  }
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;
54
68
  /**
55
69
  * Create an Anthropic SDK provider for use with OpenCode / Vercel AI SDK.
56
70
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAQvD;;;GAGG;AACH,eAAO,MAAM,WAAW,UAOvB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,0BAA0B,CAAA;AAEtD;;GAEG;AACH,eAAO,MAAM,aAAa,UAGzB,CAAA;AAED,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;AAgCD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CA8FtB;AAGD,eAAe,kBAAkB,CAAA;AAGjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EACL,qBAAqB,EACrB,SAAS,EACT,kBAAkB,EAClB,KAAK,sBAAsB,GAC5B,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAQvD;;;GAGG;AACH,eAAO,MAAM,WAAW,UAOvB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,0BAA0B,CAAA;AAEtD;;GAEG;AACH,eAAO,MAAM,aAAa,UAGzB,CAAA;AAED,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;AAED;;;;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;AAgC1C;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CA0GtB;AAGD,eAAe,kBAAkB,CAAA;AAGjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EACL,qBAAqB,EACrB,SAAS,EACT,kBAAkB,EAClB,KAAK,sBAAsB,GAC5B,MAAM,kBAAkB,CAAA"}
package/build/index.js CHANGED
@@ -265,6 +265,136 @@ function formatToolResultContent(output) {
265
265
  }
266
266
  }
267
267
 
268
+ // src/credentials.ts
269
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
270
+ import { join as join2 } from "node:path";
271
+ import { homedir as homedir2 } from "node:os";
272
+ function getCredentialsPath() {
273
+ return join2(homedir2(), ".claude", ".credentials.json");
274
+ }
275
+ function readClaudeCredentials(path) {
276
+ const credPath = path ?? getCredentialsPath();
277
+ if (!existsSync2(credPath))
278
+ return null;
279
+ try {
280
+ const raw = readFileSync2(credPath, "utf-8");
281
+ const parsed = JSON.parse(raw);
282
+ if (!parsed.claudeAiOauth?.accessToken)
283
+ return null;
284
+ return parsed.claudeAiOauth;
285
+ } catch {
286
+ return null;
287
+ }
288
+ }
289
+ function isExpired(creds) {
290
+ const buffer = 5 * 60 * 1000;
291
+ return Date.now() > creds.expiresAt - buffer;
292
+ }
293
+
294
+ // src/usage.ts
295
+ var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
296
+ async function fetchUsage(credentialsPath) {
297
+ const creds = readClaudeCredentials(credentialsPath);
298
+ if (!creds)
299
+ return null;
300
+ if (isExpired(creds))
301
+ return null;
302
+ const resp = await fetch(USAGE_URL, {
303
+ headers: {
304
+ authorization: `Bearer ${creds.accessToken}`,
305
+ "anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
306
+ "content-type": "application/json",
307
+ "user-agent": "claude-cli/2.1.81 (external, sdk-cli)",
308
+ "x-app": "cli"
309
+ }
310
+ });
311
+ if (!resp.ok) {
312
+ const body = await resp.text().catch(() => "");
313
+ const msg = body.includes("message") ? JSON.parse(body)?.error?.message ?? body : `HTTP ${resp.status}`;
314
+ throw new Error(`Usage API error: ${msg}`);
315
+ }
316
+ return resp.json();
317
+ }
318
+ function progressBar(utilization, width = 50) {
319
+ const filled = Math.round(utilization / 100 * width);
320
+ const bar = "█".repeat(filled) + " ".repeat(width - filled);
321
+ return bar;
322
+ }
323
+ function formatReset(resetsAt) {
324
+ if (!resetsAt)
325
+ return "";
326
+ const reset = new Date(resetsAt);
327
+ const now = new Date;
328
+ const diffMs = reset.getTime() - now.getTime();
329
+ if (diffMs <= 0)
330
+ return "Reset now";
331
+ const diffMin = Math.floor(diffMs / 60000);
332
+ const diffHour = Math.floor(diffMin / 60);
333
+ const timeStr = reset.toLocaleTimeString("en-US", {
334
+ hour: "numeric",
335
+ minute: "2-digit",
336
+ timeZoneName: "short"
337
+ });
338
+ const dateStr = reset.toLocaleDateString("en-US", {
339
+ month: "short",
340
+ day: "numeric"
341
+ });
342
+ if (diffHour < 24) {
343
+ return `Resets ${timeStr}`;
344
+ }
345
+ return `Resets ${dateStr}, ${timeStr}`;
346
+ }
347
+ function formatUsage(data) {
348
+ const lines = [];
349
+ if (data.five_hour) {
350
+ const pct = Math.round(data.five_hour.utilization);
351
+ lines.push(" Current session");
352
+ lines.push(` ${progressBar(pct)} ${pct}% used`);
353
+ lines.push(` ${formatReset(data.five_hour.resets_at)}`);
354
+ lines.push("");
355
+ }
356
+ if (data.seven_day) {
357
+ const pct = Math.round(data.seven_day.utilization);
358
+ lines.push(" Current week (all models)");
359
+ lines.push(` ${progressBar(pct)} ${pct}% used`);
360
+ lines.push(` ${formatReset(data.seven_day.resets_at)}`);
361
+ lines.push("");
362
+ }
363
+ if (data.seven_day_sonnet) {
364
+ const pct = Math.round(data.seven_day_sonnet.utilization);
365
+ lines.push(" Current week (Sonnet only)");
366
+ lines.push(` ${progressBar(pct)} ${pct}% used`);
367
+ if (data.seven_day_sonnet.resets_at) {
368
+ lines.push(` ${formatReset(data.seven_day_sonnet.resets_at)}`);
369
+ }
370
+ lines.push("");
371
+ }
372
+ if (data.seven_day_opus) {
373
+ const pct = Math.round(data.seven_day_opus.utilization);
374
+ lines.push(" Current week (Opus only)");
375
+ lines.push(` ${progressBar(pct)} ${pct}% used`);
376
+ if (data.seven_day_opus.resets_at) {
377
+ lines.push(` ${formatReset(data.seven_day_opus.resets_at)}`);
378
+ }
379
+ lines.push("");
380
+ }
381
+ if (data.extra_usage) {
382
+ if (data.extra_usage.is_enabled) {
383
+ const pct = Math.round(data.extra_usage.utilization ?? 0);
384
+ lines.push(" Extra usage");
385
+ lines.push(` ${progressBar(pct)} ${pct}% used`);
386
+ if (data.extra_usage.monthly_limit != null) {
387
+ lines.push(` $${data.extra_usage.used_credits?.toFixed(2) ?? "0.00"} / $${data.extra_usage.monthly_limit.toFixed(2)}`);
388
+ }
389
+ } else {
390
+ lines.push(" Extra usage: disabled");
391
+ }
392
+ lines.push("");
393
+ }
394
+ return lines.join(`
395
+ `);
396
+ }
397
+
268
398
  // src/tools.ts
269
399
  function cleanSchema(schema) {
270
400
  const cleaned = {};
@@ -352,8 +482,13 @@ function convertStream(anthropicStream, modelId) {
352
482
  usage: {
353
483
  inputTokens: inputTokens ?? 0,
354
484
  outputTokens: outputTokens ?? 0,
355
- totalTokens: (inputTokens ?? 0) + (outputTokens ?? 0),
485
+ totalTokens: (inputTokens ?? 0) + (outputTokens ?? 0) + (cachedInputTokens ?? 0) + (cacheCreationTokens ?? 0),
356
486
  cachedInputTokens
487
+ },
488
+ providerMetadata: {
489
+ anthropic: {
490
+ cacheCreationInputTokens: cacheCreationTokens ?? 0
491
+ }
357
492
  }
358
493
  });
359
494
  }
@@ -521,6 +656,74 @@ function buildMetadata() {
521
656
  user_id: JSON.stringify({ device_id: DEVICE_ID })
522
657
  };
523
658
  }
659
+ function isUsageCommand(prompt) {
660
+ for (let i = prompt.length - 1;i >= 0; i--) {
661
+ const msg = prompt[i];
662
+ if (msg.role !== "user")
663
+ continue;
664
+ const text = msg.content.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
665
+ return /^\/usage(\s|$)/i.test(text);
666
+ }
667
+ return false;
668
+ }
669
+ function formatReset2(epoch) {
670
+ if (!epoch)
671
+ return "";
672
+ const reset = new Date(epoch * 1000);
673
+ const now = new Date;
674
+ const diffMs = reset.getTime() - now.getTime();
675
+ if (diffMs <= 0)
676
+ return "Reset now";
677
+ const timeStr = reset.toLocaleTimeString("en-US", {
678
+ hour: "numeric",
679
+ minute: "2-digit",
680
+ timeZoneName: "short"
681
+ });
682
+ const dateStr = reset.toLocaleDateString("en-US", { month: "short", day: "numeric" });
683
+ return diffMs < 86400000 ? `Resets ${timeStr}` : `Resets ${dateStr}, ${timeStr}`;
684
+ }
685
+ function formatCachedUsage(usage) {
686
+ const bar = (pct, width = 50) => "█".repeat(Math.round(pct / 100 * width)) + " ".repeat(width - Math.round(pct / 100 * width));
687
+ const lines = [];
688
+ const h5 = Math.round((usage.fiveHourUtil ?? 0) * 100);
689
+ lines.push(" Current session");
690
+ lines.push(` ${bar(h5)} ${h5}% used`);
691
+ if (usage.fiveHourReset)
692
+ lines.push(` ${formatReset2(usage.fiveHourReset)}`);
693
+ lines.push("");
694
+ const d7 = Math.round((usage.sevenDayUtil ?? 0) * 100);
695
+ lines.push(" Current week");
696
+ lines.push(` ${bar(d7)} ${d7}% used`);
697
+ if (usage.sevenDayReset)
698
+ lines.push(` ${formatReset2(usage.sevenDayReset)}`);
699
+ lines.push("");
700
+ if (usage.overageStatus === "rejected") {
701
+ lines.push(" Extra usage: disabled");
702
+ } else if (usage.overageStatus) {
703
+ lines.push(` Extra usage: ${usage.overageStatus}`);
704
+ }
705
+ return lines.join(`
706
+ `);
707
+ }
708
+ async function handleUsageCommand() {
709
+ if (cachedUsage.timestamp) {
710
+ throw new Error(`Claude Subscription Usage
711
+ ` + "─".repeat(52) + `
712
+ ` + formatCachedUsage(cachedUsage));
713
+ }
714
+ try {
715
+ const data = await fetchUsage();
716
+ if (data) {
717
+ throw new Error(`Claude Subscription Usage
718
+ ` + "─".repeat(52) + `
719
+ ` + formatUsage(data));
720
+ }
721
+ } catch (e) {
722
+ if (e.message?.startsWith("Claude Subscription Usage"))
723
+ throw e;
724
+ }
725
+ throw new Error("No usage data available. Send a message first, then try /usage again.");
726
+ }
524
727
 
525
728
  class AnthropicSDKModel {
526
729
  client;
@@ -652,6 +855,8 @@ class AnthropicSDKModel {
652
855
  return { params, warnings };
653
856
  }
654
857
  async doGenerate(options) {
858
+ if (isUsageCommand(options.prompt))
859
+ await handleUsageCommand();
655
860
  const { params, warnings } = this.buildParams(options);
656
861
  if (!options.maxOutputTokens && params.max_tokens > 4096) {
657
862
  params.max_tokens = 4096;
@@ -692,14 +897,21 @@ class AnthropicSDKModel {
692
897
  }
693
898
  }
694
899
  }
900
+ const cacheReadTokens = response.usage.cache_read_input_tokens ?? 0;
901
+ const cacheCreateTokens = response.usage.cache_creation_input_tokens ?? 0;
695
902
  return {
696
903
  content,
697
904
  finishReason: mapFinishReason2(response.stop_reason),
698
905
  usage: {
699
906
  inputTokens: response.usage.input_tokens,
700
907
  outputTokens: response.usage.output_tokens,
701
- totalTokens: response.usage.input_tokens + response.usage.output_tokens,
702
- cachedInputTokens: response.usage.cache_read_input_tokens
908
+ totalTokens: response.usage.input_tokens + response.usage.output_tokens + cacheReadTokens + cacheCreateTokens,
909
+ cachedInputTokens: cacheReadTokens
910
+ },
911
+ providerMetadata: {
912
+ anthropic: {
913
+ cacheCreationInputTokens: cacheCreateTokens
914
+ }
703
915
  },
704
916
  response: {
705
917
  id: response.id,
@@ -710,6 +922,8 @@ class AnthropicSDKModel {
710
922
  };
711
923
  }
712
924
  async doStream(options) {
925
+ if (isUsageCommand(options.prompt))
926
+ await handleUsageCommand();
713
927
  const { params, warnings } = this.buildParams(options);
714
928
  let anthropicStream;
715
929
  try {
@@ -748,32 +962,6 @@ class AnthropicSDKModel {
748
962
  }
749
963
  }
750
964
 
751
- // src/credentials.ts
752
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
753
- import { join as join2 } from "node:path";
754
- import { homedir as homedir2 } from "node:os";
755
- function getCredentialsPath() {
756
- return join2(homedir2(), ".claude", ".credentials.json");
757
- }
758
- function readClaudeCredentials(path) {
759
- const credPath = path ?? getCredentialsPath();
760
- if (!existsSync2(credPath))
761
- return null;
762
- try {
763
- const raw = readFileSync2(credPath, "utf-8");
764
- const parsed = JSON.parse(raw);
765
- if (!parsed.claudeAiOauth?.accessToken)
766
- return null;
767
- return parsed.claudeAiOauth;
768
- } catch {
769
- return null;
770
- }
771
- }
772
- function isExpired(creds) {
773
- const buffer = 5 * 60 * 1000;
774
- return Date.now() > creds.expiresAt - buffer;
775
- }
776
-
777
965
  // src/index.ts
778
966
  var CLAUDE_CODE_VERSION = "2.1.81";
779
967
  var OAUTH_BETAS = [
@@ -789,6 +977,7 @@ var API_KEY_BETAS = [
789
977
  "interleaved-thinking-2025-05-14",
790
978
  "fine-grained-tool-streaming-2025-05-14"
791
979
  ];
980
+ var cachedUsage = {};
792
981
  function resolveAuth(options) {
793
982
  if (options.apiKey) {
794
983
  return { apiKey: options.apiKey, isOAuth: false };
@@ -838,6 +1027,15 @@ function createAnthropicSDK(options = {}) {
838
1027
  }
839
1028
  }
840
1029
  const resp = await baseFetch(url, init);
1030
+ const h5 = resp.headers.get("anthropic-ratelimit-unified-5h-utilization");
1031
+ if (h5 != null) {
1032
+ cachedUsage.fiveHourUtil = parseFloat(h5);
1033
+ cachedUsage.sevenDayUtil = parseFloat(resp.headers.get("anthropic-ratelimit-unified-7d-utilization") ?? "0");
1034
+ cachedUsage.fiveHourReset = parseInt(resp.headers.get("anthropic-ratelimit-unified-5h-reset") ?? "0");
1035
+ cachedUsage.sevenDayReset = parseInt(resp.headers.get("anthropic-ratelimit-unified-7d-reset") ?? "0");
1036
+ cachedUsage.overageStatus = resp.headers.get("anthropic-ratelimit-unified-overage-status") ?? undefined;
1037
+ cachedUsage.timestamp = Date.now();
1038
+ }
841
1039
  if (resp.status === 429) {
842
1040
  const unifiedStatus = resp.headers.get("anthropic-ratelimit-unified-status") ?? "";
843
1041
  const retryAfter = parseInt(resp.headers.get("retry-after") ?? "0");
@@ -877,6 +1075,7 @@ export {
877
1075
  getCredentialsPath,
878
1076
  src_default as default,
879
1077
  createAnthropicSDK,
1078
+ cachedUsage,
880
1079
  OAUTH_BETAS,
881
1080
  CONTEXT_1M_BETA,
882
1081
  AnthropicSDKModel,
@@ -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;AAQ9C,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;AAqGtE,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;IAsLb,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsE1E,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;CA2C7E"}
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;AAmMtE,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;IAsLb,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+E1E,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;CA4C7E"}
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,kBAAkB,CAAA;AACzB,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,CA4C3C"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,kBAAkB,CAAA;AACzB,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,CAiD3C"}
@@ -43,8 +43,11 @@ async function fetchUsage(credentialsPath) {
43
43
  "x-app": "cli"
44
44
  }
45
45
  });
46
- if (!resp.ok)
47
- return null;
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
+ }
48
51
  return resp.json();
49
52
  }
50
53
  function progressBar(utilization, width = 50) {
@@ -128,7 +131,13 @@ function formatUsage(data) {
128
131
  }
129
132
 
130
133
  // src/usage-cli.ts
131
- var data = await fetchUsage();
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
+ }
132
141
  if (!data) {
133
142
  console.error("Could not fetch usage. Make sure you're logged into Claude Code (~/.claude/.credentials.json).");
134
143
  process.exit(1);
@@ -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,CAiBpF;AA0CD;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAsDnD"}
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,CAsDnD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtruder/opencode-claude-max-plugin",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "OpenCode plugin for Claude Max/Pro subscription via @anthropic-ai/sdk - uses Claude Code OAuth credentials",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -13,10 +13,6 @@
13
13
  "./agent": {
14
14
  "import": "./build/agent-index.js",
15
15
  "types": "./build/agent-index.d.ts"
16
- },
17
- "./plugin": {
18
- "import": "./build/plugin.js",
19
- "types": "./build/plugin.d.ts"
20
16
  }
21
17
  },
22
18
  "bin": {
@@ -26,7 +22,7 @@
26
22
  "build"
27
23
  ],
28
24
  "scripts": {
29
- "build": "bun build ./src/index.ts ./src/agent-index.ts ./src/usage-cli.ts ./src/plugin.ts --outdir ./build --target node --format esm --packages external && bun x tsc --emitDeclarationOnly --declaration --outDir build",
25
+ "build": "bun build ./src/index.ts ./src/agent-index.ts ./src/usage-cli.ts --outdir ./build --target node --format esm --packages external && bun x tsc --emitDeclarationOnly --declaration --outDir build",
30
26
  "test": "bun run src/test.ts"
31
27
  },
32
28
  "dependencies": {
@@ -39,7 +35,6 @@
39
35
  "devDependencies": {
40
36
  "@ai-sdk/provider": "2.0.1",
41
37
  "@ai-sdk/provider-utils": "3.0.2",
42
- "@opencode-ai/plugin": "^1.3.0",
43
38
  "ai": "5.0.124",
44
39
  "bun-types": "^1.3.11",
45
40
  "typescript": "^5.7.0"
package/build/plugin.d.ts DELETED
@@ -1,14 +0,0 @@
1
- /**
2
- * OpenCode Plugin entry point for @xtruder/opencode-claude-max-plugin.
3
- *
4
- * Registers a /usage command that fetches Claude subscription usage directly
5
- * from the Anthropic OAuth API — no bash, no markdown file required.
6
- *
7
- * Register in opencode.json:
8
- * "plugin": ["@xtruder/opencode-claude-max-plugin/plugin"]
9
- */
10
- import type { Plugin } from "@opencode-ai/plugin";
11
- declare const UsagePlugin: Plugin;
12
- export default UsagePlugin;
13
- export { UsagePlugin };
14
- //# sourceMappingURL=plugin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA+CjD,QAAA,MAAM,WAAW,EAAE,MAgClB,CAAA;AAED,eAAe,WAAW,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,CAAA"}
package/build/plugin.js DELETED
@@ -1,175 +0,0 @@
1
- // src/credentials.ts
2
- import { readFileSync, existsSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { homedir } from "node:os";
5
- function getCredentialsPath() {
6
- return join(homedir(), ".claude", ".credentials.json");
7
- }
8
- function readClaudeCredentials(path) {
9
- const credPath = path ?? getCredentialsPath();
10
- if (!existsSync(credPath))
11
- return null;
12
- try {
13
- const raw = readFileSync(credPath, "utf-8");
14
- const parsed = JSON.parse(raw);
15
- if (!parsed.claudeAiOauth?.accessToken)
16
- return null;
17
- return parsed.claudeAiOauth;
18
- } catch {
19
- return null;
20
- }
21
- }
22
- function isExpired(creds) {
23
- const buffer = 5 * 60 * 1000;
24
- return Date.now() > creds.expiresAt - buffer;
25
- }
26
-
27
- // src/usage.ts
28
- var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
29
- async function fetchUsage(credentialsPath) {
30
- const creds = readClaudeCredentials(credentialsPath);
31
- if (!creds)
32
- return null;
33
- if (isExpired(creds))
34
- return null;
35
- const resp = await fetch(USAGE_URL, {
36
- headers: {
37
- authorization: `Bearer ${creds.accessToken}`,
38
- "anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
39
- "content-type": "application/json",
40
- "user-agent": "claude-cli/2.1.81 (external, sdk-cli)",
41
- "x-app": "cli"
42
- }
43
- });
44
- if (!resp.ok)
45
- return null;
46
- return resp.json();
47
- }
48
- function progressBar(utilization, width = 50) {
49
- const filled = Math.round(utilization / 100 * width);
50
- const bar = "█".repeat(filled) + " ".repeat(width - filled);
51
- return bar;
52
- }
53
- function formatReset(resetsAt) {
54
- if (!resetsAt)
55
- return "";
56
- const reset = new Date(resetsAt);
57
- const now = new Date;
58
- const diffMs = reset.getTime() - now.getTime();
59
- if (diffMs <= 0)
60
- return "Reset now";
61
- const diffMin = Math.floor(diffMs / 60000);
62
- const diffHour = Math.floor(diffMin / 60);
63
- const timeStr = reset.toLocaleTimeString("en-US", {
64
- hour: "numeric",
65
- minute: "2-digit",
66
- timeZoneName: "short"
67
- });
68
- const dateStr = reset.toLocaleDateString("en-US", {
69
- month: "short",
70
- day: "numeric"
71
- });
72
- if (diffHour < 24) {
73
- return `Resets ${timeStr}`;
74
- }
75
- return `Resets ${dateStr}, ${timeStr}`;
76
- }
77
- function formatUsage(data) {
78
- const lines = [];
79
- if (data.five_hour) {
80
- const pct = Math.round(data.five_hour.utilization);
81
- lines.push(" Current session");
82
- lines.push(` ${progressBar(pct)} ${pct}% used`);
83
- lines.push(` ${formatReset(data.five_hour.resets_at)}`);
84
- lines.push("");
85
- }
86
- if (data.seven_day) {
87
- const pct = Math.round(data.seven_day.utilization);
88
- lines.push(" Current week (all models)");
89
- lines.push(` ${progressBar(pct)} ${pct}% used`);
90
- lines.push(` ${formatReset(data.seven_day.resets_at)}`);
91
- lines.push("");
92
- }
93
- if (data.seven_day_sonnet) {
94
- const pct = Math.round(data.seven_day_sonnet.utilization);
95
- lines.push(" Current week (Sonnet only)");
96
- lines.push(` ${progressBar(pct)} ${pct}% used`);
97
- if (data.seven_day_sonnet.resets_at) {
98
- lines.push(` ${formatReset(data.seven_day_sonnet.resets_at)}`);
99
- }
100
- lines.push("");
101
- }
102
- if (data.seven_day_opus) {
103
- const pct = Math.round(data.seven_day_opus.utilization);
104
- lines.push(" Current week (Opus only)");
105
- lines.push(` ${progressBar(pct)} ${pct}% used`);
106
- if (data.seven_day_opus.resets_at) {
107
- lines.push(` ${formatReset(data.seven_day_opus.resets_at)}`);
108
- }
109
- lines.push("");
110
- }
111
- if (data.extra_usage) {
112
- if (data.extra_usage.is_enabled) {
113
- const pct = Math.round(data.extra_usage.utilization ?? 0);
114
- lines.push(" Extra usage");
115
- lines.push(` ${progressBar(pct)} ${pct}% used`);
116
- if (data.extra_usage.monthly_limit != null) {
117
- lines.push(` $${data.extra_usage.used_credits?.toFixed(2) ?? "0.00"} / $${data.extra_usage.monthly_limit.toFixed(2)}`);
118
- }
119
- } else {
120
- lines.push(" Extra usage: disabled");
121
- }
122
- lines.push("");
123
- }
124
- return lines.join(`
125
- `);
126
- }
127
-
128
- // src/plugin.ts
129
- var COMMAND_NAME = "usage";
130
- async function buildUsageText(credentialsPath) {
131
- const data = await fetchUsage(credentialsPath);
132
- if (!data) {
133
- return `Display this message verbatim to the user:
134
-
135
- ` + " Could not fetch Claude usage. " + "Make sure you are logged into Claude Code (~/.claude/.credentials.json).";
136
- }
137
- return "Display the following Claude subscription usage verbatim to the user. " + `Do not add any commentary, formatting changes, or explanation:
138
-
139
- ` + ` Claude Subscription Usage
140
- ` + " " + "─".repeat(52) + `
141
- ` + formatUsage(data);
142
- }
143
- function replaceTextInParts(parts, text) {
144
- const idx = parts.findIndex((p) => p.type === "text");
145
- if (idx >= 0) {
146
- parts[idx] = { ...parts[idx], text };
147
- } else if (parts.length > 0) {
148
- parts[0] = { ...parts[0], text };
149
- } else {
150
- parts.push({ type: "text", text });
151
- }
152
- }
153
- var UsagePlugin = async (_ctx) => {
154
- return {
155
- "command.execute.before": async (input, output) => {
156
- if (input.command.toLowerCase() !== COMMAND_NAME)
157
- return;
158
- const text = await buildUsageText();
159
- replaceTextInParts(output.parts, text);
160
- },
161
- "chat.message": async (_input, output) => {
162
- const parts = output.parts;
163
- const idx = parts.findIndex((p) => p.type === "text" && typeof p.text === "string" && p.text.trim().toLowerCase().startsWith(`/${COMMAND_NAME}`));
164
- if (idx < 0)
165
- return;
166
- const text = await buildUsageText();
167
- parts[idx] = { ...parts[idx], text };
168
- }
169
- };
170
- };
171
- var plugin_default = UsagePlugin;
172
- export {
173
- plugin_default as default,
174
- UsagePlugin
175
- };