iobroker.ai-usage 0.1.0
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/LICENSE +21 -0
- package/README.md +132 -0
- package/admin/ai-usage.svg +19 -0
- package/admin/i18n/de.json +29 -0
- package/admin/i18n/en.json +29 -0
- package/admin/i18n/es.json +29 -0
- package/admin/i18n/fr.json +29 -0
- package/admin/i18n/it.json +29 -0
- package/admin/i18n/nl.json +29 -0
- package/admin/i18n/pl.json +29 -0
- package/admin/i18n/pt.json +29 -0
- package/admin/i18n/ru.json +29 -0
- package/admin/i18n/uk.json +29 -0
- package/admin/i18n/zh-cn.json +29 -0
- package/admin/jsonConfig.json +252 -0
- package/build/lib/http.js +81 -0
- package/build/lib/http.js.map +7 -0
- package/build/lib/poll-engine.js +341 -0
- package/build/lib/poll-engine.js.map +7 -0
- package/build/lib/provider.js +40 -0
- package/build/lib/provider.js.map +7 -0
- package/build/lib/providers/anthropic-api.js +120 -0
- package/build/lib/providers/anthropic-api.js.map +7 -0
- package/build/lib/providers/claude-auth.js +109 -0
- package/build/lib/providers/claude-auth.js.map +7 -0
- package/build/lib/providers/claude-sub.js +165 -0
- package/build/lib/providers/claude-sub.js.map +7 -0
- package/build/lib/providers/deepseek.js +67 -0
- package/build/lib/providers/deepseek.js.map +7 -0
- package/build/lib/providers/openai.js +136 -0
- package/build/lib/providers/openai.js.map +7 -0
- package/build/lib/providers/openrouter.js +71 -0
- package/build/lib/providers/openrouter.js.map +7 -0
- package/build/lib/providers/report-utils.js +59 -0
- package/build/lib/providers/report-utils.js.map +7 -0
- package/build/lib/pure-helpers.js +96 -0
- package/build/lib/pure-helpers.js.map +7 -0
- package/build/lib/snapshot-tree.js +175 -0
- package/build/lib/snapshot-tree.js.map +7 -0
- package/build/lib/totals.js +79 -0
- package/build/lib/totals.js.map +7 -0
- package/build/main.js +336 -0
- package/build/main.js.map +7 -0
- package/io-package.json +222 -0
- package/package.json +91 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/poll-engine.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AccountConfig } from \"./pure-helpers\";\nimport { FetchError, type UsageProvider, type UsageSnapshot } from \"./provider\";\nimport { mapSnapshot, maxLimitPercent, type ObjectDef } from \"./snapshot-tree\";\nimport { computeTotals, type AccountStatus } from \"./totals\";\n\n/** Consecutive network failures after which an account is judged unreachable. */\nconst MAX_NETWORK_FAILURES = 3;\n/** First backoff after a rate-limit answer (ms); doubles per repeat. */\nconst BACKOFF_START_MS = 10 * 60 * 1000;\n/** Backoff ceiling (ms). */\nconst BACKOFF_MAX_MS = 60 * 60 * 1000;\n/** Stagger between the accounts' first polls (ms) so they never fire in one burst. */\nconst STAGGER_MS = 3000;\n\n/** The adapter callbacks the engine drives \u2014 narrow, so tests need no adapter mock. */\nexport interface EngineDeps {\n /** Create or update an object. */\n upsertObject(def: ObjectDef): Promise<void>;\n /** Write a state value with ack. */\n setState(id: string, value: boolean | number | string): void;\n /** Schedule a repeating callback; returns a cancel handle. */\n schedule(cb: () => void, ms: number): unknown;\n /** Schedule a one-shot callback; returns a cancel handle. */\n scheduleOnce(cb: () => void, ms: number): unknown;\n /** Cancel a handle from schedule/scheduleOnce. */\n cancel(handle: unknown): void;\n /** Current time (ms since epoch) \u2014 injected for tests. */\n now(): number;\n /** Adapter log. */\n log: { debug(m: string): void; info(m: string): void; warn(m: string): void; error(m: string): void };\n /** Raise a user-facing notification (threshold crossing, broken credentials). */\n notify?(accountName: string, message: string): void;\n}\n\n/** One account's runtime state inside the engine. */\ninterface AccountRuntime {\n config: AccountConfig;\n provider: UsageProvider;\n status: AccountStatus;\n /** Consecutive network failures. */\n failCount: number;\n /** Skip polls until this time (rate-limit backoff). */\n skipUntil: number;\n /** Current backoff length (ms). */\n backoffMs: number;\n /** Whether the auth-broken notification has been raised (reset on success). */\n authNotified: boolean;\n /** Object ids already created for this account (create-once cache). */\n createdObjects: Set<string>;\n}\n\n/**\n * Drives the polling of all configured accounts: staggered starts, one independent\n * cycle per account, typed failure handling (auth = immediate + one notification,\n * rate-limit = backoff keeping last values, network = tolerant), warn-threshold\n * transitions and the adapter-wide totals. Pure orchestration \u2014 all IO is injected.\n */\nexport class PollEngine {\n private readonly runtimes: AccountRuntime[] = [];\n private readonly handles: unknown[] = [];\n private stopped = false;\n\n /**\n * @param accounts the validated account configs\n * @param providers each account id's provider (accounts without one are skipped)\n * @param intervalSec the poll interval in seconds\n * @param deps the injected adapter callbacks\n */\n public constructor(\n accounts: readonly AccountConfig[],\n providers: ReadonlyMap<string, UsageProvider>,\n private readonly intervalSec: number,\n private readonly deps: EngineDeps,\n ) {\n for (const config of accounts) {\n const provider = providers.get(config.id);\n if (!provider) {\n deps.log.warn(`${config.name}: provider \"${config.provider}\" is not available \u2014 account skipped`);\n continue;\n }\n this.runtimes.push({\n config,\n provider,\n status: { reachable: false, warning: false },\n failCount: 0,\n skipUntil: 0,\n backoffMs: BACKOFF_START_MS,\n authNotified: false,\n createdObjects: new Set(),\n });\n }\n }\n\n /** Create the static per-account and totals objects, then arm the poll cycles. */\n public async start(): Promise<void> {\n for (const runtime of this.runtimes) {\n await this.createAccountSkeleton(runtime);\n }\n await this.createTotalsSkeleton();\n await this.writeTotals();\n this.runtimes.forEach((runtime, index) => {\n this.handles.push(\n this.deps.scheduleOnce(() => void this.pollAccount(runtime), index * STAGGER_MS),\n this.deps.schedule(() => void this.pollAccount(runtime), this.intervalSec * 1000),\n );\n });\n }\n\n /** Cancel every timer. Synchronous \u2014 safe from onUnload. */\n public stop(): void {\n this.stopped = true;\n for (const handle of this.handles) {\n this.deps.cancel(handle);\n }\n this.handles.length = 0;\n }\n\n /** The account ids the engine drives (for the stale-object cleanup). */\n public get accountIds(): string[] {\n return this.runtimes.map(runtime => runtime.config.id);\n }\n\n /**\n * Poll one account now (also used by the staggered first run).\n *\n * @param runtime the account's runtime\n */\n private async pollAccount(runtime: AccountRuntime): Promise<void> {\n if (this.stopped) {\n return;\n }\n const { config } = runtime;\n if (this.deps.now() < runtime.skipUntil) {\n this.deps.log.debug(`${config.name}: in rate-limit backoff \u2014 poll skipped`);\n return;\n }\n try {\n const snapshot = await runtime.provider.fetch();\n runtime.failCount = 0;\n runtime.backoffMs = BACKOFF_START_MS;\n runtime.authNotified = false;\n runtime.status.snapshot = snapshot;\n runtime.status.reachable = true;\n await this.applySnapshot(runtime, snapshot);\n } catch (e) {\n this.handleFailure(runtime, e);\n }\n this.writeAccountInfo(runtime);\n await this.writeTotals();\n }\n\n /**\n * Write a successful snapshot: upsert new objects (create-once cache), write the\n * values, and run the warn-threshold transition.\n *\n * @param runtime the account's runtime\n * @param snapshot the fetched snapshot\n */\n private async applySnapshot(runtime: AccountRuntime, snapshot: UsageSnapshot): Promise<void> {\n const { config } = runtime;\n const { objects, writes } = mapSnapshot(config.id, config.name, config.provider, snapshot);\n for (const object of objects) {\n if (!runtime.createdObjects.has(object.id)) {\n await this.deps.upsertObject(object);\n runtime.createdObjects.add(object.id);\n }\n }\n for (const write of writes) {\n this.deps.setState(write.id, write.value);\n }\n const percent = maxLimitPercent(snapshot) ?? 0;\n const wasWarning = runtime.status.warning;\n runtime.status.warning = percent >= config.warnThreshold;\n this.deps.setState(`${config.id}.warning`, runtime.status.warning);\n this.deps.setState(`${config.id}.limitReached`, percent >= 100);\n if (runtime.status.warning && !wasWarning) {\n const message = `${config.name}: usage at ${Math.round(percent)} % (threshold ${config.warnThreshold} %)`;\n this.deps.log.warn(message);\n this.deps.notify?.(config.name, message);\n }\n }\n\n /**\n * Classify a fetch failure: auth = unreachable + ONE notification until it recovers;\n * rate-limit = backoff, last values stay; network = tolerated MAX_NETWORK_FAILURES times.\n *\n * @param runtime the account's runtime\n * @param error the thrown error\n */\n private handleFailure(runtime: AccountRuntime, error: unknown): void {\n const { config } = runtime;\n const message = error instanceof Error ? error.message : String(error);\n if (error instanceof FetchError && error.kind === \"auth\") {\n runtime.status.reachable = false;\n if (!runtime.authNotified) {\n runtime.authNotified = true;\n const text = `${config.name}: credentials rejected \u2014 ${message}`;\n this.deps.log.warn(text);\n this.deps.notify?.(config.name, text);\n }\n return;\n }\n if (error instanceof FetchError && error.kind === \"rate-limit\") {\n runtime.skipUntil = this.deps.now() + runtime.backoffMs;\n this.deps.log.warn(\n `${config.name}: rate-limited \u2014 backing off for ${Math.round(runtime.backoffMs / 60000)} min, keeping last values`,\n );\n runtime.backoffMs = Math.min(BACKOFF_MAX_MS, runtime.backoffMs * 2);\n return;\n }\n runtime.failCount++;\n this.deps.log.debug(`${config.name}: fetch failed (${message}), attempt ${runtime.failCount}`);\n if (runtime.failCount >= MAX_NETWORK_FAILURES) {\n runtime.status.reachable = false;\n }\n }\n\n /**\n * Write one account's info states (reachable, last update).\n *\n * @param runtime the account's runtime\n */\n private writeAccountInfo(runtime: AccountRuntime): void {\n const { config } = runtime;\n this.deps.setState(`${config.id}.info.reachable`, runtime.status.reachable);\n if (runtime.status.reachable) {\n this.deps.setState(`${config.id}.info.lastUpdate`, new Date(this.deps.now()).toISOString());\n }\n }\n\n /** Recompute and write the totals + info.connection. */\n private async writeTotals(): Promise<void> {\n const totals = computeTotals(this.runtimes.map(runtime => runtime.status));\n this.deps.setState(\"total.costs.today\", totals.costsToday);\n this.deps.setState(\"total.costs.month\", totals.costsMonth);\n this.deps.setState(\"total.costs.projectedMonth\", totals.costsProjectedMonth);\n this.deps.setState(\"total.maxLimitPercent\", totals.maxLimitPercent);\n this.deps.setState(\"total.warningsActive\", totals.warningsActive);\n this.deps.setState(\"total.limitReached\", totals.limitReached);\n this.deps.setState(\"total.accountsReachable\", totals.accountsReachable);\n this.deps.setState(\"total.accounts\", totals.accounts);\n this.deps.setState(\"info.connection\", totals.accountsReachable > 0);\n return Promise.resolve();\n }\n\n /**\n * The static per-account objects that exist regardless of what the source delivers.\n *\n * @param runtime the account's runtime\n */\n private async createAccountSkeleton(runtime: AccountRuntime): Promise<void> {\n const { config } = runtime;\n const defs: ObjectDef[] = [\n { id: config.id, type: \"device\", common: { name: `${config.name} (${config.provider})` } },\n { id: `${config.id}.info`, type: \"channel\", common: { name: \"Info\" } },\n {\n id: `${config.id}.info.provider`,\n type: \"state\",\n common: { name: \"Provider\", type: \"string\", role: \"text\", read: true, write: false },\n },\n {\n id: `${config.id}.info.reachable`,\n type: \"state\",\n common: { name: \"Reachable\", type: \"boolean\", role: \"indicator.reachable\", read: true, write: false },\n },\n {\n id: `${config.id}.info.lastUpdate`,\n type: \"state\",\n common: { name: \"Last successful update\", type: \"string\", role: \"date\", read: true, write: false },\n },\n {\n id: `${config.id}.warning`,\n type: \"state\",\n common: { name: \"Above warn threshold\", type: \"boolean\", role: \"indicator\", read: true, write: false },\n },\n {\n id: `${config.id}.limitReached`,\n type: \"state\",\n common: { name: \"A limit window is full\", type: \"boolean\", role: \"indicator\", read: true, write: false },\n },\n ];\n for (const def of defs) {\n await this.deps.upsertObject(def);\n runtime.createdObjects.add(def.id);\n }\n this.deps.setState(`${config.id}.info.provider`, config.provider);\n this.deps.setState(`${config.id}.info.reachable`, false);\n }\n\n /** The totals skeleton (channel + states). */\n private async createTotalsSkeleton(): Promise<void> {\n const defs: ObjectDef[] = [\n { id: \"total.costs\", type: \"channel\", common: { name: \"Costs (USD accounts)\" } },\n {\n id: \"total.costs.today\",\n type: \"state\",\n common: {\n name: \"Costs today (all accounts)\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"USD\",\n },\n },\n {\n id: \"total.costs.month\",\n type: \"state\",\n common: {\n name: \"Costs this month (all accounts)\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"USD\",\n },\n },\n {\n id: \"total.costs.projectedMonth\",\n type: \"state\",\n common: {\n name: \"Costs projected month-end (computed)\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"USD\",\n },\n },\n {\n id: \"total.maxLimitPercent\",\n type: \"state\",\n common: {\n name: \"Highest limit utilisation of any account\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"%\",\n },\n },\n {\n id: \"total.warningsActive\",\n type: \"state\",\n common: {\n name: \"Accounts above their warn threshold\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n },\n },\n {\n id: \"total.limitReached\",\n type: \"state\",\n common: { name: \"Any limit window full\", type: \"boolean\", role: \"indicator\", read: true, write: false },\n },\n {\n id: \"total.accountsReachable\",\n type: \"state\",\n common: { name: \"Reachable accounts\", type: \"number\", role: \"value\", read: true, write: false },\n },\n {\n id: \"total.accounts\",\n type: \"state\",\n common: { name: \"Configured accounts\", type: \"number\", role: \"value\", read: true, write: false },\n },\n ];\n for (const def of defs) {\n await this.deps.upsertObject(def);\n }\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,sBAAmE;AACnE,2BAA6D;AAC7D,oBAAkD;AAGlD,MAAM,uBAAuB;AAE7B,MAAM,mBAAmB,KAAK,KAAK;AAEnC,MAAM,iBAAiB,KAAK,KAAK;AAEjC,MAAM,aAAa;AA6CZ,MAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWf,YACL,UACA,WACiB,aACA,MACjB;AAFiB;AACA;AAEjB,eAAW,UAAU,UAAU;AAC7B,YAAM,WAAW,UAAU,IAAI,OAAO,EAAE;AACxC,UAAI,CAAC,UAAU;AACb,aAAK,IAAI,KAAK,GAAG,OAAO,IAAI,eAAe,OAAO,QAAQ,2CAAsC;AAChG;AAAA,MACF;AACA,WAAK,SAAS,KAAK;AAAA,QACjB;AAAA,QACA;AAAA,QACA,QAAQ,EAAE,WAAW,OAAO,SAAS,MAAM;AAAA,QAC3C,WAAW;AAAA,QACX,WAAW;AAAA,QACX,WAAW;AAAA,QACX,cAAc;AAAA,QACd,gBAAgB,oBAAI,IAAI;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EApBmB;AAAA,EACA;AAAA,EAdF,WAA6B,CAAC;AAAA,EAC9B,UAAqB,CAAC;AAAA,EAC/B,UAAU;AAAA;AAAA,EAkClB,MAAa,QAAuB;AAClC,eAAW,WAAW,KAAK,UAAU;AACnC,YAAM,KAAK,sBAAsB,OAAO;AAAA,IAC1C;AACA,UAAM,KAAK,qBAAqB;AAChC,UAAM,KAAK,YAAY;AACvB,SAAK,SAAS,QAAQ,CAAC,SAAS,UAAU;AACxC,WAAK,QAAQ;AAAA,QACX,KAAK,KAAK,aAAa,MAAM,KAAK,KAAK,YAAY,OAAO,GAAG,QAAQ,UAAU;AAAA,QAC/E,KAAK,KAAK,SAAS,MAAM,KAAK,KAAK,YAAY,OAAO,GAAG,KAAK,cAAc,GAAI;AAAA,MAClF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGO,OAAa;AAClB,SAAK,UAAU;AACf,eAAW,UAAU,KAAK,SAAS;AACjC,WAAK,KAAK,OAAO,MAAM;AAAA,IACzB;AACA,SAAK,QAAQ,SAAS;AAAA,EACxB;AAAA;AAAA,EAGA,IAAW,aAAuB;AAChC,WAAO,KAAK,SAAS,IAAI,aAAW,QAAQ,OAAO,EAAE;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,YAAY,SAAwC;AAChE,QAAI,KAAK,SAAS;AAChB;AAAA,IACF;AACA,UAAM,EAAE,OAAO,IAAI;AACnB,QAAI,KAAK,KAAK,IAAI,IAAI,QAAQ,WAAW;AACvC,WAAK,KAAK,IAAI,MAAM,GAAG,OAAO,IAAI,6CAAwC;AAC1E;AAAA,IACF;AACA,QAAI;AACF,YAAM,WAAW,MAAM,QAAQ,SAAS,MAAM;AAC9C,cAAQ,YAAY;AACpB,cAAQ,YAAY;AACpB,cAAQ,eAAe;AACvB,cAAQ,OAAO,WAAW;AAC1B,cAAQ,OAAO,YAAY;AAC3B,YAAM,KAAK,cAAc,SAAS,QAAQ;AAAA,IAC5C,SAAS,GAAG;AACV,WAAK,cAAc,SAAS,CAAC;AAAA,IAC/B;AACA,SAAK,iBAAiB,OAAO;AAC7B,UAAM,KAAK,YAAY;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,cAAc,SAAyB,UAAwC;AA9J/F;AA+JI,UAAM,EAAE,OAAO,IAAI;AACnB,UAAM,EAAE,SAAS,OAAO,QAAI,kCAAY,OAAO,IAAI,OAAO,MAAM,OAAO,UAAU,QAAQ;AACzF,eAAW,UAAU,SAAS;AAC5B,UAAI,CAAC,QAAQ,eAAe,IAAI,OAAO,EAAE,GAAG;AAC1C,cAAM,KAAK,KAAK,aAAa,MAAM;AACnC,gBAAQ,eAAe,IAAI,OAAO,EAAE;AAAA,MACtC;AAAA,IACF;AACA,eAAW,SAAS,QAAQ;AAC1B,WAAK,KAAK,SAAS,MAAM,IAAI,MAAM,KAAK;AAAA,IAC1C;AACA,UAAM,WAAU,+CAAgB,QAAQ,MAAxB,YAA6B;AAC7C,UAAM,aAAa,QAAQ,OAAO;AAClC,YAAQ,OAAO,UAAU,WAAW,OAAO;AAC3C,SAAK,KAAK,SAAS,GAAG,OAAO,EAAE,YAAY,QAAQ,OAAO,OAAO;AACjE,SAAK,KAAK,SAAS,GAAG,OAAO,EAAE,iBAAiB,WAAW,GAAG;AAC9D,QAAI,QAAQ,OAAO,WAAW,CAAC,YAAY;AACzC,YAAM,UAAU,GAAG,OAAO,IAAI,cAAc,KAAK,MAAM,OAAO,CAAC,iBAAiB,OAAO,aAAa;AACpG,WAAK,KAAK,IAAI,KAAK,OAAO;AAC1B,uBAAK,MAAK,WAAV,4BAAmB,OAAO,MAAM;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,cAAc,SAAyB,OAAsB;AA7LvE;AA8LI,UAAM,EAAE,OAAO,IAAI;AACnB,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,QAAI,iBAAiB,8BAAc,MAAM,SAAS,QAAQ;AACxD,cAAQ,OAAO,YAAY;AAC3B,UAAI,CAAC,QAAQ,cAAc;AACzB,gBAAQ,eAAe;AACvB,cAAM,OAAO,GAAG,OAAO,IAAI,iCAA4B,OAAO;AAC9D,aAAK,KAAK,IAAI,KAAK,IAAI;AACvB,yBAAK,MAAK,WAAV,4BAAmB,OAAO,MAAM;AAAA,MAClC;AACA;AAAA,IACF;AACA,QAAI,iBAAiB,8BAAc,MAAM,SAAS,cAAc;AAC9D,cAAQ,YAAY,KAAK,KAAK,IAAI,IAAI,QAAQ;AAC9C,WAAK,KAAK,IAAI;AAAA,QACZ,GAAG,OAAO,IAAI,yCAAoC,KAAK,MAAM,QAAQ,YAAY,GAAK,CAAC;AAAA,MACzF;AACA,cAAQ,YAAY,KAAK,IAAI,gBAAgB,QAAQ,YAAY,CAAC;AAClE;AAAA,IACF;AACA,YAAQ;AACR,SAAK,KAAK,IAAI,MAAM,GAAG,OAAO,IAAI,mBAAmB,OAAO,cAAc,QAAQ,SAAS,EAAE;AAC7F,QAAI,QAAQ,aAAa,sBAAsB;AAC7C,cAAQ,OAAO,YAAY;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,iBAAiB,SAA+B;AACtD,UAAM,EAAE,OAAO,IAAI;AACnB,SAAK,KAAK,SAAS,GAAG,OAAO,EAAE,mBAAmB,QAAQ,OAAO,SAAS;AAC1E,QAAI,QAAQ,OAAO,WAAW;AAC5B,WAAK,KAAK,SAAS,GAAG,OAAO,EAAE,oBAAoB,IAAI,KAAK,KAAK,KAAK,IAAI,CAAC,EAAE,YAAY,CAAC;AAAA,IAC5F;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,cAA6B;AACzC,UAAM,aAAS,6BAAc,KAAK,SAAS,IAAI,aAAW,QAAQ,MAAM,CAAC;AACzE,SAAK,KAAK,SAAS,qBAAqB,OAAO,UAAU;AACzD,SAAK,KAAK,SAAS,qBAAqB,OAAO,UAAU;AACzD,SAAK,KAAK,SAAS,8BAA8B,OAAO,mBAAmB;AAC3E,SAAK,KAAK,SAAS,yBAAyB,OAAO,eAAe;AAClE,SAAK,KAAK,SAAS,wBAAwB,OAAO,cAAc;AAChE,SAAK,KAAK,SAAS,sBAAsB,OAAO,YAAY;AAC5D,SAAK,KAAK,SAAS,2BAA2B,OAAO,iBAAiB;AACtE,SAAK,KAAK,SAAS,kBAAkB,OAAO,QAAQ;AACpD,SAAK,KAAK,SAAS,mBAAmB,OAAO,oBAAoB,CAAC;AAClE,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,sBAAsB,SAAwC;AAC1E,UAAM,EAAE,OAAO,IAAI;AACnB,UAAM,OAAoB;AAAA,MACxB,EAAE,IAAI,OAAO,IAAI,MAAM,UAAU,QAAQ,EAAE,MAAM,GAAG,OAAO,IAAI,KAAK,OAAO,QAAQ,IAAI,EAAE;AAAA,MACzF,EAAE,IAAI,GAAG,OAAO,EAAE,SAAS,MAAM,WAAW,QAAQ,EAAE,MAAM,OAAO,EAAE;AAAA,MACrE;AAAA,QACE,IAAI,GAAG,OAAO,EAAE;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,YAAY,MAAM,UAAU,MAAM,QAAQ,MAAM,MAAM,OAAO,MAAM;AAAA,MACrF;AAAA,MACA;AAAA,QACE,IAAI,GAAG,OAAO,EAAE;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,aAAa,MAAM,WAAW,MAAM,uBAAuB,MAAM,MAAM,OAAO,MAAM;AAAA,MACtG;AAAA,MACA;AAAA,QACE,IAAI,GAAG,OAAO,EAAE;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,0BAA0B,MAAM,UAAU,MAAM,QAAQ,MAAM,MAAM,OAAO,MAAM;AAAA,MACnG;AAAA,MACA;AAAA,QACE,IAAI,GAAG,OAAO,EAAE;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,wBAAwB,MAAM,WAAW,MAAM,aAAa,MAAM,MAAM,OAAO,MAAM;AAAA,MACvG;AAAA,MACA;AAAA,QACE,IAAI,GAAG,OAAO,EAAE;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,0BAA0B,MAAM,WAAW,MAAM,aAAa,MAAM,MAAM,OAAO,MAAM;AAAA,MACzG;AAAA,IACF;AACA,eAAW,OAAO,MAAM;AACtB,YAAM,KAAK,KAAK,aAAa,GAAG;AAChC,cAAQ,eAAe,IAAI,IAAI,EAAE;AAAA,IACnC;AACA,SAAK,KAAK,SAAS,GAAG,OAAO,EAAE,kBAAkB,OAAO,QAAQ;AAChE,SAAK,KAAK,SAAS,GAAG,OAAO,EAAE,mBAAmB,KAAK;AAAA,EACzD;AAAA;AAAA,EAGA,MAAc,uBAAsC;AAClD,UAAM,OAAoB;AAAA,MACxB,EAAE,IAAI,eAAe,MAAM,WAAW,QAAQ,EAAE,MAAM,uBAAuB,EAAE;AAAA,MAC/E;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,UACP,MAAM;AAAA,QACR;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,UACP,MAAM;AAAA,QACR;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,UACP,MAAM;AAAA,QACR;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,UACP,MAAM;AAAA,QACR;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,yBAAyB,MAAM,WAAW,MAAM,aAAa,MAAM,MAAM,OAAO,MAAM;AAAA,MACxG;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,sBAAsB,MAAM,UAAU,MAAM,SAAS,MAAM,MAAM,OAAO,MAAM;AAAA,MAChG;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,uBAAuB,MAAM,UAAU,MAAM,SAAS,MAAM,MAAM,OAAO,MAAM;AAAA,MACjG;AAAA,IACF;AACA,eAAW,OAAO,MAAM;AACtB,YAAM,KAAK,KAAK,aAAa,GAAG;AAAA,IAClC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var provider_exports = {};
|
|
20
|
+
__export(provider_exports, {
|
|
21
|
+
FetchError: () => FetchError
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(provider_exports);
|
|
24
|
+
class FetchError extends Error {
|
|
25
|
+
/**
|
|
26
|
+
* @param kind the failure class
|
|
27
|
+
* @param message the human-readable reason
|
|
28
|
+
*/
|
|
29
|
+
constructor(kind, message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.kind = kind;
|
|
32
|
+
this.name = "FetchError";
|
|
33
|
+
}
|
|
34
|
+
kind;
|
|
35
|
+
}
|
|
36
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
37
|
+
0 && (module.exports = {
|
|
38
|
+
FetchError
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/provider.ts"],
|
|
4
|
+
"sourcesContent": ["/** The provider kinds the adapter speaks. */\nexport type ProviderKind = \"claude-sub\" | \"openrouter\" | \"deepseek\" | \"openai\" | \"anthropic-api\";\n\n/** One limit window (session, week, per-model) \u2014 the same shape for every provider. */\nexport interface LimitWindow {\n /** Window name; becomes the object id segment (session, week, a model name, month). */\n name: string;\n /** Human-readable label for the object name. */\n label: string;\n /** Utilisation in percent (0-100+). */\n percent: number;\n /** When the window resets (ISO timestamp), if the source reports it. */\n resetAt?: string;\n}\n\n/** Granted budget (prepaid money or request credits). */\nexport interface CreditInfo {\n /** Used amount. */\n used?: number;\n /** Granted ceiling; undefined = unlimited/unknown. */\n limit?: number;\n /** Remaining amount. */\n remaining?: number;\n /** Utilisation in percent, when both used and limit are known. */\n percent?: number;\n /** Granted (gifted) part of the balance, where the source distinguishes it (DeepSeek). */\n granted?: number;\n /** Topped-up (paid) part of the balance. */\n toppedUp?: number;\n /** Currency code (\"USD\", \"CNY\") \u2014 or a unit word for piece-counters. */\n currency: string;\n /** True when the credits are pieces (requests), not money \u2014 excluded from cost totals. */\n pieces?: boolean;\n}\n\n/** Real money spent. */\nexport interface CostInfo {\n /** Spent today. */\n today?: number;\n /** Spent this month (or billing period). */\n month?: number;\n /** Lifetime counter, where the source only reports that. */\n total?: number;\n /** Projected month-end spend (computed by the provider module, marked in the object name). */\n projectedMonth?: number;\n /** Currency code. */\n currency: string;\n}\n\n/** Token counters (API accounts). */\nexport interface TokenInfo {\n /** Input tokens today. */\n inputToday?: number;\n /** Output tokens today. */\n outputToday?: number;\n /** Per-model breakdown. */\n perModel?: { model: string; tokens?: number; cost?: number }[];\n}\n\n/**\n * A transport-neutral usage snapshot \u2014 one fetch result. Only what the source\n * actually delivered is present; the tree builder creates nothing for absent parts.\n */\nexport interface UsageSnapshot {\n /** Limit windows (subscription accounts). */\n limits?: LimitWindow[];\n /** Granted budget. */\n credits?: CreditInfo;\n /** Real money spent. */\n costs?: CostInfo;\n /** Token counters. */\n tokens?: TokenInfo;\n /** Provider-specific extra flags (e.g. DeepSeek `available`). */\n available?: boolean;\n}\n\n/** Why a fetch failed \u2014 drives reachability, backoff and notifications. */\nexport type FetchErrorKind = \"auth\" | \"rate-limit\" | \"network\";\n\n/** A typed fetch failure. */\nexport class FetchError extends Error {\n /**\n * @param kind the failure class\n * @param message the human-readable reason\n */\n public constructor(\n public readonly kind: FetchErrorKind,\n message: string,\n ) {\n super(message);\n this.name = \"FetchError\";\n }\n}\n\n/** One account's usage source. Implementations are pure fetch+parse \u2014 no ioBroker inside. */\nexport interface UsageProvider {\n /** Which provider this is. */\n readonly kind: ProviderKind;\n /** Fetch the current snapshot; throws {@link FetchError} on failure. */\n fetch(): Promise<UsageSnapshot>;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgFO,MAAM,mBAAmB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,EAK7B,YACW,MAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAMpB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var anthropic_api_exports = {};
|
|
20
|
+
__export(anthropic_api_exports, {
|
|
21
|
+
anthropicApiProvider: () => anthropicApiProvider,
|
|
22
|
+
parseAnthropicReports: () => parseAnthropicReports
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(anthropic_api_exports);
|
|
25
|
+
var import_http = require("../http");
|
|
26
|
+
var import_report_utils = require("./report-utils");
|
|
27
|
+
const BASE = "https://api.anthropic.com/v1/organizations";
|
|
28
|
+
async function fetchAllPages(url, headers, fetchJson) {
|
|
29
|
+
const buckets = [];
|
|
30
|
+
let page;
|
|
31
|
+
for (let i = 0; i < 12; i++) {
|
|
32
|
+
const body = await fetchJson(page ? `${url}&page=${encodeURIComponent(page)}` : url, headers);
|
|
33
|
+
if (Array.isArray(body == null ? void 0 : body.data)) {
|
|
34
|
+
buckets.push(...body.data);
|
|
35
|
+
}
|
|
36
|
+
if ((body == null ? void 0 : body.has_more) !== true || typeof (body == null ? void 0 : body.next_page) !== "string" || !body.next_page) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
page = body.next_page;
|
|
40
|
+
}
|
|
41
|
+
return buckets;
|
|
42
|
+
}
|
|
43
|
+
function parseAnthropicReports(usageBuckets, costBuckets, nowMs) {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
let costMonth = 0;
|
|
46
|
+
let costToday = 0;
|
|
47
|
+
for (const bucket of costBuckets) {
|
|
48
|
+
const entry = bucket;
|
|
49
|
+
if (!Array.isArray(entry == null ? void 0 : entry.results)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
let sum = 0;
|
|
53
|
+
for (const result of entry.results) {
|
|
54
|
+
const amount = Number(result == null ? void 0 : result.amount);
|
|
55
|
+
if (Number.isFinite(amount)) {
|
|
56
|
+
sum += amount;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
costMonth += sum;
|
|
60
|
+
if ((0, import_report_utils.isToday)((_a = entry.starting_at) != null ? _a : entry.start_time, nowMs)) {
|
|
61
|
+
costToday += sum;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
let inputToday = 0;
|
|
65
|
+
let outputToday = 0;
|
|
66
|
+
let sawUsageToday = false;
|
|
67
|
+
for (const bucket of usageBuckets) {
|
|
68
|
+
const entry = bucket;
|
|
69
|
+
if (!Array.isArray(entry == null ? void 0 : entry.results) || !(0, import_report_utils.isToday)((_b = entry.starting_at) != null ? _b : entry.start_time, nowMs)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
sawUsageToday = true;
|
|
73
|
+
for (const result of entry.results) {
|
|
74
|
+
const data = result;
|
|
75
|
+
const input = Number(data.uncached_input_tokens);
|
|
76
|
+
const output = Number(data.output_tokens);
|
|
77
|
+
if (Number.isFinite(input)) {
|
|
78
|
+
inputToday += input;
|
|
79
|
+
}
|
|
80
|
+
if (Number.isFinite(output)) {
|
|
81
|
+
outputToday += output;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const round = (value) => Math.round(value * 100) / 100;
|
|
86
|
+
const snapshot = {
|
|
87
|
+
costs: {
|
|
88
|
+
today: round(costToday),
|
|
89
|
+
month: round(costMonth),
|
|
90
|
+
projectedMonth: (0, import_report_utils.projectMonth)(costMonth, nowMs),
|
|
91
|
+
currency: "USD"
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
if (sawUsageToday) {
|
|
95
|
+
snapshot.tokens = { inputToday, outputToday };
|
|
96
|
+
}
|
|
97
|
+
return snapshot;
|
|
98
|
+
}
|
|
99
|
+
function anthropicApiProvider(adminKey, fetchJson = import_http.getJson, now = Date.now) {
|
|
100
|
+
return {
|
|
101
|
+
kind: "anthropic-api",
|
|
102
|
+
fetch: async () => {
|
|
103
|
+
const headers = { "x-api-key": adminKey, "anthropic-version": "2023-06-01" };
|
|
104
|
+
const start = encodeURIComponent((0, import_report_utils.monthStartIso)(now()));
|
|
105
|
+
const usage = await fetchAllPages(
|
|
106
|
+
`${BASE}/usage_report/messages?starting_at=${start}&bucket_width=1d`,
|
|
107
|
+
headers,
|
|
108
|
+
fetchJson
|
|
109
|
+
);
|
|
110
|
+
const costs = await fetchAllPages(`${BASE}/cost_report?starting_at=${start}&bucket_width=1d`, headers, fetchJson);
|
|
111
|
+
return parseAnthropicReports(usage, costs, now());
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
116
|
+
0 && (module.exports = {
|
|
117
|
+
anthropicApiProvider,
|
|
118
|
+
parseAnthropicReports
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=anthropic-api.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/providers/anthropic-api.ts"],
|
|
4
|
+
"sourcesContent": ["import { getJson, type JsonFetch } from \"../http\";\nimport type { UsageProvider, UsageSnapshot } from \"../provider\";\nimport { isToday, monthStartIso, projectMonth } from \"./report-utils\";\n\n/**\n * Anthropic organization Usage + Cost reports (official Admin API; needs an\n * organization ADMIN key). Source-verified against dbpecka/hacs-claude-stats:\n * `GET /v1/organizations/usage_report/messages` and `/v1/organizations/cost_report`\n * with `x-api-key` + `anthropic-version: 2023-06-01`, params starting_at/ending_at\n * (ISO) + bucket_width=1d, pagination via has_more/next_page (param `page`).\n * Usage results carry `uncached_input_tokens`/`output_tokens`; cost results carry\n * `amount` as a decimal STRING (USD).\n */\nconst BASE = \"https://api.anthropic.com/v1/organizations\";\n\n/**\n * Fetch all pages of a bucketed report.\n *\n * @param url the report URL without the page parameter\n * @param headers request headers\n * @param fetchJson the JSON-GET seam\n * @returns all bucket entries\n */\nasync function fetchAllPages(url: string, headers: Record<string, string>, fetchJson: JsonFetch): Promise<unknown[]> {\n const buckets: unknown[] = [];\n let page: string | undefined;\n for (let i = 0; i < 12; i++) {\n const body = (await fetchJson(page ? `${url}&page=${encodeURIComponent(page)}` : url, headers)) as {\n data?: unknown;\n has_more?: unknown;\n next_page?: unknown;\n } | null;\n if (Array.isArray(body?.data)) {\n buckets.push(...body.data);\n }\n if (body?.has_more !== true || typeof body?.next_page !== \"string\" || !body.next_page) {\n break;\n }\n page = body.next_page;\n }\n return buckets;\n}\n\n/**\n * Parse the two bucket lists into a snapshot: month/today costs (+ projection) and\n * today's input/output tokens.\n *\n * @param usageBuckets the usage report buckets\n * @param costBuckets the cost report buckets\n * @param nowMs current time (ms)\n * @returns the snapshot\n */\nexport function parseAnthropicReports(usageBuckets: unknown[], costBuckets: unknown[], nowMs: number): UsageSnapshot {\n let costMonth = 0;\n let costToday = 0;\n for (const bucket of costBuckets) {\n const entry = bucket as { starting_at?: unknown; start_time?: unknown; results?: unknown };\n if (!Array.isArray(entry?.results)) {\n continue;\n }\n let sum = 0;\n for (const result of entry.results) {\n const amount = Number((result as { amount?: unknown })?.amount);\n if (Number.isFinite(amount)) {\n sum += amount;\n }\n }\n costMonth += sum;\n if (isToday(entry.starting_at ?? entry.start_time, nowMs)) {\n costToday += sum;\n }\n }\n\n let inputToday = 0;\n let outputToday = 0;\n let sawUsageToday = false;\n for (const bucket of usageBuckets) {\n const entry = bucket as { starting_at?: unknown; start_time?: unknown; results?: unknown };\n if (!Array.isArray(entry?.results) || !isToday(entry.starting_at ?? entry.start_time, nowMs)) {\n continue;\n }\n sawUsageToday = true;\n for (const result of entry.results) {\n const data = result as { uncached_input_tokens?: unknown; output_tokens?: unknown };\n const input = Number(data.uncached_input_tokens);\n const output = Number(data.output_tokens);\n if (Number.isFinite(input)) {\n inputToday += input;\n }\n if (Number.isFinite(output)) {\n outputToday += output;\n }\n }\n }\n\n const round = (value: number): number => Math.round(value * 100) / 100;\n const snapshot: UsageSnapshot = {\n costs: {\n today: round(costToday),\n month: round(costMonth),\n projectedMonth: projectMonth(costMonth, nowMs),\n currency: \"USD\",\n },\n };\n if (sawUsageToday) {\n snapshot.tokens = { inputToday, outputToday };\n }\n return snapshot;\n}\n\n/**\n * The Anthropic API provider (organization accounts).\n *\n * @param adminKey the organization ADMIN key\n * @param fetchJson the JSON-GET seam\n * @param now clock (ms) \u2014 injected for tests\n * @returns the provider\n */\nexport function anthropicApiProvider(\n adminKey: string,\n fetchJson: JsonFetch = getJson,\n now: () => number = Date.now,\n): UsageProvider {\n return {\n kind: \"anthropic-api\",\n fetch: async (): Promise<UsageSnapshot> => {\n const headers = { \"x-api-key\": adminKey, \"anthropic-version\": \"2023-06-01\" };\n const start = encodeURIComponent(monthStartIso(now()));\n const usage = await fetchAllPages(\n `${BASE}/usage_report/messages?starting_at=${start}&bucket_width=1d`,\n headers,\n fetchJson,\n );\n const costs = await fetchAllPages(`${BASE}/cost_report?starting_at=${start}&bucket_width=1d`, headers, fetchJson);\n return parseAnthropicReports(usage, costs, now());\n },\n };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAwC;AAExC,0BAAqD;AAWrD,MAAM,OAAO;AAUb,eAAe,cAAc,KAAa,SAAiC,WAA0C;AACnH,QAAM,UAAqB,CAAC;AAC5B,MAAI;AACJ,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAM,OAAQ,MAAM,UAAU,OAAO,GAAG,GAAG,SAAS,mBAAmB,IAAI,CAAC,KAAK,KAAK,OAAO;AAK7F,QAAI,MAAM,QAAQ,6BAAM,IAAI,GAAG;AAC7B,cAAQ,KAAK,GAAG,KAAK,IAAI;AAAA,IAC3B;AACA,SAAI,6BAAM,cAAa,QAAQ,QAAO,6BAAM,eAAc,YAAY,CAAC,KAAK,WAAW;AACrF;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AACA,SAAO;AACT;AAWO,SAAS,sBAAsB,cAAyB,aAAwB,OAA8B;AApDrH;AAqDE,MAAI,YAAY;AAChB,MAAI,YAAY;AAChB,aAAW,UAAU,aAAa;AAChC,UAAM,QAAQ;AACd,QAAI,CAAC,MAAM,QAAQ,+BAAO,OAAO,GAAG;AAClC;AAAA,IACF;AACA,QAAI,MAAM;AACV,eAAW,UAAU,MAAM,SAAS;AAClC,YAAM,SAAS,OAAQ,iCAAiC,MAAM;AAC9D,UAAI,OAAO,SAAS,MAAM,GAAG;AAC3B,eAAO;AAAA,MACT;AAAA,IACF;AACA,iBAAa;AACb,YAAI,8BAAQ,WAAM,gBAAN,YAAqB,MAAM,YAAY,KAAK,GAAG;AACzD,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,MAAI,aAAa;AACjB,MAAI,cAAc;AAClB,MAAI,gBAAgB;AACpB,aAAW,UAAU,cAAc;AACjC,UAAM,QAAQ;AACd,QAAI,CAAC,MAAM,QAAQ,+BAAO,OAAO,KAAK,KAAC,8BAAQ,WAAM,gBAAN,YAAqB,MAAM,YAAY,KAAK,GAAG;AAC5F;AAAA,IACF;AACA,oBAAgB;AAChB,eAAW,UAAU,MAAM,SAAS;AAClC,YAAM,OAAO;AACb,YAAM,QAAQ,OAAO,KAAK,qBAAqB;AAC/C,YAAM,SAAS,OAAO,KAAK,aAAa;AACxC,UAAI,OAAO,SAAS,KAAK,GAAG;AAC1B,sBAAc;AAAA,MAChB;AACA,UAAI,OAAO,SAAS,MAAM,GAAG;AAC3B,uBAAe;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,CAAC,UAA0B,KAAK,MAAM,QAAQ,GAAG,IAAI;AACnE,QAAM,WAA0B;AAAA,IAC9B,OAAO;AAAA,MACL,OAAO,MAAM,SAAS;AAAA,MACtB,OAAO,MAAM,SAAS;AAAA,MACtB,oBAAgB,kCAAa,WAAW,KAAK;AAAA,MAC7C,UAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,eAAe;AACjB,aAAS,SAAS,EAAE,YAAY,YAAY;AAAA,EAC9C;AACA,SAAO;AACT;AAUO,SAAS,qBACd,UACA,YAAuB,qBACvB,MAAoB,KAAK,KACV;AACf,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,YAAoC;AACzC,YAAM,UAAU,EAAE,aAAa,UAAU,qBAAqB,aAAa;AAC3E,YAAM,QAAQ,uBAAmB,mCAAc,IAAI,CAAC,CAAC;AACrD,YAAM,QAAQ,MAAM;AAAA,QAClB,GAAG,IAAI,sCAAsC,KAAK;AAAA,QAClD;AAAA,QACA;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,cAAc,GAAG,IAAI,4BAA4B,KAAK,oBAAoB,SAAS,SAAS;AAChH,aAAO,sBAAsB,OAAO,OAAO,IAAI,CAAC;AAAA,IAClD;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var claude_auth_exports = {};
|
|
20
|
+
__export(claude_auth_exports, {
|
|
21
|
+
CLAUDE_OAUTH: () => CLAUDE_OAUTH,
|
|
22
|
+
buildAuthorizeUrl: () => buildAuthorizeUrl,
|
|
23
|
+
exchangeCode: () => exchangeCode,
|
|
24
|
+
generatePkce: () => generatePkce,
|
|
25
|
+
refreshTokens: () => refreshTokens
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(claude_auth_exports);
|
|
28
|
+
var import_node_crypto = require("node:crypto");
|
|
29
|
+
var import_provider = require("../provider");
|
|
30
|
+
const CLAUDE_OAUTH = {
|
|
31
|
+
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
|
32
|
+
authorizeUrl: "https://claude.ai/oauth/authorize",
|
|
33
|
+
tokenUrl: "https://console.anthropic.com/v1/oauth/token",
|
|
34
|
+
redirectUri: "https://console.anthropic.com/oauth/code/callback",
|
|
35
|
+
scopes: "org:create_api_key user:profile user:inference",
|
|
36
|
+
usageUrl: "https://api.anthropic.com/api/oauth/usage",
|
|
37
|
+
profileUrl: "https://api.anthropic.com/api/oauth/profile",
|
|
38
|
+
betaHeader: "oauth-2025-04-20"
|
|
39
|
+
};
|
|
40
|
+
function generatePkce() {
|
|
41
|
+
const verifier = (0, import_node_crypto.randomBytes)(32).toString("base64url");
|
|
42
|
+
const challenge = (0, import_node_crypto.createHash)("sha256").update(verifier).digest("base64url");
|
|
43
|
+
return { verifier, challenge, state: (0, import_node_crypto.randomBytes)(24).toString("base64url") };
|
|
44
|
+
}
|
|
45
|
+
function buildAuthorizeUrl(pkce) {
|
|
46
|
+
const params = new URLSearchParams({
|
|
47
|
+
code: "true",
|
|
48
|
+
client_id: CLAUDE_OAUTH.clientId,
|
|
49
|
+
response_type: "code",
|
|
50
|
+
redirect_uri: CLAUDE_OAUTH.redirectUri,
|
|
51
|
+
scope: CLAUDE_OAUTH.scopes,
|
|
52
|
+
code_challenge: pkce.challenge,
|
|
53
|
+
code_challenge_method: "S256",
|
|
54
|
+
state: pkce.state
|
|
55
|
+
});
|
|
56
|
+
return `${CLAUDE_OAUTH.authorizeUrl}?${params.toString()}`;
|
|
57
|
+
}
|
|
58
|
+
function tokenSetFrom(body, now, previousRefresh = "") {
|
|
59
|
+
const data = body;
|
|
60
|
+
const accessToken = typeof (data == null ? void 0 : data.access_token) === "string" ? data.access_token : "";
|
|
61
|
+
if (!accessToken) {
|
|
62
|
+
throw new import_provider.FetchError("auth", "token response carries no access token");
|
|
63
|
+
}
|
|
64
|
+
const refreshToken = typeof (data == null ? void 0 : data.refresh_token) === "string" && data.refresh_token ? data.refresh_token : previousRefresh;
|
|
65
|
+
const expiresIn = Number(data == null ? void 0 : data.expires_in);
|
|
66
|
+
return {
|
|
67
|
+
accessToken,
|
|
68
|
+
refreshToken,
|
|
69
|
+
expiresAt: now + (Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : 3600) * 1e3
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function exchangeCode(pastedCode, pkce, postJson, now) {
|
|
73
|
+
const [code, state = ""] = pastedCode.trim().split("#");
|
|
74
|
+
if (!code) {
|
|
75
|
+
throw new import_provider.FetchError("auth", "empty authorization code");
|
|
76
|
+
}
|
|
77
|
+
if (state && state !== pkce.state) {
|
|
78
|
+
throw new import_provider.FetchError("auth", "state mismatch \u2014 start the sign-in again");
|
|
79
|
+
}
|
|
80
|
+
const body = await postJson(CLAUDE_OAUTH.tokenUrl, {
|
|
81
|
+
grant_type: "authorization_code",
|
|
82
|
+
code,
|
|
83
|
+
state,
|
|
84
|
+
client_id: CLAUDE_OAUTH.clientId,
|
|
85
|
+
redirect_uri: CLAUDE_OAUTH.redirectUri,
|
|
86
|
+
code_verifier: pkce.verifier
|
|
87
|
+
});
|
|
88
|
+
return tokenSetFrom(body, now);
|
|
89
|
+
}
|
|
90
|
+
async function refreshTokens(tokens, postJson, now) {
|
|
91
|
+
if (!tokens.refreshToken) {
|
|
92
|
+
throw new import_provider.FetchError("auth", "no refresh token \u2014 sign in again");
|
|
93
|
+
}
|
|
94
|
+
const body = await postJson(CLAUDE_OAUTH.tokenUrl, {
|
|
95
|
+
grant_type: "refresh_token",
|
|
96
|
+
refresh_token: tokens.refreshToken,
|
|
97
|
+
client_id: CLAUDE_OAUTH.clientId
|
|
98
|
+
});
|
|
99
|
+
return tokenSetFrom(body, now, tokens.refreshToken);
|
|
100
|
+
}
|
|
101
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
102
|
+
0 && (module.exports = {
|
|
103
|
+
CLAUDE_OAUTH,
|
|
104
|
+
buildAuthorizeUrl,
|
|
105
|
+
exchangeCode,
|
|
106
|
+
generatePkce,
|
|
107
|
+
refreshTokens
|
|
108
|
+
});
|
|
109
|
+
//# sourceMappingURL=claude-auth.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/providers/claude-auth.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash, randomBytes } from \"node:crypto\";\nimport { FetchError } from \"../provider\";\n\n/**\n * Claude subscription OAuth \u2014 the flow Claude Code and the HA reference integration\n * (trickv/hass-claude-usage, source-verified 2026-08-25) use: PKCE authorize on\n * claude.ai, the user pastes the resulting code, tokens come from the console\n * token endpoint and are refreshed with the refresh token.\n */\nexport const CLAUDE_OAUTH = {\n clientId: \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\",\n authorizeUrl: \"https://claude.ai/oauth/authorize\",\n tokenUrl: \"https://console.anthropic.com/v1/oauth/token\",\n redirectUri: \"https://console.anthropic.com/oauth/code/callback\",\n scopes: \"org:create_api_key user:profile user:inference\",\n usageUrl: \"https://api.anthropic.com/api/oauth/usage\",\n profileUrl: \"https://api.anthropic.com/api/oauth/profile\",\n betaHeader: \"oauth-2025-04-20\",\n} as const;\n\n/** A PKCE pair for one sign-in attempt. */\nexport interface PkcePair {\n /** The secret verifier (sent in the code exchange). */\n verifier: string;\n /** The S256 challenge (sent in the authorize URL). */\n challenge: string;\n /** The CSRF state. */\n state: string;\n}\n\n/** The token set the flow yields. */\nexport interface TokenSet {\n /** Bearer token for the usage/profile calls. */\n accessToken: string;\n /** Long-lived token to obtain fresh access tokens. */\n refreshToken: string;\n /** When the access token expires (ms since epoch). */\n expiresAt: number;\n}\n\n/** The JSON-POST seam (tests inject a fake). */\nexport type JsonPost = (url: string, body: Record<string, unknown>) => Promise<unknown>;\n\n/**\n * Generate a PKCE verifier/challenge pair plus CSRF state.\n *\n * @returns the pair\n */\nexport function generatePkce(): PkcePair {\n const verifier = randomBytes(32).toString(\"base64url\");\n const challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n return { verifier, challenge, state: randomBytes(24).toString(\"base64url\") };\n}\n\n/**\n * Build the authorize URL the user opens to sign in.\n *\n * @param pkce the sign-in attempt's PKCE pair\n * @returns the URL\n */\nexport function buildAuthorizeUrl(pkce: PkcePair): string {\n const params = new URLSearchParams({\n code: \"true\",\n client_id: CLAUDE_OAUTH.clientId,\n response_type: \"code\",\n redirect_uri: CLAUDE_OAUTH.redirectUri,\n scope: CLAUDE_OAUTH.scopes,\n code_challenge: pkce.challenge,\n code_challenge_method: \"S256\",\n state: pkce.state,\n });\n return `${CLAUDE_OAUTH.authorizeUrl}?${params.toString()}`;\n}\n\n/**\n * Read a token response body into a {@link TokenSet}.\n *\n * @param body the token endpoint response\n * @param now current time (ms)\n * @param previousRefresh the refresh token to keep when the response carries none\n * @returns the token set\n */\nfunction tokenSetFrom(body: unknown, now: number, previousRefresh = \"\"): TokenSet {\n const data = body as Record<string, unknown> | null;\n const accessToken = typeof data?.access_token === \"string\" ? data.access_token : \"\";\n if (!accessToken) {\n throw new FetchError(\"auth\", \"token response carries no access token\");\n }\n const refreshToken =\n typeof data?.refresh_token === \"string\" && data.refresh_token ? data.refresh_token : previousRefresh;\n const expiresIn = Number(data?.expires_in);\n return {\n accessToken,\n refreshToken,\n expiresAt: now + (Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : 3600) * 1000,\n };\n}\n\n/**\n * Exchange a pasted authorization code (possibly in the `code#state` form) for tokens.\n *\n * @param pastedCode what the user pasted\n * @param pkce the sign-in attempt's PKCE pair\n * @param postJson the JSON-POST seam\n * @param now current time (ms)\n * @returns the token set\n */\nexport async function exchangeCode(\n pastedCode: string,\n pkce: PkcePair,\n postJson: JsonPost,\n now: number,\n): Promise<TokenSet> {\n const [code, state = \"\"] = pastedCode.trim().split(\"#\");\n if (!code) {\n throw new FetchError(\"auth\", \"empty authorization code\");\n }\n if (state && state !== pkce.state) {\n throw new FetchError(\"auth\", \"state mismatch \u2014 start the sign-in again\");\n }\n const body = await postJson(CLAUDE_OAUTH.tokenUrl, {\n grant_type: \"authorization_code\",\n code,\n state,\n client_id: CLAUDE_OAUTH.clientId,\n redirect_uri: CLAUDE_OAUTH.redirectUri,\n code_verifier: pkce.verifier,\n });\n return tokenSetFrom(body, now);\n}\n\n/**\n * Obtain a fresh access token with the refresh token.\n *\n * @param tokens the current token set\n * @param postJson the JSON-POST seam\n * @param now current time (ms)\n * @returns the new token set (keeps the old refresh token when none is returned)\n */\nexport async function refreshTokens(tokens: TokenSet, postJson: JsonPost, now: number): Promise<TokenSet> {\n if (!tokens.refreshToken) {\n throw new FetchError(\"auth\", \"no refresh token \u2014 sign in again\");\n }\n const body = await postJson(CLAUDE_OAUTH.tokenUrl, {\n grant_type: \"refresh_token\",\n refresh_token: tokens.refreshToken,\n client_id: CLAUDE_OAUTH.clientId,\n });\n return tokenSetFrom(body, now, tokens.refreshToken);\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAwC;AACxC,sBAA2B;AAQpB,MAAM,eAAe;AAAA,EAC1B,UAAU;AAAA,EACV,cAAc;AAAA,EACd,UAAU;AAAA,EACV,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,YAAY;AACd;AA8BO,SAAS,eAAyB;AACvC,QAAM,eAAW,gCAAY,EAAE,EAAE,SAAS,WAAW;AACrD,QAAM,gBAAY,+BAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,WAAW;AAC1E,SAAO,EAAE,UAAU,WAAW,WAAO,gCAAY,EAAE,EAAE,SAAS,WAAW,EAAE;AAC7E;AAQO,SAAS,kBAAkB,MAAwB;AACxD,QAAM,SAAS,IAAI,gBAAgB;AAAA,IACjC,MAAM;AAAA,IACN,WAAW,aAAa;AAAA,IACxB,eAAe;AAAA,IACf,cAAc,aAAa;AAAA,IAC3B,OAAO,aAAa;AAAA,IACpB,gBAAgB,KAAK;AAAA,IACrB,uBAAuB;AAAA,IACvB,OAAO,KAAK;AAAA,EACd,CAAC;AACD,SAAO,GAAG,aAAa,YAAY,IAAI,OAAO,SAAS,CAAC;AAC1D;AAUA,SAAS,aAAa,MAAe,KAAa,kBAAkB,IAAc;AAChF,QAAM,OAAO;AACb,QAAM,cAAc,QAAO,6BAAM,kBAAiB,WAAW,KAAK,eAAe;AACjF,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,2BAAW,QAAQ,wCAAwC;AAAA,EACvE;AACA,QAAM,eACJ,QAAO,6BAAM,mBAAkB,YAAY,KAAK,gBAAgB,KAAK,gBAAgB;AACvF,QAAM,YAAY,OAAO,6BAAM,UAAU;AACzC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW,OAAO,OAAO,SAAS,SAAS,KAAK,YAAY,IAAI,YAAY,QAAQ;AAAA,EACtF;AACF;AAWA,eAAsB,aACpB,YACA,MACA,UACA,KACmB;AACnB,QAAM,CAAC,MAAM,QAAQ,EAAE,IAAI,WAAW,KAAK,EAAE,MAAM,GAAG;AACtD,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,2BAAW,QAAQ,0BAA0B;AAAA,EACzD;AACA,MAAI,SAAS,UAAU,KAAK,OAAO;AACjC,UAAM,IAAI,2BAAW,QAAQ,+CAA0C;AAAA,EACzE;AACA,QAAM,OAAO,MAAM,SAAS,aAAa,UAAU;AAAA,IACjD,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,WAAW,aAAa;AAAA,IACxB,cAAc,aAAa;AAAA,IAC3B,eAAe,KAAK;AAAA,EACtB,CAAC;AACD,SAAO,aAAa,MAAM,GAAG;AAC/B;AAUA,eAAsB,cAAc,QAAkB,UAAoB,KAAgC;AACxG,MAAI,CAAC,OAAO,cAAc;AACxB,UAAM,IAAI,2BAAW,QAAQ,uCAAkC;AAAA,EACjE;AACA,QAAM,OAAO,MAAM,SAAS,aAAa,UAAU;AAAA,IACjD,YAAY;AAAA,IACZ,eAAe,OAAO;AAAA,IACtB,WAAW,aAAa;AAAA,EAC1B,CAAC;AACD,SAAO,aAAa,MAAM,KAAK,OAAO,YAAY;AACpD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var claude_sub_exports = {};
|
|
20
|
+
__export(claude_sub_exports, {
|
|
21
|
+
claudeSubProvider: () => claudeSubProvider,
|
|
22
|
+
parseClaudeUsage: () => parseClaudeUsage
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(claude_sub_exports);
|
|
25
|
+
var import_http = require("../http");
|
|
26
|
+
var import_provider = require("../provider");
|
|
27
|
+
var import_pure_helpers = require("../pure-helpers");
|
|
28
|
+
var import_claude_auth = require("./claude-auth");
|
|
29
|
+
function parseClaudeUsage(body) {
|
|
30
|
+
var _a, _b;
|
|
31
|
+
if (typeof body !== "object" || body === null) {
|
|
32
|
+
throw new import_provider.FetchError("network", "unexpected usage response");
|
|
33
|
+
}
|
|
34
|
+
const raw = body;
|
|
35
|
+
const limits = [];
|
|
36
|
+
const seen = /* @__PURE__ */ new Set();
|
|
37
|
+
const push = (name, label, percent, resetsAt) => {
|
|
38
|
+
const id = (0, import_pure_helpers.sanitizeId)(name);
|
|
39
|
+
const value = Number(percent);
|
|
40
|
+
if (!id || seen.has(id) || !Number.isFinite(value)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
seen.add(id);
|
|
44
|
+
const window = { name: id, label, percent: value };
|
|
45
|
+
if (typeof resetsAt === "string" && resetsAt) {
|
|
46
|
+
window.resetAt = resetsAt;
|
|
47
|
+
}
|
|
48
|
+
limits.push(window);
|
|
49
|
+
};
|
|
50
|
+
if (Array.isArray(raw.limits)) {
|
|
51
|
+
for (const entry of raw.limits) {
|
|
52
|
+
if (typeof entry !== "object" || entry === null) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const limit = entry;
|
|
56
|
+
const kind = typeof limit.kind === "string" ? limit.kind : "";
|
|
57
|
+
if (!kind) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const scope = (_a = limit.scope) != null ? _a : {};
|
|
61
|
+
const model = ((_b = scope.model) != null ? _b : {}).display_name;
|
|
62
|
+
const surface = scope.surface;
|
|
63
|
+
const nameParts = [kind === "session" ? "session" : kind === "weekly_all" ? "week" : kind];
|
|
64
|
+
const labelParts = [
|
|
65
|
+
kind === "session" ? "Session (5 h)" : kind === "weekly_all" ? "Week (all models)" : kind.replace(/_/g, " ")
|
|
66
|
+
];
|
|
67
|
+
if (typeof model === "string" && model) {
|
|
68
|
+
nameParts.push(model);
|
|
69
|
+
labelParts.push(model);
|
|
70
|
+
}
|
|
71
|
+
if (typeof surface === "string" && surface) {
|
|
72
|
+
nameParts.push(surface);
|
|
73
|
+
labelParts.push(`(${surface})`);
|
|
74
|
+
}
|
|
75
|
+
push(nameParts.join("-"), labelParts.join(" "), limit.percent, limit.resets_at);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (limits.length === 0) {
|
|
79
|
+
const flat = (key, name, label) => {
|
|
80
|
+
const block = raw[key];
|
|
81
|
+
if (typeof block === "object" && block !== null) {
|
|
82
|
+
const data = block;
|
|
83
|
+
push(name, label, data.utilization, data.resets_at);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
flat("five_hour", "session", "Session (5 h)");
|
|
87
|
+
flat("seven_day", "week", "Week (all models)");
|
|
88
|
+
flat("seven_day_sonnet", "week-sonnet", "Week Sonnet");
|
|
89
|
+
}
|
|
90
|
+
const snapshot = {};
|
|
91
|
+
if (limits.length > 0) {
|
|
92
|
+
snapshot.limits = limits;
|
|
93
|
+
}
|
|
94
|
+
applyExtraUsage(raw, snapshot);
|
|
95
|
+
return snapshot;
|
|
96
|
+
}
|
|
97
|
+
function applyExtraUsage(raw, snapshot) {
|
|
98
|
+
const extra = raw.extra_usage;
|
|
99
|
+
const spend = raw.spend;
|
|
100
|
+
if (extra && extra.is_enabled === true) {
|
|
101
|
+
const divisor = 10 ** (Number.isFinite(Number(extra.decimal_places)) ? Number(extra.decimal_places) : 2);
|
|
102
|
+
const used = Number(extra.used_credits);
|
|
103
|
+
const limit = Number(extra.monthly_limit);
|
|
104
|
+
const percent = Number(extra.utilization);
|
|
105
|
+
snapshot.credits = {
|
|
106
|
+
used: Number.isFinite(used) ? used / divisor : void 0,
|
|
107
|
+
limit: Number.isFinite(limit) ? limit / divisor : void 0,
|
|
108
|
+
percent: Number.isFinite(percent) ? percent : void 0,
|
|
109
|
+
currency: "USD"
|
|
110
|
+
};
|
|
111
|
+
if (snapshot.credits.used !== void 0) {
|
|
112
|
+
snapshot.costs = { month: snapshot.credits.used, currency: "USD" };
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (spend && spend.enabled === true) {
|
|
117
|
+
const money = (value) => {
|
|
118
|
+
const obj = value;
|
|
119
|
+
const amount = Number(obj == null ? void 0 : obj.amount_minor);
|
|
120
|
+
const exponent = Number(obj == null ? void 0 : obj.exponent);
|
|
121
|
+
return Number.isFinite(amount) ? amount / 10 ** (Number.isFinite(exponent) ? exponent : 2) : void 0;
|
|
122
|
+
};
|
|
123
|
+
const used = money(spend.used);
|
|
124
|
+
const percent = Number(spend.percent);
|
|
125
|
+
snapshot.credits = {
|
|
126
|
+
used,
|
|
127
|
+
limit: money(spend.limit),
|
|
128
|
+
percent: Number.isFinite(percent) ? percent : void 0,
|
|
129
|
+
currency: "USD"
|
|
130
|
+
};
|
|
131
|
+
if (used !== void 0) {
|
|
132
|
+
snapshot.costs = { month: used, currency: "USD" };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function claudeSubProvider(store, fetchJson = import_http.getJson, postJson, now = Date.now) {
|
|
137
|
+
let cached = null;
|
|
138
|
+
return {
|
|
139
|
+
kind: "claude-sub",
|
|
140
|
+
fetch: async () => {
|
|
141
|
+
cached != null ? cached : cached = await store.load();
|
|
142
|
+
if (!cached) {
|
|
143
|
+
throw new import_provider.FetchError("auth", "not signed in \u2014 run the Claude sign-in in the instance settings");
|
|
144
|
+
}
|
|
145
|
+
if (now() >= cached.expiresAt - 6e4) {
|
|
146
|
+
cached = await (0, import_claude_auth.refreshTokens)(cached, postJson, now());
|
|
147
|
+
await store.save(cached);
|
|
148
|
+
}
|
|
149
|
+
const body = await fetchJson(import_claude_auth.CLAUDE_OAUTH.usageUrl, {
|
|
150
|
+
Authorization: `Bearer ${cached.accessToken}`,
|
|
151
|
+
"anthropic-beta": import_claude_auth.CLAUDE_OAUTH.betaHeader,
|
|
152
|
+
// Identify ourselves — an unset/odd user agent lands in a harder-throttled
|
|
153
|
+
// bucket of this endpoint (community-measured; sources in the concept doc).
|
|
154
|
+
"User-Agent": "ioBroker.ai-usage"
|
|
155
|
+
});
|
|
156
|
+
return parseClaudeUsage(body);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
161
|
+
0 && (module.exports = {
|
|
162
|
+
claudeSubProvider,
|
|
163
|
+
parseClaudeUsage
|
|
164
|
+
});
|
|
165
|
+
//# sourceMappingURL=claude-sub.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/providers/claude-sub.ts"],
|
|
4
|
+
"sourcesContent": ["import { getJson, type JsonFetch } from \"../http\";\nimport { FetchError, type LimitWindow, type UsageProvider, type UsageSnapshot } from \"../provider\";\nimport { sanitizeId } from \"../pure-helpers\";\nimport { CLAUDE_OAUTH, refreshTokens, type JsonPost, type TokenSet } from \"./claude-auth\";\n\n/**\n * Parse a Claude subscription `GET /api/oauth/usage` response into a snapshot.\n *\n * Source-verified against the HA reference integration (trickv/hass-claude-usage):\n * the LIVE per-scope meters are the top-level `limits[]` array (kind session /\n * weekly_all / weekly_scoped + scope.model/surface, percent, resets_at); the flat\n * `five_hour`/`seven_day*` keys are placeholders on current accounts and serve only\n * as a fallback. Extra usage arrives as `extra_usage` (credits \u00D7 decimal_places) or\n * the newer `spend` schema (money objects with amount_minor/exponent).\n *\n * @param body the usage response\n * @returns the snapshot\n */\nexport function parseClaudeUsage(body: unknown): UsageSnapshot {\n if (typeof body !== \"object\" || body === null) {\n throw new FetchError(\"network\", \"unexpected usage response\");\n }\n const raw = body as Record<string, unknown>;\n const limits: LimitWindow[] = [];\n const seen = new Set<string>();\n const push = (name: string, label: string, percent: unknown, resetsAt: unknown): void => {\n const id = sanitizeId(name);\n const value = Number(percent);\n if (!id || seen.has(id) || !Number.isFinite(value)) {\n return;\n }\n seen.add(id);\n const window: LimitWindow = { name: id, label, percent: value };\n if (typeof resetsAt === \"string\" && resetsAt) {\n window.resetAt = resetsAt;\n }\n limits.push(window);\n };\n\n // The live meters: limits[] \u2014 buckets keyed by kind + model + surface.\n if (Array.isArray(raw.limits)) {\n for (const entry of raw.limits) {\n if (typeof entry !== \"object\" || entry === null) {\n continue;\n }\n const limit = entry as Record<string, unknown>;\n const kind = typeof limit.kind === \"string\" ? limit.kind : \"\";\n if (!kind) {\n continue;\n }\n const scope = (limit.scope ?? {}) as Record<string, unknown>;\n const model = ((scope.model ?? {}) as Record<string, unknown>).display_name;\n const surface = scope.surface;\n const nameParts = [kind === \"session\" ? \"session\" : kind === \"weekly_all\" ? \"week\" : kind];\n const labelParts = [\n kind === \"session\" ? \"Session (5 h)\" : kind === \"weekly_all\" ? \"Week (all models)\" : kind.replace(/_/g, \" \"),\n ];\n if (typeof model === \"string\" && model) {\n nameParts.push(model);\n labelParts.push(model);\n }\n if (typeof surface === \"string\" && surface) {\n nameParts.push(surface);\n labelParts.push(`(${surface})`);\n }\n push(nameParts.join(\"-\"), labelParts.join(\" \"), limit.percent, limit.resets_at);\n }\n }\n\n // Fallback for older payloads without the limits[] array.\n if (limits.length === 0) {\n const flat = (key: string, name: string, label: string): void => {\n const block = raw[key];\n if (typeof block === \"object\" && block !== null) {\n const data = block as Record<string, unknown>;\n push(name, label, data.utilization, data.resets_at);\n }\n };\n flat(\"five_hour\", \"session\", \"Session (5 h)\");\n flat(\"seven_day\", \"week\", \"Week (all models)\");\n flat(\"seven_day_sonnet\", \"week-sonnet\", \"Week Sonnet\");\n }\n\n const snapshot: UsageSnapshot = {};\n if (limits.length > 0) {\n snapshot.limits = limits;\n }\n applyExtraUsage(raw, snapshot);\n return snapshot;\n}\n\n/**\n * Map the extra-usage block (either schema) onto credits + monthly costs.\n *\n * @param raw the usage response\n * @param snapshot the snapshot to extend\n */\nfunction applyExtraUsage(raw: Record<string, unknown>, snapshot: UsageSnapshot): void {\n const extra = raw.extra_usage as Record<string, unknown> | undefined | null;\n const spend = raw.spend as Record<string, unknown> | undefined | null;\n if (extra && extra.is_enabled === true) {\n const divisor = 10 ** (Number.isFinite(Number(extra.decimal_places)) ? Number(extra.decimal_places) : 2);\n const used = Number(extra.used_credits);\n const limit = Number(extra.monthly_limit);\n const percent = Number(extra.utilization);\n snapshot.credits = {\n used: Number.isFinite(used) ? used / divisor : undefined,\n limit: Number.isFinite(limit) ? limit / divisor : undefined,\n percent: Number.isFinite(percent) ? percent : undefined,\n currency: \"USD\",\n };\n if (snapshot.credits.used !== undefined) {\n snapshot.costs = { month: snapshot.credits.used, currency: \"USD\" };\n }\n return;\n }\n if (spend && spend.enabled === true) {\n const money = (value: unknown): number | undefined => {\n const obj = value as Record<string, unknown> | null | undefined;\n const amount = Number(obj?.amount_minor);\n const exponent = Number(obj?.exponent);\n return Number.isFinite(amount) ? amount / 10 ** (Number.isFinite(exponent) ? exponent : 2) : undefined;\n };\n const used = money(spend.used);\n const percent = Number(spend.percent);\n snapshot.credits = {\n used,\n limit: money(spend.limit),\n percent: Number.isFinite(percent) ? percent : undefined,\n currency: \"USD\",\n };\n if (used !== undefined) {\n snapshot.costs = { month: used, currency: \"USD\" };\n }\n }\n}\n\n/** Persistent token storage \u2014 the adapter keeps the tokens in its data directory. */\nexport interface TokenStore {\n /** Read the stored token set, or null when never signed in. */\n load(): Promise<TokenSet | null>;\n /** Persist a token set. */\n save(tokens: TokenSet): Promise<void>;\n}\n\n/**\n * The Claude subscription provider: keeps the access token fresh (refresh 60 s\n * before expiry, persisted via the store) and reads the usage meters.\n *\n * @param store the token storage\n * @param fetchJson the JSON-GET seam\n * @param postJson the JSON-POST seam (token refresh)\n * @param now clock (ms) \u2014 injected for tests\n * @returns the provider\n */\nexport function claudeSubProvider(\n store: TokenStore,\n fetchJson: JsonFetch = getJson,\n postJson: JsonPost,\n now: () => number = Date.now,\n): UsageProvider {\n let cached: TokenSet | null = null;\n return {\n kind: \"claude-sub\",\n fetch: async (): Promise<UsageSnapshot> => {\n cached ??= await store.load();\n if (!cached) {\n throw new FetchError(\"auth\", \"not signed in \u2014 run the Claude sign-in in the instance settings\");\n }\n if (now() >= cached.expiresAt - 60_000) {\n cached = await refreshTokens(cached, postJson, now());\n await store.save(cached);\n }\n const body = await fetchJson(CLAUDE_OAUTH.usageUrl, {\n Authorization: `Bearer ${cached.accessToken}`,\n \"anthropic-beta\": CLAUDE_OAUTH.betaHeader,\n // Identify ourselves \u2014 an unset/odd user agent lands in a harder-throttled\n // bucket of this endpoint (community-measured; sources in the concept doc).\n \"User-Agent\": \"ioBroker.ai-usage\",\n });\n return parseClaudeUsage(body);\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var deepseek_exports = {};
|
|
20
|
+
__export(deepseek_exports, {
|
|
21
|
+
deepSeekProvider: () => deepSeekProvider,
|
|
22
|
+
parseDeepSeekBalance: () => parseDeepSeekBalance
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(deepseek_exports);
|
|
25
|
+
var import_http = require("../http");
|
|
26
|
+
var import_provider = require("../provider");
|
|
27
|
+
function parseDeepSeekBalance(body) {
|
|
28
|
+
const obj = body;
|
|
29
|
+
if (typeof obj !== "object" || obj === null || !Array.isArray(obj.balance_infos)) {
|
|
30
|
+
throw new import_provider.FetchError("network", "unexpected response shape (no balance_infos)");
|
|
31
|
+
}
|
|
32
|
+
const first = obj.balance_infos.find((entry) => typeof entry === "object" && entry !== null);
|
|
33
|
+
const snapshot = {};
|
|
34
|
+
if (typeof obj.is_available === "boolean") {
|
|
35
|
+
snapshot.available = obj.is_available;
|
|
36
|
+
}
|
|
37
|
+
if (first) {
|
|
38
|
+
snapshot.credits = {
|
|
39
|
+
remaining: parseAmount(first.total_balance),
|
|
40
|
+
granted: parseAmount(first.granted_balance),
|
|
41
|
+
toppedUp: parseAmount(first.topped_up_balance),
|
|
42
|
+
currency: typeof first.currency === "string" ? first.currency : "USD"
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return snapshot;
|
|
46
|
+
}
|
|
47
|
+
function deepSeekProvider(apiKey, fetchJson = import_http.getJson) {
|
|
48
|
+
return {
|
|
49
|
+
kind: "deepseek",
|
|
50
|
+
fetch: async () => parseDeepSeekBalance(
|
|
51
|
+
await fetchJson("https://api.deepseek.com/user/balance", {
|
|
52
|
+
Authorization: `Bearer ${apiKey}`,
|
|
53
|
+
Accept: "application/json"
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function parseAmount(value) {
|
|
59
|
+
const num = Number(value);
|
|
60
|
+
return value !== null && value !== void 0 && value !== "" && Number.isFinite(num) ? num : void 0;
|
|
61
|
+
}
|
|
62
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
63
|
+
0 && (module.exports = {
|
|
64
|
+
deepSeekProvider,
|
|
65
|
+
parseDeepSeekBalance
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=deepseek.js.map
|