iobroker.ai-usage 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/build/main.js +11 -2
- package/build/main.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,6 +126,10 @@ After a successful sign-in the account is queried immediately, so values appear
|
|
|
126
126
|
Placeholder for the next version (at the beginning of the line):
|
|
127
127
|
### **WORK IN PROGRESS**
|
|
128
128
|
-->
|
|
129
|
+
### 0.9.3 (2026-08-27)
|
|
130
|
+
|
|
131
|
+
- Fixed: The first start after updating no longer leaves a warning in the log
|
|
132
|
+
|
|
129
133
|
### 0.9.2 (2026-08-27)
|
|
130
134
|
|
|
131
135
|
- Fixed: Stopping the instance now marks the accounts as offline on installations that were updated too, not only on fresh ones — the previous version left them showing as online
|
|
@@ -151,10 +155,6 @@ After a successful sign-in the account is queried immediately, so values appear
|
|
|
151
155
|
- Fixed: "Configured accounts" counts what you switched on, including accounts whose credential could not be read; the sign-in button no longer hangs for up to 15 seconds
|
|
152
156
|
- Fixed: A damaged or unreadable stored sign-in now says so in the log instead of looking exactly like "never signed in"
|
|
153
157
|
|
|
154
|
-
### 0.7.1 (2026-08-27)
|
|
155
|
-
|
|
156
|
-
- Fixed: Restarting the adapter no longer writes one unchanged value into every status datapoint, so a recorded history stays free of restart noise
|
|
157
|
-
|
|
158
158
|
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
159
159
|
|
|
160
160
|
## Support
|
package/build/main.js
CHANGED
|
@@ -481,6 +481,11 @@ class AiUsageAdapter extends utils.Adapter {
|
|
|
481
481
|
* the price, it happens on the first start after the update and never again —
|
|
482
482
|
* afterwards the condition below is false. public-holidays corrects its own run
|
|
483
483
|
* mode the same way.
|
|
484
|
+
*
|
|
485
|
+
* @returns true when the correction was written and the restart is coming — the
|
|
486
|
+
* caller has to stop right there. Carrying on would arm the poll timers of a
|
|
487
|
+
* process the host is already shutting down, which the adapter's timer API
|
|
488
|
+
* refuses with a warning in the user's log (measured on the live server).
|
|
484
489
|
*/
|
|
485
490
|
async clearStopInstanceFlag() {
|
|
486
491
|
var _a;
|
|
@@ -489,17 +494,21 @@ class AiUsageAdapter extends utils.Adapter {
|
|
|
489
494
|
const obj = await this.getForeignObjectAsync(id);
|
|
490
495
|
const supported = (_a = obj == null ? void 0 : obj.common) == null ? void 0 : _a.supportedMessages;
|
|
491
496
|
if (!(supported == null ? void 0 : supported.stopInstance)) {
|
|
492
|
-
return;
|
|
497
|
+
return false;
|
|
493
498
|
}
|
|
494
499
|
this.log.info("Correcting a leftover setting from an earlier version \u2014 this instance restarts once");
|
|
495
500
|
await this.extendForeignObjectAsync(id, { common: { supportedMessages: { stopInstance: false } } });
|
|
501
|
+
return true;
|
|
496
502
|
} catch (e) {
|
|
497
503
|
this.log.debug(`Could not check the instance object: ${e instanceof Error ? e.message : String(e)}`);
|
|
504
|
+
return false;
|
|
498
505
|
}
|
|
499
506
|
}
|
|
500
507
|
async onReady() {
|
|
501
508
|
try {
|
|
502
|
-
await this.clearStopInstanceFlag()
|
|
509
|
+
if (await this.clearStopInstanceFlag()) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
503
512
|
const accounts = (0, import_pure_helpers.parseAccounts)(this.config.accounts);
|
|
504
513
|
const interval = (0, import_pure_helpers.clampPollInterval)(this.config.pollInterval);
|
|
505
514
|
await this.migrateTokenFiles();
|
package/build/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts"],
|
|
4
|
-
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { Credentials } from \"@iobroker/adapter-core\";\nimport { mkdir, readFile, rename, unlink, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { postForm, postJson } from \"./lib/http\";\nimport { PollEngine } from \"./lib/poll-engine\";\nimport {\n clampPollInterval,\n datapointBalanceLine,\n parseAccounts,\n SUBSCRIPTION_IDS,\n type AccountConfig,\n} from \"./lib/pure-helpers\";\nimport type { TokenSet, TokenStore, UsageProvider } from \"./lib/provider\";\nimport { SIGN_IN_FLOWS, SIGN_IN_LABELS, attemptExpired, type SignInState } from \"./lib/sign-in\";\nimport { buildAuthorizeUrl, exchangeCode, generatePkce, type PkcePair } from \"./lib/providers/claude-auth\";\nimport { claudeSubProvider } from \"./lib/providers/claude-sub\";\nimport {\n CHATGPT_OAUTH,\n exchangeDeviceCode,\n pollDeviceCode,\n startDeviceCode,\n type DeviceCodeStart,\n} from \"./lib/providers/chatgpt-auth\";\nimport { chatgptSubProvider } from \"./lib/providers/chatgpt-sub\";\nimport {\n buildGeminiAuthorizeUrl,\n exchangeGeminiCode,\n extractGeminiCode,\n generateGeminiPkce,\n type GeminiPkce,\n} from \"./lib/providers/gemini-auth\";\nimport { geminiSubProvider } from \"./lib/providers/gemini-sub\";\nimport { anthropicApiProvider } from \"./lib/providers/anthropic-api\";\nimport { deepSeekProvider } from \"./lib/providers/deepseek\";\nimport { openAiProvider } from \"./lib/providers/openai\";\nimport { openRouterProvider } from \"./lib/providers/openrouter\";\n\n/** A cancellable handle: interval or timeout \u2014 the engine treats them uniformly. */\ntype TimerHandle =\n | { kind: \"interval\"; handle: ioBroker.Interval | undefined }\n | { kind: \"timeout\"; handle: ioBroker.Timeout | undefined };\n\n/** One running sign-in attempt (secrets live in memory only, never on disk). */\ntype Attempt =\n | { flow: \"paste-code\"; pkce: PkcePair; url: string; expiresAt: number }\n | { flow: \"paste-url\"; pkce: GeminiPkce; url: string; expiresAt: number }\n | { flow: \"device-code\"; start: DeviceCodeStart };\n\n/**\n * AI Usage adapter \u2014 reads usage windows, credits and costs of AI accounts into\n * read-only states. Three subscriptions sign in with the user's own account\n * (Claude, ChatGPT, Google), the other accounts use a key from the admin's central\n * credential storage. Orchestration lives in the unit-tested {@link PollEngine};\n * this class wires ioBroker IO, the sign-in flows and the token files to it.\n */\nexport class AiUsageAdapter extends utils.Adapter {\n private engine: PollEngine | null = null;\n /** Running sign-in attempts, keyed by provider kind. */\n private readonly attempts = new Map<string, Attempt>();\n /** Last failure reason per provider, shown in the admin row. */\n private readonly signInErrors = new Map<string, string>();\n /** Device-code pollers, so they can be stopped on unload. */\n private readonly devicePollers = new Map<string, TimerHandle>();\n /** One token store per subscription \u2014 see {@link tokenStore} for why it is shared. */\n private readonly tokenStores = new Map<string, TokenStore>();\n\n /**\n * Every state id that already existed when this process started.\n *\n * The create path runs `extendObject` for every state once per process \u2014 also for\n * states that were already in the database \u2014 so \"the create path touched it\" would\n * report every datapoint as new after each restart. Only what is missing from this\n * snapshot is a real addition (beszel pattern).\n */\n private knownStateIds = new Set<string>();\n /** Datapoints created since the snapshot. */\n private createdStates = 0;\n /** Datapoints removed since the snapshot \u2014 excluding the one-shot migration, which reports itself. */\n private removedStates = 0;\n /** Whether the startup balance was already logged. */\n private balanceLogged = false;\n\n /**\n * Read every existing state id once, before anything creates or deletes.\n *\n * @returns nothing; fills {@link knownStateIds}\n */\n private async snapshotExistingStates(): Promise<void> {\n try {\n const view = await this.getObjectViewAsync(\"system\", \"state\", {\n startkey: `${this.namespace}.`,\n endkey: `${this.namespace}.\\uFFFF`,\n });\n for (const row of view?.rows ?? []) {\n this.knownStateIds.add(row.id.substring(this.namespace.length + 1));\n }\n } catch (e) {\n this.log.debug(`Could not snapshot existing states: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Report what the object tree gained and lost in this startup \u2014 one line, both\n * sides, silent when nothing changed. A normal restart must stay quiet.\n */\n private logDatapointBalance(): void {\n if (this.balanceLogged) {\n return;\n }\n this.balanceLogged = true;\n const line = datapointBalanceLine(this.createdStates, this.removedStates);\n if (line) {\n this.log.info(line);\n }\n }\n\n /**\n * @param options the adapter options\n */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"ai-usage\" });\n this.on(\"ready\", this.onReady.bind(this));\n this.on(\"message\", this.onMessage.bind(this));\n this.on(\"unload\", this.onUnload.bind(this));\n }\n\n // ---------------------------------------------------------------- sign-in\n\n /**\n * Handle admin messages: the three sign-in flows plus their status.\n *\n * @param obj the message\n */\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n try {\n const provider = this.providerFrom(obj.message);\n switch (obj.command) {\n case \"signInStart\":\n this.respond(obj, provider ? await this.startSignIn(provider) : { error: \"unknown provider\" });\n return;\n case \"signInSubmit\":\n this.respond(obj, provider ? await this.submitSignIn(provider, obj.message) : { error: \"unknown provider\" });\n return;\n case \"signInStatus\":\n this.respond(obj, provider ? await this.signInState(provider) : { error: \"unknown provider\" });\n return;\n case \"signOut\":\n this.respond(obj, provider ? await this.signOut(provider) : { error: \"unknown provider\" });\n return;\n default:\n // Always answer, or the caller's callback dangles until timeout.\n this.respond(obj, { error: `Unknown command: ${obj.command}` });\n }\n } catch (e) {\n this.log.error(`onMessage failed: ${e instanceof Error ? e.message : String(e)}`);\n this.respond(obj, { error: \"internal error \u2014 see log\" });\n }\n }\n\n /**\n * Send a message response, when the caller expects one.\n *\n * @param obj the request message\n * @param response the response payload\n */\n private respond(obj: ioBroker.Message, response: unknown): void {\n if (obj.callback) {\n this.sendTo(obj.from, obj.command, response, obj.callback);\n }\n }\n\n /**\n * The provider kind named in a message, if it is a subscription we know.\n *\n * @param message the message payload ({ provider })\n * @returns the provider kind, or undefined\n */\n private providerFrom(message: unknown): string | undefined {\n const value =\n typeof (message as { provider?: unknown })?.provider === \"string\"\n ? (message as { provider: string }).provider\n : \"\";\n return SIGN_IN_FLOWS[value] ? value : undefined;\n }\n\n /**\n * Begin a sign-in: build the link (Claude/Google) or fetch a device code (ChatGPT).\n *\n * @param provider the subscription kind\n * @returns what the admin panel has to show\n */\n private async startSignIn(provider: string): Promise<SignInState | { error: string }> {\n this.signInErrors.delete(provider);\n this.stopDevicePoller(provider);\n const flow = SIGN_IN_FLOWS[provider];\n const now = Date.now();\n try {\n if (flow === \"paste-code\") {\n const pkce = generatePkce();\n const url = buildAuthorizeUrl(pkce);\n this.attempts.set(provider, { flow, pkce, url, expiresAt: now + 15 * 60_000 });\n return { status: \"awaiting-paste\", url, flow };\n }\n if (flow === \"paste-url\") {\n const pkce = generateGeminiPkce();\n const url = buildGeminiAuthorizeUrl(pkce);\n this.attempts.set(provider, { flow, pkce, url, expiresAt: now + 15 * 60_000 });\n return { status: \"awaiting-paste\", url, flow };\n }\n const start = await startDeviceCode(postJson, now);\n this.attempts.set(provider, { flow: \"device-code\", start });\n this.armDevicePoller(provider, start);\n return {\n status: \"awaiting-device\",\n userCode: start.userCode,\n verificationUrl: CHATGPT_OAUTH.verificationUrl,\n expiresAt: start.expiresAt,\n };\n } catch (e) {\n const reason = e instanceof Error ? e.message : String(e);\n this.signInErrors.set(provider, reason);\n return { status: \"failed\", reason };\n }\n }\n\n /**\n * Finish a paste-based sign-in (Claude code, Google address).\n *\n * @param provider the subscription kind\n * @param message the message payload ({ value })\n * @returns the resulting state\n */\n private async submitSignIn(provider: string, message: unknown): Promise<SignInState> {\n const attempt = this.attempts.get(provider);\n const value =\n typeof (message as { value?: unknown })?.value === \"string\" ? (message as { value: string }).value.trim() : \"\";\n if (!attempt || attempt.flow === \"device-code\") {\n return { status: \"failed\", reason: \"start the sign-in first\" };\n }\n // The 15-minute window was stored but never enforced \u2014 a stale attempt used to\n // fail with the provider's own cryptic answer instead of a clear instruction.\n if (attemptExpired(attempt.expiresAt, Date.now())) {\n this.attempts.delete(provider);\n return { status: \"failed\", reason: \"the sign-in window expired \u2014 start the sign-in again\" };\n }\n if (!value) {\n return { status: \"failed\", reason: \"nothing pasted\" };\n }\n try {\n const now = Date.now();\n const tokens =\n attempt.flow === \"paste-code\"\n ? await exchangeCode(value, attempt.pkce, postJson, now)\n : await exchangeGeminiCode(extractGeminiCode(value, attempt.pkce.state), attempt.pkce, postForm, now);\n await this.finishSignIn(provider, tokens);\n return { status: \"signed-in\" };\n } catch (e) {\n const reason = e instanceof Error ? e.message : String(e);\n this.signInErrors.set(provider, reason);\n return { status: \"failed\", reason };\n }\n }\n\n /**\n * Store fresh tokens, mark the account signed in and poll it right away \u2014 waiting\n * up to a full interval after a successful sign-in reads as \"it did not work\".\n *\n * @param provider the subscription kind\n * @param tokens the token set\n */\n private async finishSignIn(provider: string, tokens: TokenSet): Promise<void> {\n await this.tokenStore(provider).save(tokens);\n this.attempts.delete(provider);\n this.signInErrors.delete(provider);\n this.stopDevicePoller(provider);\n const id = SUBSCRIPTION_IDS[provider];\n if (id) {\n // Started, not awaited: the settings page must confirm the sign-in at once,\n // and a first query that has to wait for a provider can take up to the full\n // request timeout. The values arrive a moment later on their own.\n void this.engine?.pollNow(id).catch(e => {\n this.log.debug(`First query after sign-in failed: ${e instanceof Error ? e.message : String(e)}`);\n });\n }\n this.log.info(`${SIGN_IN_LABELS[provider] ?? provider}: signed in`);\n }\n\n /**\n * Poll the device-code endpoint until the user confirmed, the window closed or\n * the adapter stops. The handle lives in memory only \u2014 a restart mid-flow just\n * means the user starts again, which is cheaper than persisting a 15-minute secret.\n *\n * @param provider the subscription kind\n * @param start the device-code handle\n */\n private armDevicePoller(provider: string, start: DeviceCodeStart): void {\n const tick = async (): Promise<void> => {\n try {\n if (attemptExpired(start.expiresAt, Date.now())) {\n this.stopDevicePoller(provider);\n this.attempts.delete(provider);\n this.signInErrors.set(provider, \"the code expired \u2014 start the sign-in again\");\n return;\n }\n const result = await pollDeviceCode(start, postJson);\n if (result.status === \"ready\") {\n this.stopDevicePoller(provider);\n const tokens = await exchangeDeviceCode(result.code, result.codeVerifier, postForm, Date.now());\n await this.finishSignIn(provider, tokens);\n }\n } catch (e) {\n this.stopDevicePoller(provider);\n this.attempts.delete(provider);\n this.signInErrors.set(provider, e instanceof Error ? e.message : String(e));\n }\n };\n this.devicePollers.set(provider, {\n kind: \"interval\",\n handle: this.setInterval(() => void tick(), Math.max(start.intervalSec, 1) * 1000),\n });\n }\n\n /**\n * Stop a running device-code poller.\n *\n * @param provider the subscription kind\n */\n private stopDevicePoller(provider: string): void {\n const handle = this.devicePollers.get(provider);\n if (handle?.kind === \"interval\") {\n this.clearInterval(handle.handle);\n }\n this.devicePollers.delete(provider);\n }\n\n /**\n * The current sign-in state of one subscription, for the admin row.\n *\n * @param provider the subscription kind\n * @returns the state\n */\n private async signInState(provider: string): Promise<SignInState> {\n const failure = this.signInErrors.get(provider);\n const attempt = this.attempts.get(provider);\n if (attempt?.flow === \"device-code\") {\n return {\n status: \"awaiting-device\",\n userCode: attempt.start.userCode,\n verificationUrl: CHATGPT_OAUTH.verificationUrl,\n expiresAt: attempt.start.expiresAt,\n };\n }\n if (attempt) {\n if (attemptExpired(attempt.expiresAt, Date.now())) {\n this.attempts.delete(provider);\n return { status: \"failed\", reason: \"the sign-in window expired \u2014 start the sign-in again\" };\n }\n return { status: \"awaiting-paste\", url: attempt.url, flow: attempt.flow };\n }\n if (failure) {\n return { status: \"failed\", reason: failure };\n }\n return (await this.tokenStore(provider).load()) ? { status: \"signed-in\" } : { status: \"signed-out\" };\n }\n\n /**\n * Forget the tokens of one subscription.\n *\n * @param provider the subscription kind\n * @returns the resulting state\n */\n private async signOut(provider: string): Promise<SignInState> {\n await this.tokenStore(provider).clear();\n this.attempts.delete(provider);\n this.signInErrors.delete(provider);\n this.stopDevicePoller(provider);\n return { status: \"signed-out\" };\n }\n\n // ------------------------------------------------------------ token files\n\n /**\n * The token store of one subscription \u2014 created ONCE per provider.\n *\n * The identity matters: the store holds the in-memory copy of the tokens, so\n * signing out really takes effect. When each provider module kept its own copy,\n * a sign-out deleted the file while the adapter kept polling with what it still\n * had \u2014 and the next token refresh wrote the deleted file back.\n *\n * @param provider the subscription kind\n * @returns the store\n */\n private tokenStore(provider: string): TokenStore {\n let store = this.tokenStores.get(provider);\n if (!store) {\n store = this.makeTokenStore(provider);\n this.tokenStores.set(provider, store);\n }\n return store;\n }\n\n /**\n * Build the store for one subscription: an encrypted file in the instance data\n * directory, named after the PROVIDER. Keying by provider (not by account name)\n * keeps a sign-in alive when an account is renamed \u2014 the previous scheme lost it.\n *\n * @param provider the subscription kind\n * @returns the store\n */\n private makeTokenStore(provider: string): TokenStore {\n const dir = utils.getAbsoluteInstanceDataDir(this);\n const file = join(dir, `tokens-${provider}.json`);\n let cached: TokenSet | null = null;\n let read = false;\n return {\n load: async (): Promise<TokenSet | null> => {\n if (!read) {\n cached = await this.readTokenFile(file, provider);\n read = true;\n }\n return cached;\n },\n save: async (tokens: TokenSet): Promise<void> => {\n await mkdir(dir, { recursive: true });\n await writeFile(file, this.encrypt(JSON.stringify(tokens)), \"utf8\");\n cached = tokens;\n read = true;\n },\n clear: async (): Promise<void> => {\n cached = null;\n read = true;\n await unlink(file).catch(() => {\n /* already gone */\n });\n },\n };\n }\n\n /**\n * Read one token file.\n *\n * A missing file means \"never signed in\" and is silent. A file that is there but\n * cannot be read \u2014 damaged, or encrypted with a different instance secret \u2014 is\n * NOT the same thing: without a word in the log, signing in would look like it\n * simply does nothing.\n *\n * @param file the file path\n * @param provider the subscription kind (for the log line)\n * @returns the tokens, or null\n */\n private async readTokenFile(file: string, provider: string): Promise<TokenSet | null> {\n let raw: string;\n try {\n raw = await readFile(file, \"utf8\");\n } catch (e) {\n if ((e as NodeJS.ErrnoException)?.code !== \"ENOENT\") {\n this.log.warn(`${SIGN_IN_LABELS[provider] ?? provider}: cannot open the stored sign-in (${String(e)})`);\n }\n return null; // never signed in \u2014 the provider reports auth-required\n }\n try {\n const parsed = JSON.parse(this.decrypt(raw)) as Partial<TokenSet>;\n if (typeof parsed.accessToken !== \"string\" || typeof parsed.refreshToken !== \"string\") {\n throw new Error(\"the file carries no tokens\");\n }\n return {\n accessToken: parsed.accessToken,\n refreshToken: parsed.refreshToken,\n expiresAt: Number(parsed.expiresAt) || 0,\n accountRef: typeof parsed.accountRef === \"string\" ? parsed.accountRef : undefined,\n };\n } catch (e) {\n this.log.warn(\n `${SIGN_IN_LABELS[provider] ?? provider}: the stored sign-in cannot be read (${\n e instanceof Error ? e.message : String(e)\n }) \u2014 sign in again in the instance settings`,\n );\n return null;\n }\n }\n\n /**\n * Carry a sign-in from the pre-0.3.0 layout over: tokens used to be stored per\n * ACCOUNT NAME (`claude-tokens-<name>.json`). Without this the rename to fixed\n * account ids would silently sign the user out.\n */\n private async migrateTokenFiles(): Promise<void> {\n const dir = utils.getAbsoluteInstanceDataDir(this);\n const target = join(dir, \"tokens-claude-sub.json\");\n try {\n await readFile(target, \"utf8\");\n return; // already migrated\n } catch {\n // not there yet \u2014 look for an old file\n }\n for (const legacy of [\"claude-tokens-Claude.json\", \"claude-tokens-claude.json\"]) {\n try {\n await rename(join(dir, legacy), target);\n this.log.info(\"Carried the existing Claude sign-in over to the new layout\");\n return;\n } catch {\n // try the next candidate\n }\n }\n }\n\n // ------------------------------------------------------------- life cycle\n\n /** Validate the configuration, clean up stale objects and start the engine. */\n /**\n * Clear a leftover `supportedMessages.stopInstance` from THIS instance's object.\n *\n * The entry lives in two places: in the adapter's manifest, and as a copy in the\n * instance object in the database. An update merges the manifest into that copy \u2014\n * it never removes a field. So on every installation that ran a version carrying\n * the entry, the host keeps killing the process outright and `onUnload` still never\n * runs: the update alone changes nothing (found by a second pair of eyes on the\n * live server 2026-08-27, after my own test had been contaminated by a value I had\n * set by hand).\n *\n * Writing the instance object makes the host restart this instance once. That is\n * the price, it happens on the first start after the update and never again \u2014\n * afterwards the condition below is false. public-holidays corrects its own run\n * mode the same way.\n */\n private async clearStopInstanceFlag(): Promise<void> {\n const id = `system.adapter.${this.namespace}`;\n try {\n const obj = await this.getForeignObjectAsync(id);\n const supported = obj?.common?.supportedMessages as { stopInstance?: unknown } | undefined;\n if (!supported?.stopInstance) {\n return;\n }\n this.log.info(\"Correcting a leftover setting from an earlier version \u2014 this instance restarts once\");\n await this.extendForeignObjectAsync(id, { common: { supportedMessages: { stopInstance: false } } });\n } catch (e) {\n this.log.debug(`Could not check the instance object: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n private async onReady(): Promise<void> {\n try {\n // First: without this the whole shutdown path stays dead on an updated install.\n await this.clearStopInstanceFlag();\n const accounts = parseAccounts(this.config.accounts);\n const interval = clampPollInterval(this.config.pollInterval);\n await this.migrateTokenFiles();\n // Baseline first: the cleanup deletes and the engine creates, both are counted\n // against this snapshot.\n await this.snapshotExistingStates();\n await this.cleanupStaleObjects();\n const retired = await this.removeRetiredStates(accounts);\n if (retired > 0) {\n this.log.info(`Object tree updated: removed ${retired} obsolete status datapoint(s)`);\n }\n if (accounts.length === 0) {\n this.log.info(\"No AI accounts configured \u2014 add accounts in the instance settings\");\n await this.setState(\"info.connection\", { val: false, ack: true });\n return;\n }\n const providers = new Map<string, UsageProvider>();\n for (const account of accounts) {\n const provider = await this.makeProvider(account);\n if (provider) {\n providers.set(account.id, provider);\n }\n }\n this.engine = new PollEngine(accounts, providers, interval, {\n upsertObject: async def => {\n await this.extendObject(def.id, { type: def.type, common: def.common as ioBroker.ObjectCommon, native: {} });\n if (def.type === \"state\" && !this.knownStateIds.has(def.id)) {\n this.knownStateIds.add(def.id);\n this.createdStates++;\n }\n },\n setState: (id, value) => {\n void this.setState(id, { val: value, ack: true }).catch(() => {\n /* states DB going down \u2014 never crash the poll loop */\n });\n },\n setStateChanged: async (id, value) => {\n // Awaited only by the shutdown path; the poll loop drops the promise.\n await this.setStateChangedAsync(id, { val: value, ack: true }).catch(() => {\n /* states DB going down \u2014 never crash the poll loop */\n });\n },\n deleteObject: async id => {\n try {\n await this.delObjectAsync(id, { recursive: true });\n if (this.knownStateIds.delete(id)) {\n this.removedStates++;\n }\n } catch (e) {\n this.log.debug(`Could not remove ${id}: ${e instanceof Error ? e.message : String(e)}`);\n }\n },\n listStateIds: async prefix => {\n const start = `${this.namespace}.${prefix}.`;\n const view = await this.getObjectViewAsync(\"system\", \"state\", {\n startkey: start,\n endkey: `${start}\uFFFF`,\n });\n return (view?.rows ?? []).map(row => row.id.substring(this.namespace.length + 1));\n },\n schedule: (cb, ms): TimerHandle => ({ kind: \"interval\", handle: this.setInterval(cb, ms) }),\n scheduleOnce: (cb, ms): TimerHandle => ({ kind: \"timeout\", handle: this.setTimeout(cb, ms) }),\n cancel: handle => {\n const timer = handle as TimerHandle;\n if (timer.kind === \"interval\") {\n this.clearInterval(timer.handle);\n } else {\n this.clearTimeout(timer.handle);\n }\n },\n now: () => Date.now(),\n log: {\n debug: m => this.log.debug(m),\n info: m => this.log.info(m),\n warn: m => this.log.warn(m),\n error: m => this.log.error(m),\n },\n afterFirstRound: () => this.logDatapointBalance(),\n notify: this.config.notifications\n ? (_account, message) =>\n void this.registerNotification(\"ai-usage\", \"userActionRequired\", message).catch(e =>\n this.log.debug(`Could not raise notification: ${e instanceof Error ? e.message : String(e)}`),\n )\n : undefined,\n });\n await this.engine.start();\n this.log.info(`Monitoring ${providers.size} of ${accounts.length} AI account(s), polling every ${interval} s`);\n } catch (e) {\n this.log.error(`Startup failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Build the provider for one account.\n *\n * @param account the validated account config\n * @returns the provider, or undefined to skip the account\n */\n private async makeProvider(account: AccountConfig): Promise<UsageProvider | undefined> {\n switch (account.provider) {\n case \"claude-sub\":\n return claudeSubProvider(this.tokenStore(account.provider), postJson);\n case \"chatgpt-sub\":\n return chatgptSubProvider(this.tokenStore(account.provider), postJson);\n case \"gemini-sub\":\n return geminiSubProvider(this.tokenStore(account.provider), postJson, postForm);\n case \"openrouter\": {\n const key = await this.resolveKey(account);\n return key ? openRouterProvider(key) : undefined;\n }\n case \"deepseek\": {\n const key = await this.resolveKey(account);\n return key ? deepSeekProvider(key) : undefined;\n }\n case \"openai\": {\n const key = await this.resolveKey(account);\n return key ? openAiProvider(key) : undefined;\n }\n case \"anthropic-api\": {\n const key = await this.resolveKey(account);\n return key ? anthropicApiProvider(key) : undefined;\n }\n default:\n return undefined;\n }\n }\n\n /**\n * Read and decrypt a key-form credential from the central credential storage.\n *\n * @param account the account whose credential to resolve\n * @returns the key, or undefined (with a log line) when it cannot be read\n */\n private async resolveKey(account: AccountConfig): Promise<string | undefined> {\n if (!account.credentialId) {\n this.log.warn(`${account.name}: no credential selected \u2014 pick one in the instance settings`);\n return undefined;\n }\n try {\n const credential = await Credentials.getCredentials(this, account.credentialId);\n const values = credential.values as { key?: unknown };\n const key = typeof values.key === \"string\" && values.key ? values.key : undefined;\n if (!key) {\n this.log.warn(`${account.name}: credential ${account.credentialId} carries no API key`);\n }\n return key;\n } catch (e) {\n this.log.warn(\n `${account.name}: cannot read credential ${account.credentialId} (${e instanceof Error ? e.message : String(e)})`,\n );\n return undefined;\n }\n }\n\n /**\n * Status states retired in 0.5.0 \u2014 `provider` (visible in the node name anyway),\n * `reachable`/`serviceOnline`/`state` (three spellings of one fact) and `signedIn`\n * (the settings page asks the adapter directly).\n *\n * ioBroker never garbage-collects an object whose id the adapter stopped writing:\n * it would sit there frozen on its last value and keep lying. So the old ids are\n * deleted from a fixed list \u2014 no state reading, no heuristics.\n */\n private static readonly RETIRED_INFO_STATES = [\n \"info.provider\",\n \"info.reachable\",\n \"info.serviceOnline\",\n \"info.state\",\n \"info.signedIn\",\n ];\n\n /**\n * Remove the retired status states of every configured account.\n *\n * Which of them still exist is answered by the startup snapshot that was read a\n * moment earlier \u2014 asking the database for every id on every start would keep\n * costing 35 lookups forever for a migration that is done after the first one.\n *\n * @param accounts the configured accounts\n * @returns how many objects were actually deleted\n */\n private async removeRetiredStates(accounts: readonly AccountConfig[]): Promise<number> {\n let removed = 0;\n for (const account of accounts) {\n for (const suffix of AiUsageAdapter.RETIRED_INFO_STATES) {\n const id = `${account.id}.${suffix}`;\n if (!this.knownStateIds.has(id)) {\n continue;\n }\n try {\n await this.delObjectAsync(id);\n removed++;\n // Deliberately NOT counted in the startup balance: this one-shot migration\n // reports its own sum, and the same deletion must not appear in two lines\n // that mean different things.\n this.knownStateIds.delete(id);\n } catch (e) {\n this.log.debug(`Could not remove ${id}: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n }\n return removed;\n }\n\n /**\n * Delete object trees that no longer belong to a configured account, plus the\n * `auth` branch of the pre-0.3.0 layout. An EMPTY table deletes nothing \u2014 the\n * guard against wiping everything through an accidental clear.\n */\n private async cleanupStaleObjects(): Promise<void> {\n const keepIds = parseAccounts(this.config.accounts).map(account => account.id);\n if (keepIds.length === 0) {\n return;\n }\n const keep = new Set([...keepIds, \"info\", \"total\"]);\n try {\n const objects = await this.getAdapterObjectsAsync();\n const roots = new Set<string>();\n for (const id of Object.keys(objects)) {\n const root = id.substring(this.namespace.length + 1).split(\".\")[0];\n if (root && !keep.has(root)) {\n roots.add(root);\n }\n }\n for (const root of roots) {\n this.log.info(\n root === \"auth\"\n ? \"Removing the old sign-in branch \u2014 the sign-in state now lives inside each account\"\n : `Removing objects of no longer configured account \"${root}\"`,\n );\n // Count the datapoints BEFORE they are gone \u2014 afterwards there is nothing to count.\n for (const id of [...this.knownStateIds]) {\n if (id === root || id.startsWith(`${root}.`)) {\n this.knownStateIds.delete(id);\n this.removedStates++;\n }\n }\n await this.delObjectAsync(root, { recursive: true });\n }\n } catch (e) {\n this.log.warn(`Cleanup of stale objects failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Tear down: cancel everything, then say that nothing is delivering any more.\n *\n * The final writes are AWAITED before `callback()` \u2014 measured on the live server\n * (2026-08-27), a fire-and-forget write followed by an immediate callback never\n * reached the database, so a switched-off instance kept showing every account as\n * online. The whole thing takes about 100 ms.\n *\n * Deliberately WITHOUT a time limit of its own: the adapter's timer API refuses to\n * arm once shutdown has begun, and the host already applies the only deadline that\n * matters \u2014 it ends the process a second after asking. A states database that\n * hangs would swallow the writes either way, so a second guard adds code, not\n * safety.\n *\n * None of this runs while `common.supportedMessages.stopInstance` sits in the\n * manifest: with it the host kills the process outright instead of asking, and\n * every state written here is dead code. A test pins that the entry stays out.\n *\n * @param callback invoked when cleanup is done\n */\n private onUnload(callback: () => void): void {\n try {\n for (const provider of [...this.devicePollers.keys()]) {\n this.stopDevicePoller(provider);\n }\n const engine = this.engine;\n this.engine?.stop();\n this.engine = null;\n void (engine?.markAllOffline() ?? this.setState(\"info.connection\", { val: false, ack: true }))\n .then(() => this.log.debug(\"Shutdown: final states written\"))\n .catch(e => this.log.debug(`Shutdown: final states rejected \u2014 ${e instanceof Error ? e.message : String(e)}`))\n .finally(callback);\n } catch {\n callback();\n }\n }\n}\n\nif (require.main !== module) {\n // Export the constructor in compact mode\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new AiUsageAdapter(options);\n} else {\n (() => new AiUsageAdapter())();\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { Credentials } from \"@iobroker/adapter-core\";\nimport { mkdir, readFile, rename, unlink, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { postForm, postJson } from \"./lib/http\";\nimport { PollEngine } from \"./lib/poll-engine\";\nimport {\n clampPollInterval,\n datapointBalanceLine,\n parseAccounts,\n SUBSCRIPTION_IDS,\n type AccountConfig,\n} from \"./lib/pure-helpers\";\nimport type { TokenSet, TokenStore, UsageProvider } from \"./lib/provider\";\nimport { SIGN_IN_FLOWS, SIGN_IN_LABELS, attemptExpired, type SignInState } from \"./lib/sign-in\";\nimport { buildAuthorizeUrl, exchangeCode, generatePkce, type PkcePair } from \"./lib/providers/claude-auth\";\nimport { claudeSubProvider } from \"./lib/providers/claude-sub\";\nimport {\n CHATGPT_OAUTH,\n exchangeDeviceCode,\n pollDeviceCode,\n startDeviceCode,\n type DeviceCodeStart,\n} from \"./lib/providers/chatgpt-auth\";\nimport { chatgptSubProvider } from \"./lib/providers/chatgpt-sub\";\nimport {\n buildGeminiAuthorizeUrl,\n exchangeGeminiCode,\n extractGeminiCode,\n generateGeminiPkce,\n type GeminiPkce,\n} from \"./lib/providers/gemini-auth\";\nimport { geminiSubProvider } from \"./lib/providers/gemini-sub\";\nimport { anthropicApiProvider } from \"./lib/providers/anthropic-api\";\nimport { deepSeekProvider } from \"./lib/providers/deepseek\";\nimport { openAiProvider } from \"./lib/providers/openai\";\nimport { openRouterProvider } from \"./lib/providers/openrouter\";\n\n/** A cancellable handle: interval or timeout \u2014 the engine treats them uniformly. */\ntype TimerHandle =\n | { kind: \"interval\"; handle: ioBroker.Interval | undefined }\n | { kind: \"timeout\"; handle: ioBroker.Timeout | undefined };\n\n/** One running sign-in attempt (secrets live in memory only, never on disk). */\ntype Attempt =\n | { flow: \"paste-code\"; pkce: PkcePair; url: string; expiresAt: number }\n | { flow: \"paste-url\"; pkce: GeminiPkce; url: string; expiresAt: number }\n | { flow: \"device-code\"; start: DeviceCodeStart };\n\n/**\n * AI Usage adapter \u2014 reads usage windows, credits and costs of AI accounts into\n * read-only states. Three subscriptions sign in with the user's own account\n * (Claude, ChatGPT, Google), the other accounts use a key from the admin's central\n * credential storage. Orchestration lives in the unit-tested {@link PollEngine};\n * this class wires ioBroker IO, the sign-in flows and the token files to it.\n */\nexport class AiUsageAdapter extends utils.Adapter {\n private engine: PollEngine | null = null;\n /** Running sign-in attempts, keyed by provider kind. */\n private readonly attempts = new Map<string, Attempt>();\n /** Last failure reason per provider, shown in the admin row. */\n private readonly signInErrors = new Map<string, string>();\n /** Device-code pollers, so they can be stopped on unload. */\n private readonly devicePollers = new Map<string, TimerHandle>();\n /** One token store per subscription \u2014 see {@link tokenStore} for why it is shared. */\n private readonly tokenStores = new Map<string, TokenStore>();\n\n /**\n * Every state id that already existed when this process started.\n *\n * The create path runs `extendObject` for every state once per process \u2014 also for\n * states that were already in the database \u2014 so \"the create path touched it\" would\n * report every datapoint as new after each restart. Only what is missing from this\n * snapshot is a real addition (beszel pattern).\n */\n private knownStateIds = new Set<string>();\n /** Datapoints created since the snapshot. */\n private createdStates = 0;\n /** Datapoints removed since the snapshot \u2014 excluding the one-shot migration, which reports itself. */\n private removedStates = 0;\n /** Whether the startup balance was already logged. */\n private balanceLogged = false;\n\n /**\n * Read every existing state id once, before anything creates or deletes.\n *\n * @returns nothing; fills {@link knownStateIds}\n */\n private async snapshotExistingStates(): Promise<void> {\n try {\n const view = await this.getObjectViewAsync(\"system\", \"state\", {\n startkey: `${this.namespace}.`,\n endkey: `${this.namespace}.\\uFFFF`,\n });\n for (const row of view?.rows ?? []) {\n this.knownStateIds.add(row.id.substring(this.namespace.length + 1));\n }\n } catch (e) {\n this.log.debug(`Could not snapshot existing states: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Report what the object tree gained and lost in this startup \u2014 one line, both\n * sides, silent when nothing changed. A normal restart must stay quiet.\n */\n private logDatapointBalance(): void {\n if (this.balanceLogged) {\n return;\n }\n this.balanceLogged = true;\n const line = datapointBalanceLine(this.createdStates, this.removedStates);\n if (line) {\n this.log.info(line);\n }\n }\n\n /**\n * @param options the adapter options\n */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"ai-usage\" });\n this.on(\"ready\", this.onReady.bind(this));\n this.on(\"message\", this.onMessage.bind(this));\n this.on(\"unload\", this.onUnload.bind(this));\n }\n\n // ---------------------------------------------------------------- sign-in\n\n /**\n * Handle admin messages: the three sign-in flows plus their status.\n *\n * @param obj the message\n */\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n try {\n const provider = this.providerFrom(obj.message);\n switch (obj.command) {\n case \"signInStart\":\n this.respond(obj, provider ? await this.startSignIn(provider) : { error: \"unknown provider\" });\n return;\n case \"signInSubmit\":\n this.respond(obj, provider ? await this.submitSignIn(provider, obj.message) : { error: \"unknown provider\" });\n return;\n case \"signInStatus\":\n this.respond(obj, provider ? await this.signInState(provider) : { error: \"unknown provider\" });\n return;\n case \"signOut\":\n this.respond(obj, provider ? await this.signOut(provider) : { error: \"unknown provider\" });\n return;\n default:\n // Always answer, or the caller's callback dangles until timeout.\n this.respond(obj, { error: `Unknown command: ${obj.command}` });\n }\n } catch (e) {\n this.log.error(`onMessage failed: ${e instanceof Error ? e.message : String(e)}`);\n this.respond(obj, { error: \"internal error \u2014 see log\" });\n }\n }\n\n /**\n * Send a message response, when the caller expects one.\n *\n * @param obj the request message\n * @param response the response payload\n */\n private respond(obj: ioBroker.Message, response: unknown): void {\n if (obj.callback) {\n this.sendTo(obj.from, obj.command, response, obj.callback);\n }\n }\n\n /**\n * The provider kind named in a message, if it is a subscription we know.\n *\n * @param message the message payload ({ provider })\n * @returns the provider kind, or undefined\n */\n private providerFrom(message: unknown): string | undefined {\n const value =\n typeof (message as { provider?: unknown })?.provider === \"string\"\n ? (message as { provider: string }).provider\n : \"\";\n return SIGN_IN_FLOWS[value] ? value : undefined;\n }\n\n /**\n * Begin a sign-in: build the link (Claude/Google) or fetch a device code (ChatGPT).\n *\n * @param provider the subscription kind\n * @returns what the admin panel has to show\n */\n private async startSignIn(provider: string): Promise<SignInState | { error: string }> {\n this.signInErrors.delete(provider);\n this.stopDevicePoller(provider);\n const flow = SIGN_IN_FLOWS[provider];\n const now = Date.now();\n try {\n if (flow === \"paste-code\") {\n const pkce = generatePkce();\n const url = buildAuthorizeUrl(pkce);\n this.attempts.set(provider, { flow, pkce, url, expiresAt: now + 15 * 60_000 });\n return { status: \"awaiting-paste\", url, flow };\n }\n if (flow === \"paste-url\") {\n const pkce = generateGeminiPkce();\n const url = buildGeminiAuthorizeUrl(pkce);\n this.attempts.set(provider, { flow, pkce, url, expiresAt: now + 15 * 60_000 });\n return { status: \"awaiting-paste\", url, flow };\n }\n const start = await startDeviceCode(postJson, now);\n this.attempts.set(provider, { flow: \"device-code\", start });\n this.armDevicePoller(provider, start);\n return {\n status: \"awaiting-device\",\n userCode: start.userCode,\n verificationUrl: CHATGPT_OAUTH.verificationUrl,\n expiresAt: start.expiresAt,\n };\n } catch (e) {\n const reason = e instanceof Error ? e.message : String(e);\n this.signInErrors.set(provider, reason);\n return { status: \"failed\", reason };\n }\n }\n\n /**\n * Finish a paste-based sign-in (Claude code, Google address).\n *\n * @param provider the subscription kind\n * @param message the message payload ({ value })\n * @returns the resulting state\n */\n private async submitSignIn(provider: string, message: unknown): Promise<SignInState> {\n const attempt = this.attempts.get(provider);\n const value =\n typeof (message as { value?: unknown })?.value === \"string\" ? (message as { value: string }).value.trim() : \"\";\n if (!attempt || attempt.flow === \"device-code\") {\n return { status: \"failed\", reason: \"start the sign-in first\" };\n }\n // The 15-minute window was stored but never enforced \u2014 a stale attempt used to\n // fail with the provider's own cryptic answer instead of a clear instruction.\n if (attemptExpired(attempt.expiresAt, Date.now())) {\n this.attempts.delete(provider);\n return { status: \"failed\", reason: \"the sign-in window expired \u2014 start the sign-in again\" };\n }\n if (!value) {\n return { status: \"failed\", reason: \"nothing pasted\" };\n }\n try {\n const now = Date.now();\n const tokens =\n attempt.flow === \"paste-code\"\n ? await exchangeCode(value, attempt.pkce, postJson, now)\n : await exchangeGeminiCode(extractGeminiCode(value, attempt.pkce.state), attempt.pkce, postForm, now);\n await this.finishSignIn(provider, tokens);\n return { status: \"signed-in\" };\n } catch (e) {\n const reason = e instanceof Error ? e.message : String(e);\n this.signInErrors.set(provider, reason);\n return { status: \"failed\", reason };\n }\n }\n\n /**\n * Store fresh tokens, mark the account signed in and poll it right away \u2014 waiting\n * up to a full interval after a successful sign-in reads as \"it did not work\".\n *\n * @param provider the subscription kind\n * @param tokens the token set\n */\n private async finishSignIn(provider: string, tokens: TokenSet): Promise<void> {\n await this.tokenStore(provider).save(tokens);\n this.attempts.delete(provider);\n this.signInErrors.delete(provider);\n this.stopDevicePoller(provider);\n const id = SUBSCRIPTION_IDS[provider];\n if (id) {\n // Started, not awaited: the settings page must confirm the sign-in at once,\n // and a first query that has to wait for a provider can take up to the full\n // request timeout. The values arrive a moment later on their own.\n void this.engine?.pollNow(id).catch(e => {\n this.log.debug(`First query after sign-in failed: ${e instanceof Error ? e.message : String(e)}`);\n });\n }\n this.log.info(`${SIGN_IN_LABELS[provider] ?? provider}: signed in`);\n }\n\n /**\n * Poll the device-code endpoint until the user confirmed, the window closed or\n * the adapter stops. The handle lives in memory only \u2014 a restart mid-flow just\n * means the user starts again, which is cheaper than persisting a 15-minute secret.\n *\n * @param provider the subscription kind\n * @param start the device-code handle\n */\n private armDevicePoller(provider: string, start: DeviceCodeStart): void {\n const tick = async (): Promise<void> => {\n try {\n if (attemptExpired(start.expiresAt, Date.now())) {\n this.stopDevicePoller(provider);\n this.attempts.delete(provider);\n this.signInErrors.set(provider, \"the code expired \u2014 start the sign-in again\");\n return;\n }\n const result = await pollDeviceCode(start, postJson);\n if (result.status === \"ready\") {\n this.stopDevicePoller(provider);\n const tokens = await exchangeDeviceCode(result.code, result.codeVerifier, postForm, Date.now());\n await this.finishSignIn(provider, tokens);\n }\n } catch (e) {\n this.stopDevicePoller(provider);\n this.attempts.delete(provider);\n this.signInErrors.set(provider, e instanceof Error ? e.message : String(e));\n }\n };\n this.devicePollers.set(provider, {\n kind: \"interval\",\n handle: this.setInterval(() => void tick(), Math.max(start.intervalSec, 1) * 1000),\n });\n }\n\n /**\n * Stop a running device-code poller.\n *\n * @param provider the subscription kind\n */\n private stopDevicePoller(provider: string): void {\n const handle = this.devicePollers.get(provider);\n if (handle?.kind === \"interval\") {\n this.clearInterval(handle.handle);\n }\n this.devicePollers.delete(provider);\n }\n\n /**\n * The current sign-in state of one subscription, for the admin row.\n *\n * @param provider the subscription kind\n * @returns the state\n */\n private async signInState(provider: string): Promise<SignInState> {\n const failure = this.signInErrors.get(provider);\n const attempt = this.attempts.get(provider);\n if (attempt?.flow === \"device-code\") {\n return {\n status: \"awaiting-device\",\n userCode: attempt.start.userCode,\n verificationUrl: CHATGPT_OAUTH.verificationUrl,\n expiresAt: attempt.start.expiresAt,\n };\n }\n if (attempt) {\n if (attemptExpired(attempt.expiresAt, Date.now())) {\n this.attempts.delete(provider);\n return { status: \"failed\", reason: \"the sign-in window expired \u2014 start the sign-in again\" };\n }\n return { status: \"awaiting-paste\", url: attempt.url, flow: attempt.flow };\n }\n if (failure) {\n return { status: \"failed\", reason: failure };\n }\n return (await this.tokenStore(provider).load()) ? { status: \"signed-in\" } : { status: \"signed-out\" };\n }\n\n /**\n * Forget the tokens of one subscription.\n *\n * @param provider the subscription kind\n * @returns the resulting state\n */\n private async signOut(provider: string): Promise<SignInState> {\n await this.tokenStore(provider).clear();\n this.attempts.delete(provider);\n this.signInErrors.delete(provider);\n this.stopDevicePoller(provider);\n return { status: \"signed-out\" };\n }\n\n // ------------------------------------------------------------ token files\n\n /**\n * The token store of one subscription \u2014 created ONCE per provider.\n *\n * The identity matters: the store holds the in-memory copy of the tokens, so\n * signing out really takes effect. When each provider module kept its own copy,\n * a sign-out deleted the file while the adapter kept polling with what it still\n * had \u2014 and the next token refresh wrote the deleted file back.\n *\n * @param provider the subscription kind\n * @returns the store\n */\n private tokenStore(provider: string): TokenStore {\n let store = this.tokenStores.get(provider);\n if (!store) {\n store = this.makeTokenStore(provider);\n this.tokenStores.set(provider, store);\n }\n return store;\n }\n\n /**\n * Build the store for one subscription: an encrypted file in the instance data\n * directory, named after the PROVIDER. Keying by provider (not by account name)\n * keeps a sign-in alive when an account is renamed \u2014 the previous scheme lost it.\n *\n * @param provider the subscription kind\n * @returns the store\n */\n private makeTokenStore(provider: string): TokenStore {\n const dir = utils.getAbsoluteInstanceDataDir(this);\n const file = join(dir, `tokens-${provider}.json`);\n let cached: TokenSet | null = null;\n let read = false;\n return {\n load: async (): Promise<TokenSet | null> => {\n if (!read) {\n cached = await this.readTokenFile(file, provider);\n read = true;\n }\n return cached;\n },\n save: async (tokens: TokenSet): Promise<void> => {\n await mkdir(dir, { recursive: true });\n await writeFile(file, this.encrypt(JSON.stringify(tokens)), \"utf8\");\n cached = tokens;\n read = true;\n },\n clear: async (): Promise<void> => {\n cached = null;\n read = true;\n await unlink(file).catch(() => {\n /* already gone */\n });\n },\n };\n }\n\n /**\n * Read one token file.\n *\n * A missing file means \"never signed in\" and is silent. A file that is there but\n * cannot be read \u2014 damaged, or encrypted with a different instance secret \u2014 is\n * NOT the same thing: without a word in the log, signing in would look like it\n * simply does nothing.\n *\n * @param file the file path\n * @param provider the subscription kind (for the log line)\n * @returns the tokens, or null\n */\n private async readTokenFile(file: string, provider: string): Promise<TokenSet | null> {\n let raw: string;\n try {\n raw = await readFile(file, \"utf8\");\n } catch (e) {\n if ((e as NodeJS.ErrnoException)?.code !== \"ENOENT\") {\n this.log.warn(`${SIGN_IN_LABELS[provider] ?? provider}: cannot open the stored sign-in (${String(e)})`);\n }\n return null; // never signed in \u2014 the provider reports auth-required\n }\n try {\n const parsed = JSON.parse(this.decrypt(raw)) as Partial<TokenSet>;\n if (typeof parsed.accessToken !== \"string\" || typeof parsed.refreshToken !== \"string\") {\n throw new Error(\"the file carries no tokens\");\n }\n return {\n accessToken: parsed.accessToken,\n refreshToken: parsed.refreshToken,\n expiresAt: Number(parsed.expiresAt) || 0,\n accountRef: typeof parsed.accountRef === \"string\" ? parsed.accountRef : undefined,\n };\n } catch (e) {\n this.log.warn(\n `${SIGN_IN_LABELS[provider] ?? provider}: the stored sign-in cannot be read (${\n e instanceof Error ? e.message : String(e)\n }) \u2014 sign in again in the instance settings`,\n );\n return null;\n }\n }\n\n /**\n * Carry a sign-in from the pre-0.3.0 layout over: tokens used to be stored per\n * ACCOUNT NAME (`claude-tokens-<name>.json`). Without this the rename to fixed\n * account ids would silently sign the user out.\n */\n private async migrateTokenFiles(): Promise<void> {\n const dir = utils.getAbsoluteInstanceDataDir(this);\n const target = join(dir, \"tokens-claude-sub.json\");\n try {\n await readFile(target, \"utf8\");\n return; // already migrated\n } catch {\n // not there yet \u2014 look for an old file\n }\n for (const legacy of [\"claude-tokens-Claude.json\", \"claude-tokens-claude.json\"]) {\n try {\n await rename(join(dir, legacy), target);\n this.log.info(\"Carried the existing Claude sign-in over to the new layout\");\n return;\n } catch {\n // try the next candidate\n }\n }\n }\n\n // ------------------------------------------------------------- life cycle\n\n /** Validate the configuration, clean up stale objects and start the engine. */\n /**\n * Clear a leftover `supportedMessages.stopInstance` from THIS instance's object.\n *\n * The entry lives in two places: in the adapter's manifest, and as a copy in the\n * instance object in the database. An update merges the manifest into that copy \u2014\n * it never removes a field. So on every installation that ran a version carrying\n * the entry, the host keeps killing the process outright and `onUnload` still never\n * runs: the update alone changes nothing (found by a second pair of eyes on the\n * live server 2026-08-27, after my own test had been contaminated by a value I had\n * set by hand).\n *\n * Writing the instance object makes the host restart this instance once. That is\n * the price, it happens on the first start after the update and never again \u2014\n * afterwards the condition below is false. public-holidays corrects its own run\n * mode the same way.\n *\n * @returns true when the correction was written and the restart is coming \u2014 the\n * caller has to stop right there. Carrying on would arm the poll timers of a\n * process the host is already shutting down, which the adapter's timer API\n * refuses with a warning in the user's log (measured on the live server).\n */\n private async clearStopInstanceFlag(): Promise<boolean> {\n const id = `system.adapter.${this.namespace}`;\n try {\n const obj = await this.getForeignObjectAsync(id);\n const supported = obj?.common?.supportedMessages as { stopInstance?: unknown } | undefined;\n if (!supported?.stopInstance) {\n return false;\n }\n this.log.info(\"Correcting a leftover setting from an earlier version \u2014 this instance restarts once\");\n await this.extendForeignObjectAsync(id, { common: { supportedMessages: { stopInstance: false } } });\n return true;\n } catch (e) {\n this.log.debug(`Could not check the instance object: ${e instanceof Error ? e.message : String(e)}`);\n return false;\n }\n }\n\n private async onReady(): Promise<void> {\n try {\n // First: without this the whole shutdown path stays dead on an updated install.\n // A correction means the host is restarting us \u2014 no point setting anything up.\n if (await this.clearStopInstanceFlag()) {\n return;\n }\n const accounts = parseAccounts(this.config.accounts);\n const interval = clampPollInterval(this.config.pollInterval);\n await this.migrateTokenFiles();\n // Baseline first: the cleanup deletes and the engine creates, both are counted\n // against this snapshot.\n await this.snapshotExistingStates();\n await this.cleanupStaleObjects();\n const retired = await this.removeRetiredStates(accounts);\n if (retired > 0) {\n this.log.info(`Object tree updated: removed ${retired} obsolete status datapoint(s)`);\n }\n if (accounts.length === 0) {\n this.log.info(\"No AI accounts configured \u2014 add accounts in the instance settings\");\n await this.setState(\"info.connection\", { val: false, ack: true });\n return;\n }\n const providers = new Map<string, UsageProvider>();\n for (const account of accounts) {\n const provider = await this.makeProvider(account);\n if (provider) {\n providers.set(account.id, provider);\n }\n }\n this.engine = new PollEngine(accounts, providers, interval, {\n upsertObject: async def => {\n await this.extendObject(def.id, { type: def.type, common: def.common as ioBroker.ObjectCommon, native: {} });\n if (def.type === \"state\" && !this.knownStateIds.has(def.id)) {\n this.knownStateIds.add(def.id);\n this.createdStates++;\n }\n },\n setState: (id, value) => {\n void this.setState(id, { val: value, ack: true }).catch(() => {\n /* states DB going down \u2014 never crash the poll loop */\n });\n },\n setStateChanged: async (id, value) => {\n // Awaited only by the shutdown path; the poll loop drops the promise.\n await this.setStateChangedAsync(id, { val: value, ack: true }).catch(() => {\n /* states DB going down \u2014 never crash the poll loop */\n });\n },\n deleteObject: async id => {\n try {\n await this.delObjectAsync(id, { recursive: true });\n if (this.knownStateIds.delete(id)) {\n this.removedStates++;\n }\n } catch (e) {\n this.log.debug(`Could not remove ${id}: ${e instanceof Error ? e.message : String(e)}`);\n }\n },\n listStateIds: async prefix => {\n const start = `${this.namespace}.${prefix}.`;\n const view = await this.getObjectViewAsync(\"system\", \"state\", {\n startkey: start,\n endkey: `${start}\uFFFF`,\n });\n return (view?.rows ?? []).map(row => row.id.substring(this.namespace.length + 1));\n },\n schedule: (cb, ms): TimerHandle => ({ kind: \"interval\", handle: this.setInterval(cb, ms) }),\n scheduleOnce: (cb, ms): TimerHandle => ({ kind: \"timeout\", handle: this.setTimeout(cb, ms) }),\n cancel: handle => {\n const timer = handle as TimerHandle;\n if (timer.kind === \"interval\") {\n this.clearInterval(timer.handle);\n } else {\n this.clearTimeout(timer.handle);\n }\n },\n now: () => Date.now(),\n log: {\n debug: m => this.log.debug(m),\n info: m => this.log.info(m),\n warn: m => this.log.warn(m),\n error: m => this.log.error(m),\n },\n afterFirstRound: () => this.logDatapointBalance(),\n notify: this.config.notifications\n ? (_account, message) =>\n void this.registerNotification(\"ai-usage\", \"userActionRequired\", message).catch(e =>\n this.log.debug(`Could not raise notification: ${e instanceof Error ? e.message : String(e)}`),\n )\n : undefined,\n });\n await this.engine.start();\n this.log.info(`Monitoring ${providers.size} of ${accounts.length} AI account(s), polling every ${interval} s`);\n } catch (e) {\n this.log.error(`Startup failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Build the provider for one account.\n *\n * @param account the validated account config\n * @returns the provider, or undefined to skip the account\n */\n private async makeProvider(account: AccountConfig): Promise<UsageProvider | undefined> {\n switch (account.provider) {\n case \"claude-sub\":\n return claudeSubProvider(this.tokenStore(account.provider), postJson);\n case \"chatgpt-sub\":\n return chatgptSubProvider(this.tokenStore(account.provider), postJson);\n case \"gemini-sub\":\n return geminiSubProvider(this.tokenStore(account.provider), postJson, postForm);\n case \"openrouter\": {\n const key = await this.resolveKey(account);\n return key ? openRouterProvider(key) : undefined;\n }\n case \"deepseek\": {\n const key = await this.resolveKey(account);\n return key ? deepSeekProvider(key) : undefined;\n }\n case \"openai\": {\n const key = await this.resolveKey(account);\n return key ? openAiProvider(key) : undefined;\n }\n case \"anthropic-api\": {\n const key = await this.resolveKey(account);\n return key ? anthropicApiProvider(key) : undefined;\n }\n default:\n return undefined;\n }\n }\n\n /**\n * Read and decrypt a key-form credential from the central credential storage.\n *\n * @param account the account whose credential to resolve\n * @returns the key, or undefined (with a log line) when it cannot be read\n */\n private async resolveKey(account: AccountConfig): Promise<string | undefined> {\n if (!account.credentialId) {\n this.log.warn(`${account.name}: no credential selected \u2014 pick one in the instance settings`);\n return undefined;\n }\n try {\n const credential = await Credentials.getCredentials(this, account.credentialId);\n const values = credential.values as { key?: unknown };\n const key = typeof values.key === \"string\" && values.key ? values.key : undefined;\n if (!key) {\n this.log.warn(`${account.name}: credential ${account.credentialId} carries no API key`);\n }\n return key;\n } catch (e) {\n this.log.warn(\n `${account.name}: cannot read credential ${account.credentialId} (${e instanceof Error ? e.message : String(e)})`,\n );\n return undefined;\n }\n }\n\n /**\n * Status states retired in 0.5.0 \u2014 `provider` (visible in the node name anyway),\n * `reachable`/`serviceOnline`/`state` (three spellings of one fact) and `signedIn`\n * (the settings page asks the adapter directly).\n *\n * ioBroker never garbage-collects an object whose id the adapter stopped writing:\n * it would sit there frozen on its last value and keep lying. So the old ids are\n * deleted from a fixed list \u2014 no state reading, no heuristics.\n */\n private static readonly RETIRED_INFO_STATES = [\n \"info.provider\",\n \"info.reachable\",\n \"info.serviceOnline\",\n \"info.state\",\n \"info.signedIn\",\n ];\n\n /**\n * Remove the retired status states of every configured account.\n *\n * Which of them still exist is answered by the startup snapshot that was read a\n * moment earlier \u2014 asking the database for every id on every start would keep\n * costing 35 lookups forever for a migration that is done after the first one.\n *\n * @param accounts the configured accounts\n * @returns how many objects were actually deleted\n */\n private async removeRetiredStates(accounts: readonly AccountConfig[]): Promise<number> {\n let removed = 0;\n for (const account of accounts) {\n for (const suffix of AiUsageAdapter.RETIRED_INFO_STATES) {\n const id = `${account.id}.${suffix}`;\n if (!this.knownStateIds.has(id)) {\n continue;\n }\n try {\n await this.delObjectAsync(id);\n removed++;\n // Deliberately NOT counted in the startup balance: this one-shot migration\n // reports its own sum, and the same deletion must not appear in two lines\n // that mean different things.\n this.knownStateIds.delete(id);\n } catch (e) {\n this.log.debug(`Could not remove ${id}: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n }\n return removed;\n }\n\n /**\n * Delete object trees that no longer belong to a configured account, plus the\n * `auth` branch of the pre-0.3.0 layout. An EMPTY table deletes nothing \u2014 the\n * guard against wiping everything through an accidental clear.\n */\n private async cleanupStaleObjects(): Promise<void> {\n const keepIds = parseAccounts(this.config.accounts).map(account => account.id);\n if (keepIds.length === 0) {\n return;\n }\n const keep = new Set([...keepIds, \"info\", \"total\"]);\n try {\n const objects = await this.getAdapterObjectsAsync();\n const roots = new Set<string>();\n for (const id of Object.keys(objects)) {\n const root = id.substring(this.namespace.length + 1).split(\".\")[0];\n if (root && !keep.has(root)) {\n roots.add(root);\n }\n }\n for (const root of roots) {\n this.log.info(\n root === \"auth\"\n ? \"Removing the old sign-in branch \u2014 the sign-in state now lives inside each account\"\n : `Removing objects of no longer configured account \"${root}\"`,\n );\n // Count the datapoints BEFORE they are gone \u2014 afterwards there is nothing to count.\n for (const id of [...this.knownStateIds]) {\n if (id === root || id.startsWith(`${root}.`)) {\n this.knownStateIds.delete(id);\n this.removedStates++;\n }\n }\n await this.delObjectAsync(root, { recursive: true });\n }\n } catch (e) {\n this.log.warn(`Cleanup of stale objects failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Tear down: cancel everything, then say that nothing is delivering any more.\n *\n * The final writes are AWAITED before `callback()` \u2014 measured on the live server\n * (2026-08-27), a fire-and-forget write followed by an immediate callback never\n * reached the database, so a switched-off instance kept showing every account as\n * online. The whole thing takes about 100 ms.\n *\n * Deliberately WITHOUT a time limit of its own: the adapter's timer API refuses to\n * arm once shutdown has begun, and the host already applies the only deadline that\n * matters \u2014 it ends the process a second after asking. A states database that\n * hangs would swallow the writes either way, so a second guard adds code, not\n * safety.\n *\n * None of this runs while `common.supportedMessages.stopInstance` sits in the\n * manifest: with it the host kills the process outright instead of asking, and\n * every state written here is dead code. A test pins that the entry stays out.\n *\n * @param callback invoked when cleanup is done\n */\n private onUnload(callback: () => void): void {\n try {\n for (const provider of [...this.devicePollers.keys()]) {\n this.stopDevicePoller(provider);\n }\n const engine = this.engine;\n this.engine?.stop();\n this.engine = null;\n void (engine?.markAllOffline() ?? this.setState(\"info.connection\", { val: false, ack: true }))\n .then(() => this.log.debug(\"Shutdown: final states written\"))\n .catch(e => this.log.debug(`Shutdown: final states rejected \u2014 ${e instanceof Error ? e.message : String(e)}`))\n .finally(callback);\n } catch {\n callback();\n }\n }\n}\n\nif (require.main !== module) {\n // Export the constructor in compact mode\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new AiUsageAdapter(options);\n} else {\n (() => new AiUsageAdapter())();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "ai-usage",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.3",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.9.3": {
|
|
7
|
+
"en": "The first start after updating no longer leaves a warning in the log",
|
|
8
|
+
"de": "Der erste Start nach einem Update hinterlässt keine Warnung mehr im Protokoll",
|
|
9
|
+
"ru": "Первый запуск после обновления больше не оставляет предупреждение в журнале",
|
|
10
|
+
"pt": "O primeiro arranque após a atualização deixa de deixar um aviso no registo",
|
|
11
|
+
"nl": "De eerste start na een update laat geen waarschuwing meer achter in het logboek",
|
|
12
|
+
"fr": "Le premier démarrage après une mise à jour ne laisse plus d'avertissement dans le journal",
|
|
13
|
+
"it": "Il primo avvio dopo un aggiornamento non lascia più un avviso nel log",
|
|
14
|
+
"es": "El primer inicio tras la actualización ya no deja un aviso en el registro",
|
|
15
|
+
"pl": "Pierwszy start po aktualizacji nie zostawia już ostrzeżenia w dzienniku",
|
|
16
|
+
"uk": "Перший запуск після оновлення більше не залишає попередження в журналі",
|
|
17
|
+
"zh-cn": "更新后的首次启动不再在日志中留下警告"
|
|
18
|
+
},
|
|
6
19
|
"0.9.2": {
|
|
7
20
|
"en": "Stopping the instance now marks the accounts as offline on updated installations too, not only on fresh ones — before, they kept showing as online",
|
|
8
21
|
"de": "Das Stoppen der Instanz kennzeichnet die Konten jetzt auch auf aktualisierten Installationen als offline, nicht nur auf frischen — vorher blieben sie auf online stehen",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Po zmianie dziennik podaje w jednej linii, ile punktów danych przybyło i ubyło — bez przeglądania drzewa obiektów",
|
|
81
94
|
"uk": "Після зміни журнал одним рядком повідомляє, скільки точок даних додалося та зникло — без перегляду дерева об'єктів",
|
|
82
95
|
"zh-cn": "更改后,日志用一行说明对象树新增和移除了多少数据点,无需逐层点开查看"
|
|
83
|
-
},
|
|
84
|
-
"0.6.0": {
|
|
85
|
-
"en": "Every account now carries the connection icon in the object tree — green while it delivers, struck through when it does not, like any other ioBroker device",
|
|
86
|
-
"de": "Jedes Konto trägt jetzt das Verbindungs-Symbol im Objektbaum — grün solange es liefert, durchgestrichen wenn nicht, wie bei jedem anderen ioBroker-Gerät",
|
|
87
|
-
"ru": "Каждый аккаунт теперь показывает значок соединения в дереве объектов — зелёный, пока данные идут, перечёркнутый, когда нет, как у любого устройства ioBroker",
|
|
88
|
-
"pt": "Cada conta mostra agora o ícone de ligação na árvore de objetos — verde enquanto entrega dados, cortado quando não, como qualquer dispositivo ioBroker",
|
|
89
|
-
"nl": "Elk account toont nu het verbindingspictogram in de objectboom — groen zolang er data komt, doorgestreept als dat niet zo is, net als elk ioBroker-apparaat",
|
|
90
|
-
"fr": "Chaque compte affiche désormais l'icône de connexion dans l'arbre des objets — verte tant qu'il fournit des données, barrée sinon, comme tout appareil ioBroker",
|
|
91
|
-
"it": "Ogni account mostra ora l'icona di connessione nell'albero degli oggetti — verde finché fornisce dati, barrata quando no, come qualsiasi dispositivo ioBroker",
|
|
92
|
-
"es": "Cada cuenta muestra ahora el icono de conexión en el árbol de objetos — verde mientras entrega datos, tachado cuando no, como cualquier dispositivo de ioBroker",
|
|
93
|
-
"pl": "Każde konto pokazuje teraz ikonę połączenia w drzewie obiektów — zieloną, dopóki dostarcza dane, przekreśloną gdy nie, jak każde urządzenie ioBroker",
|
|
94
|
-
"uk": "Кожен акаунт тепер має піктограму з’єднання в дереві об’єктів — зелену, поки дані надходять, перекреслену, коли ні, як у будь-якого пристрою ioBroker",
|
|
95
|
-
"zh-cn": "每个账户现在都会在对象树中显示连接图标——有数据时为绿色,无数据时带斜杠,与其他 ioBroker 设备一致"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"plugins": {
|