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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/admin/ai-usage.svg +19 -0
  4. package/admin/i18n/de.json +29 -0
  5. package/admin/i18n/en.json +29 -0
  6. package/admin/i18n/es.json +29 -0
  7. package/admin/i18n/fr.json +29 -0
  8. package/admin/i18n/it.json +29 -0
  9. package/admin/i18n/nl.json +29 -0
  10. package/admin/i18n/pl.json +29 -0
  11. package/admin/i18n/pt.json +29 -0
  12. package/admin/i18n/ru.json +29 -0
  13. package/admin/i18n/uk.json +29 -0
  14. package/admin/i18n/zh-cn.json +29 -0
  15. package/admin/jsonConfig.json +252 -0
  16. package/build/lib/http.js +81 -0
  17. package/build/lib/http.js.map +7 -0
  18. package/build/lib/poll-engine.js +341 -0
  19. package/build/lib/poll-engine.js.map +7 -0
  20. package/build/lib/provider.js +40 -0
  21. package/build/lib/provider.js.map +7 -0
  22. package/build/lib/providers/anthropic-api.js +120 -0
  23. package/build/lib/providers/anthropic-api.js.map +7 -0
  24. package/build/lib/providers/claude-auth.js +109 -0
  25. package/build/lib/providers/claude-auth.js.map +7 -0
  26. package/build/lib/providers/claude-sub.js +165 -0
  27. package/build/lib/providers/claude-sub.js.map +7 -0
  28. package/build/lib/providers/deepseek.js +67 -0
  29. package/build/lib/providers/deepseek.js.map +7 -0
  30. package/build/lib/providers/openai.js +136 -0
  31. package/build/lib/providers/openai.js.map +7 -0
  32. package/build/lib/providers/openrouter.js +71 -0
  33. package/build/lib/providers/openrouter.js.map +7 -0
  34. package/build/lib/providers/report-utils.js +59 -0
  35. package/build/lib/providers/report-utils.js.map +7 -0
  36. package/build/lib/pure-helpers.js +96 -0
  37. package/build/lib/pure-helpers.js.map +7 -0
  38. package/build/lib/snapshot-tree.js +175 -0
  39. package/build/lib/snapshot-tree.js.map +7 -0
  40. package/build/lib/totals.js +79 -0
  41. package/build/lib/totals.js.map +7 -0
  42. package/build/main.js +336 -0
  43. package/build/main.js.map +7 -0
  44. package/io-package.json +222 -0
  45. package/package.json +91 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/providers/deepseek.ts"],
4
+ "sourcesContent": ["import { getJson, type JsonFetch } from \"../http\";\nimport { FetchError, type UsageProvider, type UsageSnapshot } from \"../provider\";\n\n/**\n * Parse a DeepSeek `GET /user/balance` response into a snapshot. Capture-verified\n * shape: `{ is_available, balance_infos: [{ currency, total_balance,\n * granted_balance, topped_up_balance }] }` \u2014 the amounts arrive as strings.\n * With several currency entries the first is used (the rest is ignored).\n *\n * @param body the response body\n * @returns the snapshot\n */\nexport function parseDeepSeekBalance(body: unknown): UsageSnapshot {\n const obj = body as { is_available?: unknown; balance_infos?: unknown } | null;\n if (typeof obj !== \"object\" || obj === null || !Array.isArray(obj.balance_infos)) {\n throw new FetchError(\"network\", \"unexpected response shape (no balance_infos)\");\n }\n const first = obj.balance_infos.find(entry => typeof entry === \"object\" && entry !== null) as\n Record<string, unknown> | undefined;\n const snapshot: UsageSnapshot = {};\n if (typeof obj.is_available === \"boolean\") {\n snapshot.available = obj.is_available;\n }\n if (first) {\n snapshot.credits = {\n remaining: parseAmount(first.total_balance),\n granted: parseAmount(first.granted_balance),\n toppedUp: parseAmount(first.topped_up_balance),\n currency: typeof first.currency === \"string\" ? first.currency : \"USD\",\n };\n }\n return snapshot;\n}\n\n/**\n * The DeepSeek provider: reads the account balance.\n *\n * @param apiKey the DeepSeek API key\n * @param fetchJson the JSON-GET seam (tests inject a fake)\n * @returns the provider\n */\nexport function deepSeekProvider(apiKey: string, fetchJson: JsonFetch = getJson): UsageProvider {\n return {\n kind: \"deepseek\",\n fetch: async (): Promise<UsageSnapshot> =>\n parseDeepSeekBalance(\n await fetchJson(\"https://api.deepseek.com/user/balance\", {\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n }),\n ),\n };\n}\n\n/**\n * Parse a string/number amount to a finite number, or undefined.\n *\n * @param value the raw value (\"110.00\")\n * @returns the number or undefined\n */\nfunction parseAmount(value: unknown): number | undefined {\n const num = Number(value);\n return value !== null && value !== undefined && value !== \"\" && Number.isFinite(num) ? num : undefined;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAwC;AACxC,sBAAmE;AAW5D,SAAS,qBAAqB,MAA8B;AACjE,QAAM,MAAM;AACZ,MAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,CAAC,MAAM,QAAQ,IAAI,aAAa,GAAG;AAChF,UAAM,IAAI,2BAAW,WAAW,8CAA8C;AAAA,EAChF;AACA,QAAM,QAAQ,IAAI,cAAc,KAAK,WAAS,OAAO,UAAU,YAAY,UAAU,IAAI;AAEzF,QAAM,WAA0B,CAAC;AACjC,MAAI,OAAO,IAAI,iBAAiB,WAAW;AACzC,aAAS,YAAY,IAAI;AAAA,EAC3B;AACA,MAAI,OAAO;AACT,aAAS,UAAU;AAAA,MACjB,WAAW,YAAY,MAAM,aAAa;AAAA,MAC1C,SAAS,YAAY,MAAM,eAAe;AAAA,MAC1C,UAAU,YAAY,MAAM,iBAAiB;AAAA,MAC7C,UAAU,OAAO,MAAM,aAAa,WAAW,MAAM,WAAW;AAAA,IAClE;AAAA,EACF;AACA,SAAO;AACT;AASO,SAAS,iBAAiB,QAAgB,YAAuB,qBAAwB;AAC9F,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,YACL;AAAA,MACE,MAAM,UAAU,yCAAyC;AAAA,QACvD,eAAe,UAAU,MAAM;AAAA,QAC/B,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACJ;AACF;AAQA,SAAS,YAAY,OAAoC;AACvD,QAAM,MAAM,OAAO,KAAK;AACxB,SAAO,UAAU,QAAQ,UAAU,UAAa,UAAU,MAAM,OAAO,SAAS,GAAG,IAAI,MAAM;AAC/F;",
6
+ "names": []
7
+ }
@@ -0,0 +1,136 @@
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 openai_exports = {};
20
+ __export(openai_exports, {
21
+ openAiProvider: () => openAiProvider,
22
+ parseOpenAiReports: () => parseOpenAiReports
23
+ });
24
+ module.exports = __toCommonJS(openai_exports);
25
+ var import_http = require("../http");
26
+ var import_report_utils = require("./report-utils");
27
+ const BASE = "https://api.openai.com/v1/organization";
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 parseOpenAiReports(usageBuckets, costBuckets, nowMs) {
44
+ var _a;
45
+ let costMonth = 0;
46
+ let costToday = 0;
47
+ let currency = "USD";
48
+ for (const bucket of costBuckets) {
49
+ const entry = bucket;
50
+ if (!Array.isArray(entry == null ? void 0 : entry.results)) {
51
+ continue;
52
+ }
53
+ let sum = 0;
54
+ for (const result of entry.results) {
55
+ const amount = result == null ? void 0 : result.amount;
56
+ const value = Number(amount == null ? void 0 : amount.value);
57
+ if (Number.isFinite(value)) {
58
+ sum += value;
59
+ }
60
+ if (typeof (amount == null ? void 0 : amount.currency) === "string" && amount.currency) {
61
+ currency = amount.currency.toUpperCase();
62
+ }
63
+ }
64
+ costMonth += sum;
65
+ if ((0, import_report_utils.isToday)(entry.start_time, nowMs)) {
66
+ costToday += sum;
67
+ }
68
+ }
69
+ let inputToday = 0;
70
+ let outputToday = 0;
71
+ const perModel = /* @__PURE__ */ new Map();
72
+ let sawUsageToday = false;
73
+ for (const bucket of usageBuckets) {
74
+ const entry = bucket;
75
+ if (!Array.isArray(entry == null ? void 0 : entry.results) || !(0, import_report_utils.isToday)(entry.start_time, nowMs)) {
76
+ continue;
77
+ }
78
+ sawUsageToday = true;
79
+ for (const result of entry.results) {
80
+ const data = result;
81
+ const input = Number(data.input_tokens);
82
+ const output = Number(data.output_tokens);
83
+ if (Number.isFinite(input)) {
84
+ inputToday += input;
85
+ }
86
+ if (Number.isFinite(output)) {
87
+ outputToday += output;
88
+ }
89
+ if (typeof data.model === "string" && data.model) {
90
+ const tokens = (Number.isFinite(input) ? input : 0) + (Number.isFinite(output) ? output : 0);
91
+ const existing = (_a = perModel.get(data.model)) != null ? _a : { tokens: 0 };
92
+ existing.tokens += tokens;
93
+ perModel.set(data.model, existing);
94
+ }
95
+ }
96
+ }
97
+ const round = (value) => Math.round(value * 100) / 100;
98
+ const snapshot = {
99
+ costs: {
100
+ today: round(costToday),
101
+ month: round(costMonth),
102
+ projectedMonth: (0, import_report_utils.projectMonth)(costMonth, nowMs),
103
+ currency
104
+ }
105
+ };
106
+ if (sawUsageToday) {
107
+ snapshot.tokens = {
108
+ inputToday,
109
+ outputToday,
110
+ perModel: [...perModel.entries()].map(([model, data]) => ({ model, tokens: data.tokens }))
111
+ };
112
+ }
113
+ return snapshot;
114
+ }
115
+ function openAiProvider(adminKey, fetchJson = import_http.getJson, now = Date.now) {
116
+ return {
117
+ kind: "openai",
118
+ fetch: async () => {
119
+ const headers = { Authorization: `Bearer ${adminKey}` };
120
+ const start = (0, import_report_utils.monthStartUnix)(now());
121
+ const usage = await fetchAllPages(
122
+ `${BASE}/usage/completions?start_time=${start}&bucket_width=1d&limit=31&group_by=model`,
123
+ headers,
124
+ fetchJson
125
+ );
126
+ const costs = await fetchAllPages(`${BASE}/costs?start_time=${start}&limit=31`, headers, fetchJson);
127
+ return parseOpenAiReports(usage, costs, now());
128
+ }
129
+ };
130
+ }
131
+ // Annotate the CommonJS export names for ESM import in node:
132
+ 0 && (module.exports = {
133
+ openAiProvider,
134
+ parseOpenAiReports
135
+ });
136
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/providers/openai.ts"],
4
+ "sourcesContent": ["import { getJson, type JsonFetch } from \"../http\";\nimport type { UsageProvider, UsageSnapshot } from \"../provider\";\nimport { isToday, monthStartUnix, projectMonth } from \"./report-utils\";\n\n/**\n * OpenAI organization Usage + Costs API (official; needs an ADMIN key, not a normal\n * API key \u2014 platform.openai.com/docs/api-reference/usage). Daily buckets since the\n * start of the month: `data[]` entries with `start_time` (unix) and `results[]`\n * ({ input_tokens, output_tokens, model } for usage; { amount: { value, currency } }\n * for costs). Pagination via `has_more`/`next_page`.\n */\nconst BASE = \"https://api.openai.com/v1/organization\";\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 // The month yields at most 31 daily buckets \u2014 the loop is bounded by has_more.\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 tokens with the per-model split.\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 parseOpenAiReports(usageBuckets: unknown[], costBuckets: unknown[], nowMs: number): UsageSnapshot {\n let costMonth = 0;\n let costToday = 0;\n let currency = \"USD\";\n for (const bucket of costBuckets) {\n const entry = bucket as { 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 = (result as { amount?: { value?: unknown; currency?: unknown } })?.amount;\n const value = Number(amount?.value);\n if (Number.isFinite(value)) {\n sum += value;\n }\n if (typeof amount?.currency === \"string\" && amount.currency) {\n currency = amount.currency.toUpperCase();\n }\n }\n costMonth += sum;\n if (isToday(entry.start_time, nowMs)) {\n costToday += sum;\n }\n }\n\n let inputToday = 0;\n let outputToday = 0;\n const perModel = new Map<string, { tokens: number }>();\n let sawUsageToday = false;\n for (const bucket of usageBuckets) {\n const entry = bucket as { start_time?: unknown; results?: unknown };\n if (!Array.isArray(entry?.results) || !isToday(entry.start_time, nowMs)) {\n continue;\n }\n sawUsageToday = true;\n for (const result of entry.results) {\n const data = result as { input_tokens?: unknown; output_tokens?: unknown; model?: unknown };\n const input = Number(data.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 if (typeof data.model === \"string\" && data.model) {\n const tokens = (Number.isFinite(input) ? input : 0) + (Number.isFinite(output) ? output : 0);\n const existing = perModel.get(data.model) ?? { tokens: 0 };\n existing.tokens += tokens;\n perModel.set(data.model, existing);\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,\n },\n };\n if (sawUsageToday) {\n snapshot.tokens = {\n inputToday,\n outputToday,\n perModel: [...perModel.entries()].map(([model, data]) => ({ model, tokens: data.tokens })),\n };\n }\n return snapshot;\n}\n\n/**\n * The OpenAI API provider.\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 openAiProvider(\n adminKey: string,\n fetchJson: JsonFetch = getJson,\n now: () => number = Date.now,\n): UsageProvider {\n return {\n kind: \"openai\",\n fetch: async (): Promise<UsageSnapshot> => {\n const headers = { Authorization: `Bearer ${adminKey}` };\n const start = monthStartUnix(now());\n const usage = await fetchAllPages(\n `${BASE}/usage/completions?start_time=${start}&bucket_width=1d&limit=31&group_by=model`,\n headers,\n fetchJson,\n );\n const costs = await fetchAllPages(`${BASE}/costs?start_time=${start}&limit=31`, headers, fetchJson);\n return parseOpenAiReports(usage, costs, now());\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,71 @@
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 openrouter_exports = {};
20
+ __export(openrouter_exports, {
21
+ openRouterProvider: () => openRouterProvider,
22
+ parseOpenRouterKeyInfo: () => parseOpenRouterKeyInfo
23
+ });
24
+ module.exports = __toCommonJS(openrouter_exports);
25
+ var import_http = require("../http");
26
+ var import_provider = require("../provider");
27
+ function parseOpenRouterKeyInfo(body) {
28
+ var _a, _b, _c;
29
+ const data = body == null ? void 0 : body.data;
30
+ if (typeof data !== "object" || data === null) {
31
+ throw new import_provider.FetchError("network", "unexpected response shape (no data object)");
32
+ }
33
+ const info = data;
34
+ const used = numberOrUndefined((_a = info.usage) != null ? _a : info.credits_used);
35
+ const limit = numberOrUndefined((_b = info.limit) != null ? _b : info.credit_limit);
36
+ const remaining = (_c = numberOrUndefined(info.limit_remaining)) != null ? _c : used !== void 0 && limit !== void 0 ? round2(limit - used) : void 0;
37
+ const snapshot = {
38
+ credits: {
39
+ used,
40
+ limit,
41
+ remaining,
42
+ percent: used !== void 0 && limit !== void 0 && limit > 0 ? round2(used / limit * 100) : void 0,
43
+ currency: "USD"
44
+ }
45
+ };
46
+ if (used !== void 0) {
47
+ snapshot.costs = { total: used, currency: "USD" };
48
+ }
49
+ return snapshot;
50
+ }
51
+ function openRouterProvider(apiKey, fetchJson = import_http.getJson) {
52
+ return {
53
+ kind: "openrouter",
54
+ fetch: async () => parseOpenRouterKeyInfo(
55
+ await fetchJson("https://openrouter.ai/api/v1/auth/key", { Authorization: `Bearer ${apiKey}` })
56
+ )
57
+ };
58
+ }
59
+ function numberOrUndefined(value) {
60
+ const num = Number(value);
61
+ return value !== null && value !== void 0 && value !== "" && Number.isFinite(num) ? num : void 0;
62
+ }
63
+ function round2(value) {
64
+ return Math.round(value * 100) / 100;
65
+ }
66
+ // Annotate the CommonJS export names for ESM import in node:
67
+ 0 && (module.exports = {
68
+ openRouterProvider,
69
+ parseOpenRouterKeyInfo
70
+ });
71
+ //# sourceMappingURL=openrouter.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/providers/openrouter.ts"],
4
+ "sourcesContent": ["import { getJson, type JsonFetch } from \"../http\";\nimport { FetchError, type UsageProvider, type UsageSnapshot } from \"../provider\";\n\n/**\n * Parse an OpenRouter `GET /api/v1/auth/key` response into a snapshot. The key info\n * carries lifetime usage in dollars and an optional credit ceiling. Field names are\n * read defensively (`usage`/`limit` per the API reference, `credits_used`/\n * `credit_limit` as seen in community captures).\n *\n * @param body the response body\n * @returns the snapshot\n */\nexport function parseOpenRouterKeyInfo(body: unknown): UsageSnapshot {\n const data = (body as { data?: unknown } | null)?.data;\n if (typeof data !== \"object\" || data === null) {\n throw new FetchError(\"network\", \"unexpected response shape (no data object)\");\n }\n const info = data as Record<string, unknown>;\n const used = numberOrUndefined(info.usage ?? info.credits_used);\n const limit = numberOrUndefined(info.limit ?? info.credit_limit);\n const remaining =\n numberOrUndefined(info.limit_remaining) ??\n (used !== undefined && limit !== undefined ? round2(limit - used) : undefined);\n const snapshot: UsageSnapshot = {\n credits: {\n used,\n limit,\n remaining,\n percent: used !== undefined && limit !== undefined && limit > 0 ? round2((used / limit) * 100) : undefined,\n currency: \"USD\",\n },\n };\n if (used !== undefined) {\n snapshot.costs = { total: used, currency: \"USD\" };\n }\n return snapshot;\n}\n\n/**\n * The OpenRouter provider: reads the key info (credits used/limit/remaining).\n *\n * @param apiKey the OpenRouter API key\n * @param fetchJson the JSON-GET seam (tests inject a fake)\n * @returns the provider\n */\nexport function openRouterProvider(apiKey: string, fetchJson: JsonFetch = getJson): UsageProvider {\n return {\n kind: \"openrouter\",\n fetch: async (): Promise<UsageSnapshot> =>\n parseOpenRouterKeyInfo(\n await fetchJson(\"https://openrouter.ai/api/v1/auth/key\", { Authorization: `Bearer ${apiKey}` }),\n ),\n };\n}\n\n/**\n * A finite number, or undefined for anything else (null = unlimited stays undefined).\n *\n * @param value the raw value\n * @returns the number or undefined\n */\nfunction numberOrUndefined(value: unknown): number | undefined {\n const num = Number(value);\n return value !== null && value !== undefined && value !== \"\" && Number.isFinite(num) ? num : undefined;\n}\n\n/**\n * Round to two decimals.\n *\n * @param value the value\n * @returns the rounded value\n */\nfunction round2(value: number): number {\n return Math.round(value * 100) / 100;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAwC;AACxC,sBAAmE;AAW5D,SAAS,uBAAuB,MAA8B;AAZrE;AAaE,QAAM,OAAQ,6BAAoC;AAClD,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,UAAM,IAAI,2BAAW,WAAW,4CAA4C;AAAA,EAC9E;AACA,QAAM,OAAO;AACb,QAAM,OAAO,mBAAkB,UAAK,UAAL,YAAc,KAAK,YAAY;AAC9D,QAAM,QAAQ,mBAAkB,UAAK,UAAL,YAAc,KAAK,YAAY;AAC/D,QAAM,aACJ,uBAAkB,KAAK,eAAe,MAAtC,YACC,SAAS,UAAa,UAAU,SAAY,OAAO,QAAQ,IAAI,IAAI;AACtE,QAAM,WAA0B;AAAA,IAC9B,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,SAAS,UAAa,UAAU,UAAa,QAAQ,IAAI,OAAQ,OAAO,QAAS,GAAG,IAAI;AAAA,MACjG,UAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,SAAS,QAAW;AACtB,aAAS,QAAQ,EAAE,OAAO,MAAM,UAAU,MAAM;AAAA,EAClD;AACA,SAAO;AACT;AASO,SAAS,mBAAmB,QAAgB,YAAuB,qBAAwB;AAChG,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,YACL;AAAA,MACE,MAAM,UAAU,yCAAyC,EAAE,eAAe,UAAU,MAAM,GAAG,CAAC;AAAA,IAChG;AAAA,EACJ;AACF;AAQA,SAAS,kBAAkB,OAAoC;AAC7D,QAAM,MAAM,OAAO,KAAK;AACxB,SAAO,UAAU,QAAQ,UAAU,UAAa,UAAU,MAAM,OAAO,SAAS,GAAG,IAAI,MAAM;AAC/F;AAQA,SAAS,OAAO,OAAuB;AACrC,SAAO,KAAK,MAAM,QAAQ,GAAG,IAAI;AACnC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,59 @@
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 report_utils_exports = {};
20
+ __export(report_utils_exports, {
21
+ isToday: () => isToday,
22
+ monthStartIso: () => monthStartIso,
23
+ monthStartUnix: () => monthStartUnix,
24
+ projectMonth: () => projectMonth
25
+ });
26
+ module.exports = __toCommonJS(report_utils_exports);
27
+ function monthStartUnix(nowMs) {
28
+ const now = new Date(nowMs);
29
+ return Math.floor(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1) / 1e3);
30
+ }
31
+ function monthStartIso(nowMs) {
32
+ return new Date(monthStartUnix(nowMs) * 1e3).toISOString().replace(/\.\d{3}Z$/, "Z");
33
+ }
34
+ function isToday(bucketStart, nowMs) {
35
+ let date;
36
+ if (typeof bucketStart === "number") {
37
+ date = new Date(bucketStart * 1e3);
38
+ } else if (typeof bucketStart === "string" && bucketStart) {
39
+ date = new Date(bucketStart);
40
+ } else {
41
+ return false;
42
+ }
43
+ const now = new Date(nowMs);
44
+ return date.getUTCFullYear() === now.getUTCFullYear() && date.getUTCMonth() === now.getUTCMonth() && date.getUTCDate() === now.getUTCDate();
45
+ }
46
+ function projectMonth(monthSum, nowMs) {
47
+ const now = new Date(nowMs);
48
+ const daysInMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0)).getUTCDate();
49
+ const dayOfMonth = now.getUTCDate();
50
+ return Math.round(monthSum / dayOfMonth * daysInMonth * 100) / 100;
51
+ }
52
+ // Annotate the CommonJS export names for ESM import in node:
53
+ 0 && (module.exports = {
54
+ isToday,
55
+ monthStartIso,
56
+ monthStartUnix,
57
+ projectMonth
58
+ });
59
+ //# sourceMappingURL=report-utils.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/providers/report-utils.ts"],
4
+ "sourcesContent": ["/** Shared date/aggregation helpers for the daily-bucket report providers (OpenAI, Anthropic API). */\n\n/**\n * Start of the current month (UTC) as unix seconds.\n *\n * @param nowMs current time (ms)\n * @returns unix seconds\n */\nexport function monthStartUnix(nowMs: number): number {\n const now = new Date(nowMs);\n return Math.floor(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1) / 1000);\n}\n\n/**\n * Start of the current month (UTC) as an ISO timestamp.\n *\n * @param nowMs current time (ms)\n * @returns ISO string\n */\nexport function monthStartIso(nowMs: number): string {\n return new Date(monthStartUnix(nowMs) * 1000).toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n}\n\n/**\n * Whether a bucket start (unix seconds or ISO string) falls on today (UTC).\n *\n * @param bucketStart the bucket's start\n * @param nowMs current time (ms)\n * @returns true when the bucket is today's\n */\nexport function isToday(bucketStart: unknown, nowMs: number): boolean {\n let date: Date;\n if (typeof bucketStart === \"number\") {\n date = new Date(bucketStart * 1000);\n } else if (typeof bucketStart === \"string\" && bucketStart) {\n date = new Date(bucketStart);\n } else {\n return false;\n }\n const now = new Date(nowMs);\n return (\n date.getUTCFullYear() === now.getUTCFullYear() &&\n date.getUTCMonth() === now.getUTCMonth() &&\n date.getUTCDate() === now.getUTCDate()\n );\n}\n\n/**\n * Project the month-end spend from the spend so far: linear on elapsed days (UTC).\n *\n * @param monthSum spend since the 1st\n * @param nowMs current time (ms)\n * @returns the projected month total, rounded to cents\n */\nexport function projectMonth(monthSum: number, nowMs: number): number {\n const now = new Date(nowMs);\n const daysInMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0)).getUTCDate();\n const dayOfMonth = now.getUTCDate();\n return Math.round((monthSum / dayOfMonth) * daysInMonth * 100) / 100;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQO,SAAS,eAAe,OAAuB;AACpD,QAAM,MAAM,IAAI,KAAK,KAAK;AAC1B,SAAO,KAAK,MAAM,KAAK,IAAI,IAAI,eAAe,GAAG,IAAI,YAAY,GAAG,CAAC,IAAI,GAAI;AAC/E;AAQO,SAAS,cAAc,OAAuB;AACnD,SAAO,IAAI,KAAK,eAAe,KAAK,IAAI,GAAI,EAAE,YAAY,EAAE,QAAQ,aAAa,GAAG;AACtF;AASO,SAAS,QAAQ,aAAsB,OAAwB;AACpE,MAAI;AACJ,MAAI,OAAO,gBAAgB,UAAU;AACnC,WAAO,IAAI,KAAK,cAAc,GAAI;AAAA,EACpC,WAAW,OAAO,gBAAgB,YAAY,aAAa;AACzD,WAAO,IAAI,KAAK,WAAW;AAAA,EAC7B,OAAO;AACL,WAAO;AAAA,EACT;AACA,QAAM,MAAM,IAAI,KAAK,KAAK;AAC1B,SACE,KAAK,eAAe,MAAM,IAAI,eAAe,KAC7C,KAAK,YAAY,MAAM,IAAI,YAAY,KACvC,KAAK,WAAW,MAAM,IAAI,WAAW;AAEzC;AASO,SAAS,aAAa,UAAkB,OAAuB;AACpE,QAAM,MAAM,IAAI,KAAK,KAAK;AAC1B,QAAM,cAAc,IAAI,KAAK,KAAK,IAAI,IAAI,eAAe,GAAG,IAAI,YAAY,IAAI,GAAG,CAAC,CAAC,EAAE,WAAW;AAClG,QAAM,aAAa,IAAI,WAAW;AAClC,SAAO,KAAK,MAAO,WAAW,aAAc,cAAc,GAAG,IAAI;AACnE;",
6
+ "names": []
7
+ }
@@ -0,0 +1,96 @@
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 pure_helpers_exports = {};
20
+ __export(pure_helpers_exports, {
21
+ PROVIDER_KINDS: () => PROVIDER_KINDS,
22
+ clampPollInterval: () => clampPollInterval,
23
+ parseAccounts: () => parseAccounts,
24
+ sanitizeId: () => sanitizeId,
25
+ validAccountIds: () => validAccountIds
26
+ });
27
+ module.exports = __toCommonJS(pure_helpers_exports);
28
+ function sanitizeId(name) {
29
+ return name.trim().replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_{2,}/g, "_").replace(/^_+|_+$/g, "");
30
+ }
31
+ const PROVIDER_KINDS = ["claude-sub", "openrouter", "deepseek", "openai", "anthropic-api"];
32
+ function parseAccounts(raw) {
33
+ if (!Array.isArray(raw)) {
34
+ return [];
35
+ }
36
+ const accounts = [];
37
+ const seen = /* @__PURE__ */ new Set();
38
+ for (const entry of raw) {
39
+ if (typeof entry !== "object" || entry === null) {
40
+ continue;
41
+ }
42
+ const row = entry;
43
+ if (row.enabled === false) {
44
+ continue;
45
+ }
46
+ const name = typeof row.name === "string" ? row.name.trim() : "";
47
+ const id = sanitizeId(name);
48
+ const provider = typeof row.provider === "string" ? row.provider : "";
49
+ if (!id || id === "info" || id === "total" || !PROVIDER_KINDS.includes(provider) || seen.has(id)) {
50
+ continue;
51
+ }
52
+ seen.add(id);
53
+ const threshold = Number(row.warnThreshold);
54
+ accounts.push({
55
+ name,
56
+ id,
57
+ provider,
58
+ credentialId: typeof row.credentialId === "string" ? row.credentialId : "",
59
+ warnThreshold: Number.isFinite(threshold) && threshold >= 10 && threshold <= 100 ? threshold : 80
60
+ });
61
+ }
62
+ return accounts;
63
+ }
64
+ function validAccountIds(raw) {
65
+ if (!Array.isArray(raw)) {
66
+ return [];
67
+ }
68
+ const ids = [];
69
+ for (const entry of raw) {
70
+ if (typeof entry !== "object" || entry === null) {
71
+ continue;
72
+ }
73
+ const row = entry;
74
+ const id = sanitizeId(typeof row.name === "string" ? row.name : "");
75
+ if (id && id !== "info" && id !== "total" && !ids.includes(id)) {
76
+ ids.push(id);
77
+ }
78
+ }
79
+ return ids;
80
+ }
81
+ function clampPollInterval(raw) {
82
+ const value = Number(raw);
83
+ if (!Number.isFinite(value) || value <= 0) {
84
+ return 300;
85
+ }
86
+ return Math.min(3600, Math.max(60, Math.round(value)));
87
+ }
88
+ // Annotate the CommonJS export names for ESM import in node:
89
+ 0 && (module.exports = {
90
+ PROVIDER_KINDS,
91
+ clampPollInterval,
92
+ parseAccounts,
93
+ sanitizeId,
94
+ validAccountIds
95
+ });
96
+ //# sourceMappingURL=pure-helpers.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/pure-helpers.ts"],
4
+ "sourcesContent": ["/** One configured AI account, validated from the admin table (API boundary). */\nexport interface AccountConfig {\n /** Display name from the table. */\n name: string;\n /** Id-safe object id derived from the name. */\n id: string;\n /** Provider kind. */\n provider: string;\n /** Central credential id (system.credentials.*); empty for claude-sub. */\n credentialId: string;\n /** Warn threshold in percent. */\n warnThreshold: number;\n}\n\n/**\n * Make a string safe as an ioBroker object-id segment.\n *\n * @param name the raw name\n * @returns the sanitized id segment\n */\nexport function sanitizeId(name: string): string {\n return name\n .trim()\n .replace(/[^a-zA-Z0-9_-]/g, \"_\")\n .replace(/_{2,}/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n}\n\n/** The provider kinds the adapter knows. */\nexport const PROVIDER_KINDS = [\"claude-sub\", \"openrouter\", \"deepseek\", \"openai\", \"anthropic-api\"];\n\n/**\n * Parse and validate the admin accounts table. Rows without a usable name or with an\n * unknown provider are skipped (type-guarded \u2014 the table is external input); disabled\n * rows are skipped too. Duplicate ids keep the first row.\n *\n * @param raw the native.accounts value\n * @returns the validated, enabled accounts\n */\nexport function parseAccounts(raw: unknown): AccountConfig[] {\n if (!Array.isArray(raw)) {\n return [];\n }\n const accounts: AccountConfig[] = [];\n const seen = new Set<string>();\n for (const entry of raw) {\n if (typeof entry !== \"object\" || entry === null) {\n continue;\n }\n const row = entry as Record<string, unknown>;\n if (row.enabled === false) {\n continue;\n }\n const name = typeof row.name === \"string\" ? row.name.trim() : \"\";\n const id = sanitizeId(name);\n const provider = typeof row.provider === \"string\" ? row.provider : \"\";\n if (!id || id === \"info\" || id === \"total\" || !PROVIDER_KINDS.includes(provider) || seen.has(id)) {\n continue;\n }\n seen.add(id);\n const threshold = Number(row.warnThreshold);\n accounts.push({\n name,\n id,\n provider,\n credentialId: typeof row.credentialId === \"string\" ? row.credentialId : \"\",\n warnThreshold: Number.isFinite(threshold) && threshold >= 10 && threshold <= 100 ? threshold : 80,\n });\n }\n return accounts;\n}\n\n/**\n * The ids of ALL valid table rows \u2014 including disabled ones. Used by the stale-object\n * cleanup: a disabled account is paused, not deleted; only rows removed from the\n * table lose their tree.\n *\n * @param raw the native.accounts value\n * @returns the id-safe ids of every valid row\n */\nexport function validAccountIds(raw: unknown): string[] {\n if (!Array.isArray(raw)) {\n return [];\n }\n const ids: string[] = [];\n for (const entry of raw) {\n if (typeof entry !== \"object\" || entry === null) {\n continue;\n }\n const row = entry as Record<string, unknown>;\n const id = sanitizeId(typeof row.name === \"string\" ? row.name : \"\");\n if (id && id !== \"info\" && id !== \"total\" && !ids.includes(id)) {\n ids.push(id);\n }\n }\n return ids;\n}\n\n/**\n * Clamp the poll interval to the safe range: minimum 60 s (provider throttling locks\n * whole accounts), default 300 s for anything unusable.\n *\n * @param raw the configured value\n * @returns the effective interval in seconds\n */\nexport function clampPollInterval(raw: unknown): number {\n const value = Number(raw);\n if (!Number.isFinite(value) || value <= 0) {\n return 300;\n }\n return Math.min(3600, Math.max(60, Math.round(value)));\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBO,SAAS,WAAW,MAAsB;AAC/C,SAAO,KACJ,KAAK,EACL,QAAQ,mBAAmB,GAAG,EAC9B,QAAQ,UAAU,GAAG,EACrB,QAAQ,YAAY,EAAE;AAC3B;AAGO,MAAM,iBAAiB,CAAC,cAAc,cAAc,YAAY,UAAU,eAAe;AAUzF,SAAS,cAAc,KAA+B;AAC3D,MAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,WAAO,CAAC;AAAA,EACV;AACA,QAAM,WAA4B,CAAC;AACnC,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,SAAS,KAAK;AACvB,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C;AAAA,IACF;AACA,UAAM,MAAM;AACZ,QAAI,IAAI,YAAY,OAAO;AACzB;AAAA,IACF;AACA,UAAM,OAAO,OAAO,IAAI,SAAS,WAAW,IAAI,KAAK,KAAK,IAAI;AAC9D,UAAM,KAAK,WAAW,IAAI;AAC1B,UAAM,WAAW,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;AACnE,QAAI,CAAC,MAAM,OAAO,UAAU,OAAO,WAAW,CAAC,eAAe,SAAS,QAAQ,KAAK,KAAK,IAAI,EAAE,GAAG;AAChG;AAAA,IACF;AACA,SAAK,IAAI,EAAE;AACX,UAAM,YAAY,OAAO,IAAI,aAAa;AAC1C,aAAS,KAAK;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc,OAAO,IAAI,iBAAiB,WAAW,IAAI,eAAe;AAAA,MACxE,eAAe,OAAO,SAAS,SAAS,KAAK,aAAa,MAAM,aAAa,MAAM,YAAY;AAAA,IACjG,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAUO,SAAS,gBAAgB,KAAwB;AACtD,MAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,KAAK;AACvB,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C;AAAA,IACF;AACA,UAAM,MAAM;AACZ,UAAM,KAAK,WAAW,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,EAAE;AAClE,QAAI,MAAM,OAAO,UAAU,OAAO,WAAW,CAAC,IAAI,SAAS,EAAE,GAAG;AAC9D,UAAI,KAAK,EAAE;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;AASO,SAAS,kBAAkB,KAAsB;AACtD,QAAM,QAAQ,OAAO,GAAG;AACxB,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,GAAG;AACzC,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,CAAC,CAAC;AACvD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,175 @@
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 snapshot_tree_exports = {};
20
+ __export(snapshot_tree_exports, {
21
+ mapSnapshot: () => mapSnapshot,
22
+ maxLimitPercent: () => maxLimitPercent
23
+ });
24
+ module.exports = __toCommonJS(snapshot_tree_exports);
25
+ var import_pure_helpers = require("./pure-helpers");
26
+ function state(id, name, type, role, value, unit) {
27
+ const common = { name, type, role, read: true, write: false };
28
+ if (unit !== void 0) {
29
+ common.unit = unit;
30
+ }
31
+ return { def: { id, type: "state", common }, write: { id, value } };
32
+ }
33
+ function mapSnapshot(accountId, accountName, provider, snapshot) {
34
+ var _a, _b;
35
+ const objects = [];
36
+ const writes = [];
37
+ const add = (pair) => {
38
+ objects.push(pair.def);
39
+ writes.push(pair.write);
40
+ };
41
+ const channel = (id, name) => {
42
+ objects.push({ id, type: "channel", common: { name } });
43
+ };
44
+ objects.push({ id: accountId, type: "device", common: { name: `${accountName} (${provider})` } });
45
+ if (snapshot.limits && snapshot.limits.length > 0) {
46
+ channel(`${accountId}.limits`, "Limit windows");
47
+ for (const limit of snapshot.limits) {
48
+ const windowId = (0, import_pure_helpers.sanitizeId)(limit.name);
49
+ if (!windowId) {
50
+ continue;
51
+ }
52
+ channel(`${accountId}.limits.${windowId}`, limit.label);
53
+ add(
54
+ state(`${accountId}.limits.${windowId}.percent`, `${limit.label} used`, "number", "value", limit.percent, "%")
55
+ );
56
+ if (limit.resetAt !== void 0) {
57
+ add(
58
+ state(`${accountId}.limits.${windowId}.resetAt`, `${limit.label} resets at`, "string", "date", limit.resetAt)
59
+ );
60
+ }
61
+ }
62
+ }
63
+ const credits = snapshot.credits;
64
+ if (credits) {
65
+ channel(`${accountId}.credits`, "Credits");
66
+ const unit = credits.pieces ? "" : credits.currency;
67
+ if (credits.used !== void 0) {
68
+ add(state(`${accountId}.credits.used`, "Credits used", "number", "value", credits.used, unit));
69
+ }
70
+ if (credits.limit !== void 0) {
71
+ add(state(`${accountId}.credits.limit`, "Credits limit", "number", "value", credits.limit, unit));
72
+ }
73
+ if (credits.remaining !== void 0) {
74
+ add(state(`${accountId}.credits.remaining`, "Credits remaining", "number", "value", credits.remaining, unit));
75
+ }
76
+ if (credits.percent !== void 0) {
77
+ add(state(`${accountId}.credits.percent`, "Credits used (percent)", "number", "value", credits.percent, "%"));
78
+ }
79
+ if (credits.granted !== void 0) {
80
+ add(state(`${accountId}.credits.granted`, "Granted balance", "number", "value", credits.granted, unit));
81
+ }
82
+ if (credits.toppedUp !== void 0) {
83
+ add(state(`${accountId}.credits.toppedUp`, "Topped-up balance", "number", "value", credits.toppedUp, unit));
84
+ }
85
+ }
86
+ const costs = snapshot.costs;
87
+ if (costs) {
88
+ channel(`${accountId}.costs`, "Costs");
89
+ if (costs.today !== void 0) {
90
+ add(state(`${accountId}.costs.today`, "Costs today", "number", "value", costs.today, costs.currency));
91
+ }
92
+ if (costs.month !== void 0) {
93
+ add(state(`${accountId}.costs.month`, "Costs this month", "number", "value", costs.month, costs.currency));
94
+ }
95
+ if (costs.total !== void 0) {
96
+ add(state(`${accountId}.costs.total`, "Costs total (lifetime)", "number", "value", costs.total, costs.currency));
97
+ }
98
+ if (costs.projectedMonth !== void 0) {
99
+ add(
100
+ state(
101
+ `${accountId}.costs.projectedMonth`,
102
+ "Costs projected month-end (computed)",
103
+ "number",
104
+ "value",
105
+ costs.projectedMonth,
106
+ costs.currency
107
+ )
108
+ );
109
+ }
110
+ }
111
+ const tokens = snapshot.tokens;
112
+ if (tokens) {
113
+ channel(`${accountId}.tokens`, "Tokens");
114
+ if (tokens.inputToday !== void 0) {
115
+ add(state(`${accountId}.tokens.inputToday`, "Input tokens today", "number", "value", tokens.inputToday));
116
+ }
117
+ if (tokens.outputToday !== void 0) {
118
+ add(state(`${accountId}.tokens.outputToday`, "Output tokens today", "number", "value", tokens.outputToday));
119
+ }
120
+ if (tokens.perModel && tokens.perModel.length > 0) {
121
+ channel(`${accountId}.models`, "Per-model usage");
122
+ for (const model of tokens.perModel) {
123
+ const modelId = (0, import_pure_helpers.sanitizeId)(model.model);
124
+ if (!modelId) {
125
+ continue;
126
+ }
127
+ channel(`${accountId}.models.${modelId}`, model.model);
128
+ if (model.tokens !== void 0) {
129
+ add(
130
+ state(
131
+ `${accountId}.models.${modelId}.tokensToday`,
132
+ `${model.model} tokens today`,
133
+ "number",
134
+ "value",
135
+ model.tokens
136
+ )
137
+ );
138
+ }
139
+ if (model.cost !== void 0) {
140
+ add(
141
+ state(
142
+ `${accountId}.models.${modelId}.costToday`,
143
+ `${model.model} costs today`,
144
+ "number",
145
+ "value",
146
+ model.cost,
147
+ (_b = (_a = snapshot.costs) == null ? void 0 : _a.currency) != null ? _b : "USD"
148
+ )
149
+ );
150
+ }
151
+ }
152
+ }
153
+ }
154
+ if (snapshot.available !== void 0) {
155
+ add(state(`${accountId}.available`, "Balance sufficient for calls", "boolean", "indicator", snapshot.available));
156
+ }
157
+ return { objects, writes };
158
+ }
159
+ function maxLimitPercent(snapshot) {
160
+ var _a, _b, _c;
161
+ const percents = [
162
+ ...(_b = (_a = snapshot.limits) == null ? void 0 : _a.map((limit) => limit.percent)) != null ? _b : [],
163
+ ...((_c = snapshot.credits) == null ? void 0 : _c.percent) !== void 0 ? [snapshot.credits.percent] : []
164
+ ];
165
+ if (percents.length === 0) {
166
+ return void 0;
167
+ }
168
+ return Math.max(...percents);
169
+ }
170
+ // Annotate the CommonJS export names for ESM import in node:
171
+ 0 && (module.exports = {
172
+ mapSnapshot,
173
+ maxLimitPercent
174
+ });
175
+ //# sourceMappingURL=snapshot-tree.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/snapshot-tree.ts"],
4
+ "sourcesContent": ["import type { UsageSnapshot } from \"./provider\";\nimport { sanitizeId } from \"./pure-helpers\";\n\n/** An object to create: id (relative to the instance), type and common. */\nexport interface ObjectDef {\n /** Object id relative to the instance root. */\n id: string;\n /** ioBroker object type. */\n type: \"device\" | \"channel\" | \"state\";\n /** The object's common. */\n common: {\n name: string | Record<string, string>;\n type?: \"boolean\" | \"number\" | \"string\";\n role?: string;\n read?: boolean;\n write?: boolean;\n unit?: string;\n min?: number;\n max?: number;\n };\n}\n\n/** A state value to write (ack). */\nexport interface StateWrite {\n /** State id relative to the instance root. */\n id: string;\n /** The value. */\n value: boolean | number | string;\n}\n\n/**\n * A read-only state definition plus its value \u2014 the tree builder's working unit.\n *\n * @param id the state id\n * @param name the object name\n * @param type the value type\n * @param role the state role\n * @param value the current value\n * @param unit optional unit\n * @returns the def/value pair\n */\nfunction state(\n id: string,\n name: string,\n type: \"boolean\" | \"number\" | \"string\",\n role: string,\n value: boolean | number | string,\n unit?: string,\n): { def: ObjectDef; write: StateWrite } {\n const common: ObjectDef[\"common\"] = { name, type, role, read: true, write: false };\n if (unit !== undefined) {\n common.unit = unit;\n }\n return { def: { id, type: \"state\", common }, write: { id, value } };\n}\n\n/** The result of mapping one snapshot: objects to upsert (parents first) and values to write. */\nexport interface TreeResult {\n /** Objects in creation order (device, channels, states). */\n objects: ObjectDef[];\n /** State values to write with ack. */\n writes: StateWrite[];\n}\n\n/**\n * Map one account snapshot onto the harmonised object tree: `limits.*`, `credits.*`,\n * `costs.*`, `tokens.*` (+ `models.*`) \u2014 only what the snapshot carries is created,\n * so the tree mirrors exactly what the provider's source delivers.\n *\n * @param accountId the id-safe account id (device node)\n * @param accountName the display name for the device object\n * @param provider the provider kind (device object name suffix)\n * @param snapshot the fetched snapshot\n * @returns objects (parents first) and state writes\n */\nexport function mapSnapshot(\n accountId: string,\n accountName: string,\n provider: string,\n snapshot: UsageSnapshot,\n): TreeResult {\n const objects: ObjectDef[] = [];\n const writes: StateWrite[] = [];\n const add = (pair: { def: ObjectDef; write: StateWrite }): void => {\n objects.push(pair.def);\n writes.push(pair.write);\n };\n const channel = (id: string, name: string): void => {\n objects.push({ id, type: \"channel\", common: { name } });\n };\n\n objects.push({ id: accountId, type: \"device\", common: { name: `${accountName} (${provider})` } });\n\n if (snapshot.limits && snapshot.limits.length > 0) {\n channel(`${accountId}.limits`, \"Limit windows\");\n for (const limit of snapshot.limits) {\n const windowId = sanitizeId(limit.name);\n if (!windowId) {\n continue;\n }\n channel(`${accountId}.limits.${windowId}`, limit.label);\n add(\n state(`${accountId}.limits.${windowId}.percent`, `${limit.label} used`, \"number\", \"value\", limit.percent, \"%\"),\n );\n if (limit.resetAt !== undefined) {\n add(\n state(`${accountId}.limits.${windowId}.resetAt`, `${limit.label} resets at`, \"string\", \"date\", limit.resetAt),\n );\n }\n }\n }\n\n const credits = snapshot.credits;\n if (credits) {\n channel(`${accountId}.credits`, \"Credits\");\n const unit = credits.pieces ? \"\" : credits.currency;\n if (credits.used !== undefined) {\n add(state(`${accountId}.credits.used`, \"Credits used\", \"number\", \"value\", credits.used, unit));\n }\n if (credits.limit !== undefined) {\n add(state(`${accountId}.credits.limit`, \"Credits limit\", \"number\", \"value\", credits.limit, unit));\n }\n if (credits.remaining !== undefined) {\n add(state(`${accountId}.credits.remaining`, \"Credits remaining\", \"number\", \"value\", credits.remaining, unit));\n }\n if (credits.percent !== undefined) {\n add(state(`${accountId}.credits.percent`, \"Credits used (percent)\", \"number\", \"value\", credits.percent, \"%\"));\n }\n if (credits.granted !== undefined) {\n add(state(`${accountId}.credits.granted`, \"Granted balance\", \"number\", \"value\", credits.granted, unit));\n }\n if (credits.toppedUp !== undefined) {\n add(state(`${accountId}.credits.toppedUp`, \"Topped-up balance\", \"number\", \"value\", credits.toppedUp, unit));\n }\n }\n\n const costs = snapshot.costs;\n if (costs) {\n channel(`${accountId}.costs`, \"Costs\");\n if (costs.today !== undefined) {\n add(state(`${accountId}.costs.today`, \"Costs today\", \"number\", \"value\", costs.today, costs.currency));\n }\n if (costs.month !== undefined) {\n add(state(`${accountId}.costs.month`, \"Costs this month\", \"number\", \"value\", costs.month, costs.currency));\n }\n if (costs.total !== undefined) {\n add(state(`${accountId}.costs.total`, \"Costs total (lifetime)\", \"number\", \"value\", costs.total, costs.currency));\n }\n if (costs.projectedMonth !== undefined) {\n add(\n state(\n `${accountId}.costs.projectedMonth`,\n \"Costs projected month-end (computed)\",\n \"number\",\n \"value\",\n costs.projectedMonth,\n costs.currency,\n ),\n );\n }\n }\n\n const tokens = snapshot.tokens;\n if (tokens) {\n channel(`${accountId}.tokens`, \"Tokens\");\n if (tokens.inputToday !== undefined) {\n add(state(`${accountId}.tokens.inputToday`, \"Input tokens today\", \"number\", \"value\", tokens.inputToday));\n }\n if (tokens.outputToday !== undefined) {\n add(state(`${accountId}.tokens.outputToday`, \"Output tokens today\", \"number\", \"value\", tokens.outputToday));\n }\n if (tokens.perModel && tokens.perModel.length > 0) {\n channel(`${accountId}.models`, \"Per-model usage\");\n for (const model of tokens.perModel) {\n const modelId = sanitizeId(model.model);\n if (!modelId) {\n continue;\n }\n channel(`${accountId}.models.${modelId}`, model.model);\n if (model.tokens !== undefined) {\n add(\n state(\n `${accountId}.models.${modelId}.tokensToday`,\n `${model.model} tokens today`,\n \"number\",\n \"value\",\n model.tokens,\n ),\n );\n }\n if (model.cost !== undefined) {\n add(\n state(\n `${accountId}.models.${modelId}.costToday`,\n `${model.model} costs today`,\n \"number\",\n \"value\",\n model.cost,\n snapshot.costs?.currency ?? \"USD\",\n ),\n );\n }\n }\n }\n }\n\n if (snapshot.available !== undefined) {\n add(state(`${accountId}.available`, \"Balance sufficient for calls\", \"boolean\", \"indicator\", snapshot.available));\n }\n\n return { objects, writes };\n}\n\n/**\n * The highest limit percent in a snapshot, or undefined when it has no windows.\n *\n * @param snapshot the snapshot\n * @returns the maximum percent\n */\nexport function maxLimitPercent(snapshot: UsageSnapshot): number | undefined {\n const percents = [\n ...(snapshot.limits?.map(limit => limit.percent) ?? []),\n ...(snapshot.credits?.percent !== undefined ? [snapshot.credits.percent] : []),\n ];\n if (percents.length === 0) {\n return undefined;\n }\n return Math.max(...percents);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }