@felan-ai/felan 0.38.0 → 0.39.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 (56) hide show
  1. package/dist/dependency-onboarding.d.ts +1 -0
  2. package/dist/dependency-onboarding.d.ts.map +1 -1
  3. package/dist/dependency-onboarding.js +5 -1
  4. package/dist/dependency-onboarding.js.map +1 -1
  5. package/dist/providers/dependencies.js +1 -1
  6. package/dist/providers/dependencies.js.map +1 -1
  7. package/dist/providers/execution-services.js +1 -1
  8. package/dist/providers/execution-services.js.map +1 -1
  9. package/dist/providers/savings.d.ts +2 -0
  10. package/dist/providers/savings.d.ts.map +1 -1
  11. package/dist/providers/savings.js +9 -1
  12. package/dist/providers/savings.js.map +1 -1
  13. package/dist/providers/sessions.js +2 -2
  14. package/dist/providers/sessions.js.map +1 -1
  15. package/dist/providers/setup/manager.d.ts +1 -0
  16. package/dist/providers/setup/manager.d.ts.map +1 -1
  17. package/dist/providers/setup/manager.js +49 -7
  18. package/dist/providers/setup/manager.js.map +1 -1
  19. package/dist/savings/dashboard.d.ts +26 -0
  20. package/dist/savings/dashboard.d.ts.map +1 -0
  21. package/dist/savings/dashboard.js +225 -0
  22. package/dist/savings/dashboard.js.map +1 -0
  23. package/dist/savings/data.d.ts +119 -0
  24. package/dist/savings/data.d.ts.map +1 -0
  25. package/dist/savings/data.js +232 -0
  26. package/dist/savings/data.js.map +1 -0
  27. package/dist/savings-command.d.ts +11 -2
  28. package/dist/savings-command.d.ts.map +1 -1
  29. package/dist/savings-command.js +137 -27
  30. package/dist/savings-command.js.map +1 -1
  31. package/dist/tui/presenters.d.ts.map +1 -1
  32. package/dist/tui/presenters.js +2 -0
  33. package/dist/tui/presenters.js.map +1 -1
  34. package/dist/tui/renderers/insights/panel.d.ts +2 -0
  35. package/dist/tui/renderers/insights/panel.d.ts.map +1 -1
  36. package/dist/tui/renderers/insights/panel.js +13 -2
  37. package/dist/tui/renderers/insights/panel.js.map +1 -1
  38. package/dist/tui/renderers/savings/panel.d.ts +64 -0
  39. package/dist/tui/renderers/savings/panel.d.ts.map +1 -0
  40. package/dist/tui/renderers/savings/panel.js +327 -0
  41. package/dist/tui/renderers/savings/panel.js.map +1 -0
  42. package/dist/tui/renderers/savings.d.ts +5 -0
  43. package/dist/tui/renderers/savings.d.ts.map +1 -0
  44. package/dist/tui/renderers/savings.js +43 -0
  45. package/dist/tui/renderers/savings.js.map +1 -0
  46. package/dist/tui/run.d.ts.map +1 -1
  47. package/dist/tui/run.js +22 -8
  48. package/dist/tui/run.js.map +1 -1
  49. package/dist/tui/setup-ui.d.ts.map +1 -1
  50. package/dist/tui/setup-ui.js +6 -3
  51. package/dist/tui/setup-ui.js.map +1 -1
  52. package/dist/tui/setup.d.ts.map +1 -1
  53. package/dist/tui/setup.js +2 -1
  54. package/dist/tui/setup.js.map +1 -1
  55. package/npm-shrinkwrap.json +25 -25
  56. package/package.json +12 -12
@@ -0,0 +1,26 @@
1
+ import type { UiValues } from '@felan-ai/agent-core';
2
+ import type { SavingsBucket } from '../providers/savings.js';
3
+ import { type SavingsDashboard, type SavingsRangeId, type SavingsTabId } from './data.js';
4
+ export interface SavingsSessionLabel {
5
+ readonly title: string;
6
+ readonly cwd: string;
7
+ }
8
+ export interface SavingsDashboardInput {
9
+ readonly buckets: readonly SavingsBucket[];
10
+ readonly diagnostics: readonly string[];
11
+ readonly now: Date;
12
+ readonly currentSessionId: string;
13
+ readonly currentProjectKey: string;
14
+ /** Labels the current project even before its session appears in the history list. */
15
+ readonly currentProjectCwd?: string;
16
+ readonly sessions: ReadonlyMap<string, SavingsSessionLabel>;
17
+ readonly initialRange?: SavingsRangeId;
18
+ readonly initialTab?: SavingsTabId;
19
+ }
20
+ /** Data is omitted when even a trimmed dashboard exceeds the budget. */
21
+ export declare function createSavingsDashboard(input: SavingsDashboardInput): {
22
+ dashboard: SavingsDashboard;
23
+ data?: UiValues;
24
+ };
25
+ export declare function savingsDashboardMarkdown(dashboard: SavingsDashboard): string;
26
+ //# sourceMappingURL=dashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/savings/dashboard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,yBAAyB,CAAC;AACnF,OAAO,EAG8D,KAAK,gBAAgB,EAAE,KAAK,cAAc,EACjE,KAAK,YAAY,EAC9D,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,mBAAmB;IAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE;AAErF,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IACnB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,sFAAsF;IACtF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC5D,QAAQ,CAAC,YAAY,CAAC,EAAE,cAAc,CAAC;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC;CACpC;AAkBD,wEAAwE;AACxE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,qBAAqB,GAAG;IAAE,SAAS,EAAE,gBAAgB,CAAC;IAAC,IAAI,CAAC,EAAE,QAAQ,CAAA;CAAE,CAYrH;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,gBAAgB,GAAG,MAAM,CA8B5E"}
@@ -0,0 +1,225 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { SAVINGS_CATEGORIES, SAVINGS_DEFAULT_RANGE, SAVINGS_RANGES, encodeSavingsDashboard, formatSavingsTokenImpact, formatSavingsTokens, formatSavingsUsd, savingsCategoryLabel, savingsRangeDays, savingsRangeLabel, savingsText, } from './data.js';
3
+ const EXTENSION_LABELS = {
4
+ '@felan-ai/agent-core/dynamic-thinking': 'Dynamic thinking',
5
+ '@felan-ai/ext-markitdown': 'MarkItDown',
6
+ '@felan-ai/ext-output-style': 'Output style',
7
+ '@felan-ai/ext-prewalk': 'Prewalk',
8
+ '@felan-ai/ext-rtk-optimizer': 'RTK optimizer',
9
+ '@felan-ai/ext-subagents': 'Subagents',
10
+ };
11
+ /** Data is omitted when even a trimmed dashboard exceeds the budget. */
12
+ export function createSavingsDashboard(input) {
13
+ const model = buildModel(input);
14
+ for (;;) {
15
+ const dashboard = layout(model);
16
+ const data = encodeSavingsDashboard(dashboard);
17
+ if (data)
18
+ return { dashboard, data };
19
+ const widest = model.ranges.reduce((best, range) => range.sessions.length > best.sessions.length ? range : best);
20
+ if (!widest.sessions.length)
21
+ return { dashboard };
22
+ const drop = Math.max(1, Math.ceil(widest.sessions.length / 5));
23
+ widest.sessions.splice(-drop);
24
+ widest.omittedSessions += drop;
25
+ }
26
+ }
27
+ export function savingsDashboardMarkdown(dashboard) {
28
+ const range = dashboard.ranges.find(view => view.id === dashboard.initialRange);
29
+ const extension = (index) => dashboard.extensions[index];
30
+ const lines = [
31
+ `# Estimated savings — ${range.label}`, '',
32
+ `Estimated API-equivalent cost avoided: **${formatSavingsUsd(range.totals.savedUsd)}** (baseline ${formatSavingsUsd(range.totals.baselineUsd)}, actual ${formatSavingsUsd(range.totals.actualUsd)})`,
33
+ `Measured optimization decisions: ${range.totals.decisions} · Token impact: ${formatSavingsTokenImpact(range.totals.savedTokens)}`,
34
+ `This session: ${formatSavingsUsd(range.currentSession.savedUsd)} · This project: ${formatSavingsUsd(range.currentProject.savedUsd)}`,
35
+ ];
36
+ if (range.unpriced.decisions)
37
+ lines.push(`${range.unpriced.decisions} decisions without a price are excluded from USD totals (${formatSavingsTokenImpact(range.unpriced.savedTokens)}).`);
38
+ if (range.categories.length) {
39
+ lines.push('', '| Category | Decisions | Saved |', '| --- | ---: | ---: |', ...range.categories.map(row => `| ${savingsCategoryLabel(row.category)} | ${row.decisions} | ${formatSavingsUsd(row.savedUsd)} |`));
40
+ }
41
+ if (range.extensions.length) {
42
+ lines.push('', '| Extension | Category | Decisions | Tokens saved | Saved |', '| --- | --- | ---: | ---: | ---: |', ...range.extensions.map(row => `| ${markdownCell(extension(row.extension).label)} | ${savingsCategoryLabel(extension(row.extension).category)} | ${row.decisions} | ${formatSavingsTokens(row.savedTokens)} | ${formatSavingsUsd(row.savedUsd)} |`));
43
+ }
44
+ if (range.sessions.length) {
45
+ lines.push('', '| Session | Project | Decisions | Saved |', '| --- | --- | ---: | ---: |', ...range.sessions.map(row => {
46
+ const session = dashboard.sessions[row.session];
47
+ return `| ${markdownCell(session.title)} | ${markdownCell(dashboard.projects[session.project].label)} | ${row.decisions} | ${formatSavingsUsd(row.savedUsd)} |`;
48
+ }));
49
+ if (range.omittedSessions)
50
+ lines.push('', `${range.omittedSessions} smaller sessions are not listed.`);
51
+ }
52
+ lines.push('', 'Values are API-equivalent estimates priced from the model catalog when each decision was measured. Days are UTC.');
53
+ if (dashboard.firstDay)
54
+ lines.push(`Measurements retained since ${dashboard.firstDay}.`);
55
+ lines.push(...dashboard.diagnostics.map(diagnostic => `Warning: ${diagnostic}`));
56
+ return lines.join('\n');
57
+ }
58
+ function buildModel(input) {
59
+ const today = input.now.toISOString().slice(0, 10);
60
+ const projectLabels = new Map();
61
+ for (const session of input.sessions.values()) {
62
+ projectLabels.set(createHash('sha256').update(session.cwd).digest('hex'), savingsText(basename(session.cwd) || session.cwd || 'Unknown project'));
63
+ }
64
+ if (input.currentProjectCwd && !projectLabels.has(input.currentProjectKey)) {
65
+ projectLabels.set(input.currentProjectKey, savingsText(basename(input.currentProjectCwd) || input.currentProjectCwd));
66
+ }
67
+ const projectLabel = (key) => projectLabels.get(key) ?? `Project ${key.slice(0, 8)}`;
68
+ const days = input.buckets.map(bucket => bucket.day).sort();
69
+ const extensionIds = [...new Set(input.buckets.map(bucket => bucket.producerId))]
70
+ .sort((left, right) => extensionLabel(left).localeCompare(extensionLabel(right)) || left.localeCompare(right));
71
+ const extensions = extensionIds.map(id => ({ id: savingsText(id), label: savingsText(extensionLabel(id)), category: dominantCategory(input.buckets.filter(bucket => bucket.producerId === id)) }));
72
+ const extensionIndex = new Map(extensionIds.map((id, index) => [id, index]));
73
+ const projectKeys = [...new Set([...input.buckets.map(bucket => bucket.projectKey), input.currentProjectKey])]
74
+ .sort((left, right) => projectLabel(left).localeCompare(projectLabel(right)) || left.localeCompare(right));
75
+ const projectIndex = new Map(projectKeys.map((key, index) => [key, index]));
76
+ const sessions = new Map();
77
+ const bySession = groupBy(input.buckets, bucket => bucket.sessionId);
78
+ for (const id of new Set([...bySession.keys(), input.currentSessionId])) {
79
+ const own = (bySession.get(id) ?? []).slice().sort((left, right) => left.day.localeCompare(right.day));
80
+ const latest = own.at(-1);
81
+ sessions.set(id, {
82
+ id: savingsText(id),
83
+ title: savingsText(input.sessions.get(id)?.title || `Session ${id.slice(0, 8)}`),
84
+ project: projectIndex.get(latest?.projectKey ?? input.currentProjectKey),
85
+ lastDay: latest?.day ?? today,
86
+ extensions: [...groupBy(own, bucket => bucket.producerId)].map(([producer, rows]) => ({ ...shareOf(rows), extension: extensionIndex.get(producer) }))
87
+ .sort((left, right) => right.savedUsd - left.savedUsd || left.extension - right.extension),
88
+ });
89
+ }
90
+ return {
91
+ generatedAt: input.now.toISOString(),
92
+ ...(days.length ? { firstDay: days[0], lastDay: days.at(-1) } : {}),
93
+ initialRange: input.initialRange ?? SAVINGS_DEFAULT_RANGE,
94
+ initialTab: input.initialTab ?? 'overview',
95
+ currentSessionId: input.currentSessionId,
96
+ currentProject: projectIndex.get(input.currentProjectKey),
97
+ extensions,
98
+ projects: projectKeys.map(key => ({ label: projectLabel(key) })),
99
+ sessions,
100
+ ranges: SAVINGS_RANGES.map(id => rangeModel(id, input, today, days[0], extensionIndex, projectIndex)),
101
+ diagnostics: input.diagnostics.map(diagnostic => savingsText(diagnostic)),
102
+ };
103
+ }
104
+ function rangeModel(id, input, to, firstDay, extensionIndex, projectIndex) {
105
+ const length = savingsRangeDays(id);
106
+ const from = length === undefined ? undefined : addDays(to, 1 - length);
107
+ const buckets = input.buckets.filter(bucket => from === undefined || bucket.day >= from && bucket.day <= to);
108
+ const periods = from === undefined
109
+ ? months(firstDay?.slice(0, 7) ?? to.slice(0, 7), to.slice(0, 7))
110
+ : Array.from({ length: length }, (_, offset) => addDays(from, offset));
111
+ const byPeriod = groupBy(buckets, bucket => from === undefined ? bucket.day.slice(0, 7) : bucket.day);
112
+ return {
113
+ id, label: savingsRangeLabel(id), ...(from === undefined ? {} : { from }), to,
114
+ totals: amountOf(buckets),
115
+ unpriced: unpricedOf(buckets),
116
+ currentSession: amountOf(buckets.filter(bucket => bucket.sessionId === input.currentSessionId)),
117
+ currentProject: amountOf(buckets.filter(bucket => bucket.projectKey === input.currentProjectKey)),
118
+ categories: SAVINGS_CATEGORIES.flatMap(category => {
119
+ const rows = buckets.filter(bucket => bucket.category === category);
120
+ return rows.length ? [{ ...amountOf(rows), category }] : [];
121
+ }),
122
+ extensions: ranked([...groupBy(buckets, bucket => bucket.producerId)].map(([producer, rows]) => ({
123
+ ...amountOf(rows), extension: extensionIndex.get(producer),
124
+ operations: ranked([...groupBy(rows, bucket => JSON.stringify([bucket.operation ?? '', bucket.basis.kind, bucket.basis.method]))].map(([key, group]) => {
125
+ const [operation, basis, method] = JSON.parse(key);
126
+ return { ...amountOf(group), operation: savingsText(operation || 'unspecified'), basis, method: savingsText(method) };
127
+ })),
128
+ }))),
129
+ sessions: ranked([...groupBy(buckets, bucket => bucket.sessionId)].map(([session, rows]) => ({ ...shareOf(rows), id: session }))),
130
+ projects: ranked([...groupBy(buckets, bucket => bucket.projectKey)].map(([project, rows]) => ({
131
+ ...amountOf(rows), project: projectIndex.get(project), sessions: new Set(rows.map(bucket => bucket.sessionId)).size,
132
+ }))),
133
+ trend: { granularity: from === undefined ? 'month' : 'day', points: periods.map(period => ({ ...amountOf(byPeriod.get(period) ?? []), period })) },
134
+ omittedSessions: 0,
135
+ };
136
+ }
137
+ function layout(model) {
138
+ const referenced = new Set([model.currentSessionId, ...model.ranges.flatMap(range => range.sessions.map(row => row.id))]);
139
+ const ids = [...model.sessions.keys()].filter(id => referenced.has(id))
140
+ .sort((left, right) => model.sessions.get(right).lastDay.localeCompare(model.sessions.get(left).lastDay) || left.localeCompare(right));
141
+ const sessionIndex = new Map(ids.map((id, index) => [id, index]));
142
+ const { currentSessionId, sessions, ranges, ...rest } = model;
143
+ return {
144
+ ...rest,
145
+ currentSession: sessionIndex.get(currentSessionId),
146
+ sessions: ids.map(id => sessions.get(id)),
147
+ ranges: ranges.map(range => ({ ...range, sessions: range.sessions.map(({ id, ...amount }) => ({ ...amount, session: sessionIndex.get(id) })) })),
148
+ };
149
+ }
150
+ function amountOf(buckets) {
151
+ let savedUsd = 0;
152
+ let baselineUsd = 0;
153
+ let actualUsd = 0;
154
+ let decisions = 0;
155
+ let savedTokens = 0;
156
+ for (const bucket of buckets) {
157
+ if (!priced(bucket))
158
+ continue;
159
+ baselineUsd += bucket.baseline.costUsd;
160
+ actualUsd += bucket.actual.costUsd;
161
+ savedUsd += bucket.baseline.costUsd - bucket.actual.costUsd;
162
+ decisions += bucket.calls;
163
+ savedTokens += tokenDelta(bucket);
164
+ }
165
+ return { savedUsd: usd(savedUsd), baselineUsd: usd(baselineUsd), actualUsd: usd(actualUsd), decisions, savedTokens };
166
+ }
167
+ function shareOf(buckets) {
168
+ const { savedUsd, decisions, savedTokens } = amountOf(buckets);
169
+ return { savedUsd, decisions, savedTokens };
170
+ }
171
+ function unpricedOf(buckets) {
172
+ const rows = buckets.filter(bucket => !priced(bucket));
173
+ return { decisions: rows.reduce((sum, bucket) => sum + bucket.calls, 0), savedTokens: rows.reduce((sum, bucket) => sum + tokenDelta(bucket), 0) };
174
+ }
175
+ function priced(bucket) { return bucket.baseline.costUsd !== undefined && bucket.actual.costUsd !== undefined; }
176
+ /** Token impact is only known when both sides report usage; cache writes are part of `cacheWrite`. */
177
+ function tokenDelta(bucket) {
178
+ const total = (outcome) => outcome.tokens
179
+ ? outcome.tokens.input + outcome.tokens.output + outcome.tokens.cacheRead + outcome.tokens.cacheWrite : undefined;
180
+ const baseline = total(bucket.baseline);
181
+ const actual = total(bucket.actual);
182
+ return baseline === undefined || actual === undefined ? 0 : baseline - actual;
183
+ }
184
+ function ranked(rows) {
185
+ return rows.sort((left, right) => right.savedUsd - left.savedUsd || right.decisions - left.decisions);
186
+ }
187
+ function dominantCategory(buckets) {
188
+ const counts = new Map();
189
+ for (const bucket of buckets)
190
+ counts.set(bucket.category, (counts.get(bucket.category) ?? 0) + bucket.calls);
191
+ return [...counts].sort((left, right) => right[1] - left[1] || SAVINGS_CATEGORIES.indexOf(left[0]) - SAVINGS_CATEGORIES.indexOf(right[0]))[0][0];
192
+ }
193
+ function extensionLabel(id) { return EXTENSION_LABELS[id] ?? id; }
194
+ function groupBy(values, key) {
195
+ const groups = new Map();
196
+ for (const value of values) {
197
+ const name = key(value);
198
+ const group = groups.get(name);
199
+ if (group)
200
+ group.push(value);
201
+ else
202
+ groups.set(name, [value]);
203
+ }
204
+ return groups;
205
+ }
206
+ function addDays(day, offset) {
207
+ return new Date(Date.parse(`${day}T00:00:00.000Z`) + offset * 86_400_000).toISOString().slice(0, 10);
208
+ }
209
+ function months(first, last) {
210
+ const result = [];
211
+ for (let [year, month] = first.split('-').map(Number); `${year}-${String(month).padStart(2, '0')}` <= last;) {
212
+ result.push(`${year}-${String(month).padStart(2, '0')}`);
213
+ if (month === 12) {
214
+ year += 1;
215
+ month = 1;
216
+ }
217
+ else
218
+ month += 1;
219
+ }
220
+ return result;
221
+ }
222
+ function basename(path) { return path.split(/[\\/]+/u).filter(Boolean).at(-1) ?? ''; }
223
+ function usd(value) { return Math.round(value * 1e6) / 1e6; }
224
+ function markdownCell(value) { return value.replaceAll('\\', '\\\\').replaceAll('|', '\\|'); }
225
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../src/savings/dashboard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ import type { UiValues } from '@felan-ai/agent-core';
2
+ export declare const SAVINGS_DASHBOARD_HINT: Readonly<{
3
+ id: "felan.savings-dashboard";
4
+ version: "1";
5
+ }>;
6
+ export declare const SAVINGS_ACTIONS: Readonly<{
7
+ readonly refresh: "refresh";
8
+ readonly close: "close";
9
+ }>;
10
+ export declare const SAVINGS_RANGES: readonly ["today", "7d", "30d", "90d", "all"];
11
+ export declare const SAVINGS_TABS: readonly ["overview", "extensions", "sessions", "projects"];
12
+ export declare const SAVINGS_CATEGORIES: readonly ["output-optimization", "model-routing", "context-management", "other"];
13
+ export declare const SAVINGS_DEFAULT_RANGE: SavingsRangeId;
14
+ export declare const SAVINGS_TEXT_BYTES = 256;
15
+ /** The insights dashboard budget. A larger dashboard is published without data, so clients show the Markdown fallback. */
16
+ export declare const SAVINGS_DASHBOARD_BYTES: number;
17
+ export type SavingsRangeId = typeof SAVINGS_RANGES[number];
18
+ export type SavingsTabId = typeof SAVINGS_TABS[number];
19
+ export type SavingsCategoryId = typeof SAVINGS_CATEGORIES[number];
20
+ export type SavingsBasisKind = 'observed-comparison' | 'estimated-baseline';
21
+ /** Priced decisions only. `savedUsd` and `savedTokens` are signed; negative values are added cost. */
22
+ export interface SavingsAmount {
23
+ savedUsd: number;
24
+ baselineUsd: number;
25
+ actualUsd: number;
26
+ decisions: number;
27
+ savedTokens: number;
28
+ }
29
+ /** The per-session subset of `SavingsAmount`, which keeps the payload small for long histories. */
30
+ export type SavingsShare = Pick<SavingsAmount, 'savedUsd' | 'decisions' | 'savedTokens'>;
31
+ /** Decisions without a resolvable price, excluded from every USD figure. */
32
+ export interface SavingsUnpriced {
33
+ decisions: number;
34
+ savedTokens: number;
35
+ }
36
+ export interface SavingsExtensionInfo {
37
+ id: string;
38
+ label: string;
39
+ category: SavingsCategoryId;
40
+ }
41
+ export interface SavingsProjectInfo {
42
+ label: string;
43
+ }
44
+ export interface SavingsSessionInfo {
45
+ id: string;
46
+ title: string;
47
+ project: number;
48
+ lastDay: string;
49
+ /** All-time totals of this session, per extension. */
50
+ extensions: Array<SavingsShare & {
51
+ extension: number;
52
+ }>;
53
+ }
54
+ export interface SavingsOperationRow extends SavingsAmount {
55
+ operation: string;
56
+ basis: SavingsBasisKind;
57
+ method: string;
58
+ }
59
+ export interface SavingsRangeView {
60
+ id: SavingsRangeId;
61
+ label: string;
62
+ /** Inclusive UTC days, matching how measurements are stored; `from` is absent for all time. */
63
+ from?: string;
64
+ to: string;
65
+ totals: SavingsAmount;
66
+ unpriced: SavingsUnpriced;
67
+ currentSession: SavingsAmount;
68
+ currentProject: SavingsAmount;
69
+ categories: Array<SavingsAmount & {
70
+ category: SavingsCategoryId;
71
+ }>;
72
+ extensions: Array<SavingsAmount & {
73
+ extension: number;
74
+ operations: SavingsOperationRow[];
75
+ }>;
76
+ sessions: Array<SavingsShare & {
77
+ session: number;
78
+ }>;
79
+ projects: Array<SavingsAmount & {
80
+ project: number;
81
+ sessions: number;
82
+ }>;
83
+ trend: {
84
+ granularity: 'day' | 'month';
85
+ points: Array<SavingsAmount & {
86
+ period: string;
87
+ }>;
88
+ };
89
+ omittedSessions: number;
90
+ }
91
+ export interface SavingsDashboard {
92
+ generatedAt: string;
93
+ firstDay?: string;
94
+ lastDay?: string;
95
+ initialRange: SavingsRangeId;
96
+ initialTab: SavingsTabId;
97
+ currentSession?: number;
98
+ currentProject?: number;
99
+ extensions: SavingsExtensionInfo[];
100
+ sessions: SavingsSessionInfo[];
101
+ projects: SavingsProjectInfo[];
102
+ /** One view per `SAVINGS_RANGES` entry, in that order. */
103
+ ranges: SavingsRangeView[];
104
+ diagnostics: string[];
105
+ }
106
+ export declare function savingsRangeDays(id: SavingsRangeId): number | undefined;
107
+ export declare function savingsRangeLabel(id: SavingsRangeId): string;
108
+ export declare function savingsCategoryLabel(category: SavingsCategoryId): string;
109
+ export declare function formatSavingsUsd(value: number): string;
110
+ /** Signed token counts; negative means the optimization used more tokens than its baseline. */
111
+ export declare function formatSavingsTokens(value: number): string;
112
+ /** Token impact as prose, so a routing decision that uses more tokens never reads as negative savings. */
113
+ export declare function formatSavingsTokenImpact(value: number): string;
114
+ /** Turns untrusted text, such as a session title, into one bounded display line. */
115
+ export declare function savingsText(value: string, maxBytes?: number): string;
116
+ export declare function encodeSavingsDashboard(dashboard: SavingsDashboard): UiValues | undefined;
117
+ export declare function readSavingsDashboard(data: UiValues | undefined): SavingsDashboard;
118
+ export declare function parseSavingsDashboard(value: unknown): SavingsDashboard;
119
+ //# sourceMappingURL=data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/savings/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,eAAO,MAAM,sBAAsB;;;EAAiE,CAAC;AAErG,eAAO,MAAM,eAAe;;;EAAiE,CAAC;AAC9F,eAAO,MAAM,cAAc,+CAAgD,CAAC;AAC5E,eAAO,MAAM,YAAY,6DAA8D,CAAC;AACxF,eAAO,MAAM,kBAAkB,kFAAmF,CAAC;AACnH,eAAO,MAAM,qBAAqB,EAAE,cAAsB,CAAC;AAC3D,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,0HAA0H;AAC1H,eAAO,MAAM,uBAAuB,QAAa,CAAC;AAElD,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AACvD,MAAM,MAAM,iBAAiB,GAAG,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAClE,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,GAAG,oBAAoB,CAAC;AAE5E,sGAAsG;AACtG,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,mGAAmG;AACnG,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,CAAC,CAAC;AAEzF,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAAG,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE;AAE3E,MAAM,WAAW,oBAAoB;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE;AAChG,MAAM,WAAW,kBAAkB;IAAG,KAAK,EAAE,MAAM,CAAA;CAAE;AACrD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,UAAU,EAAE,KAAK,CAAC,YAAY,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IAAG,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAEzH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,cAAc,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,cAAc,EAAE,aAAa,CAAC;IAC9B,cAAc,EAAE,aAAa,CAAC;IAC9B,UAAU,EAAE,KAAK,CAAC,aAAa,GAAG;QAAE,QAAQ,EAAE,iBAAiB,CAAA;KAAE,CAAC,CAAC;IACnE,UAAU,EAAE,KAAK,CAAC,aAAa,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,mBAAmB,EAAE,CAAA;KAAE,CAAC,CAAC;IAC5F,QAAQ,EAAE,KAAK,CAAC,YAAY,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,QAAQ,EAAE,KAAK,CAAC,aAAa,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,KAAK,EAAE;QAAE,WAAW,EAAE,KAAK,GAAG,OAAO,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC,aAAa,GAAG;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IAC3F,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,cAAc,CAAC;IAC7B,UAAU,EAAE,YAAY,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,oBAAoB,EAAE,CAAC;IACnC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,0DAA0D;IAC1D,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAWD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAEvE;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,CAG5D;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,CAExE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED,0GAA0G;AAC1G,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAqB,GAAG,MAAM,CAehF;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,gBAAgB,GAAG,QAAQ,GAAG,SAAS,CAGxF;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,gBAAgB,CAMjF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CA8BtE"}
@@ -0,0 +1,232 @@
1
+ export const SAVINGS_DASHBOARD_HINT = Object.freeze({ id: 'felan.savings-dashboard', version: '1' });
2
+ export const SAVINGS_ACTIONS = Object.freeze({ refresh: 'refresh', close: 'close' });
3
+ export const SAVINGS_RANGES = ['today', '7d', '30d', '90d', 'all'];
4
+ export const SAVINGS_TABS = ['overview', 'extensions', 'sessions', 'projects'];
5
+ export const SAVINGS_CATEGORIES = ['output-optimization', 'model-routing', 'context-management', 'other'];
6
+ export const SAVINGS_DEFAULT_RANGE = '30d';
7
+ export const SAVINGS_TEXT_BYTES = 256;
8
+ /** The insights dashboard budget. A larger dashboard is published without data, so clients show the Markdown fallback. */
9
+ export const SAVINGS_DASHBOARD_BYTES = 512 * 1024;
10
+ const encoder = new TextEncoder();
11
+ const CONTROL = /[\u0000-\u001f\u007f-\u009f]/u;
12
+ const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u;
13
+ const AMOUNT_KEYS = ['savedUsd', 'baselineUsd', 'actualUsd', 'decisions', 'savedTokens'];
14
+ const SHARE_KEYS = ['savedUsd', 'decisions', 'savedTokens'];
15
+ const bytes = (value) => encoder.encode(value).length;
16
+ function invalid() { throw new Error('Invalid savings dashboard data.'); }
17
+ export function savingsRangeDays(id) {
18
+ return id === 'all' ? undefined : id === 'today' ? 1 : Number(id.slice(0, -1));
19
+ }
20
+ export function savingsRangeLabel(id) {
21
+ const days = savingsRangeDays(id);
22
+ return days === undefined ? 'All time' : days === 1 ? 'Today (UTC)' : `Last ${days} days`;
23
+ }
24
+ export function savingsCategoryLabel(category) {
25
+ return category.split('-').map((word, position) => position === 0 ? `${word[0].toUpperCase()}${word.slice(1)}` : word).join(' ');
26
+ }
27
+ export function formatSavingsUsd(value) {
28
+ const digits = Math.abs(value).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
29
+ return `${value < 0 && digits !== '0.00' ? '-' : ''}$${digits}`;
30
+ }
31
+ /** Signed token counts; negative means the optimization used more tokens than its baseline. */
32
+ export function formatSavingsTokens(value) {
33
+ const size = Math.abs(value);
34
+ const digits = size >= 1e9 ? `${(size / 1e9).toFixed(1)}B` : size >= 1e6 ? `${(size / 1e6).toFixed(1)}M`
35
+ : size >= 1e3 ? `${(size / 1e3).toFixed(1)}K` : String(size);
36
+ return `${value < 0 ? '-' : ''}${digits.replace(/\.0(?=[KMB])/u, '')}`;
37
+ }
38
+ /** Token impact as prose, so a routing decision that uses more tokens never reads as negative savings. */
39
+ export function formatSavingsTokenImpact(value) {
40
+ return value === 0 ? 'no token change' : `${formatSavingsTokens(Math.abs(value))} ${value > 0 ? 'fewer' : 'more'} tokens`;
41
+ }
42
+ /** Turns untrusted text, such as a session title, into one bounded display line. */
43
+ export function savingsText(value, maxBytes = SAVINGS_TEXT_BYTES) {
44
+ const chars = Array.from(value.replace(/[\s\u0000-\u001f\u007f-\u009f]+/gu, ' ').trim(), char => {
45
+ const code = char.codePointAt(0);
46
+ return code >= 0xd800 && code <= 0xdfff ? '\uFFFD' : char;
47
+ });
48
+ const line = chars.join('');
49
+ if (bytes(line) <= maxBytes)
50
+ return line;
51
+ let clipped = '';
52
+ let size = bytes('…');
53
+ for (const char of chars) {
54
+ size += bytes(char);
55
+ if (size > maxBytes)
56
+ break;
57
+ clipped += char;
58
+ }
59
+ return `${clipped}…`;
60
+ }
61
+ export function encodeSavingsDashboard(dashboard) {
62
+ const encoded = JSON.stringify(parseSavingsDashboard(dashboard));
63
+ return bytes(encoded) > SAVINGS_DASHBOARD_BYTES ? undefined : { dashboard: encoded };
64
+ }
65
+ export function readSavingsDashboard(data) {
66
+ const encoded = data?.dashboard;
67
+ if (typeof encoded !== 'string' || bytes(encoded) > SAVINGS_DASHBOARD_BYTES)
68
+ invalid();
69
+ let value;
70
+ try {
71
+ value = JSON.parse(encoded);
72
+ }
73
+ catch {
74
+ invalid();
75
+ }
76
+ return parseSavingsDashboard(value);
77
+ }
78
+ export function parseSavingsDashboard(value) {
79
+ const source = record(value, ['generatedAt', 'initialRange', 'initialTab', 'extensions', 'sessions', 'projects', 'ranges', 'diagnostics'], ['firstDay', 'lastDay', 'currentSession', 'currentProject']);
80
+ const extensions = list(source.extensions, entry => {
81
+ const row = record(entry, ['id', 'label', 'category']);
82
+ return { id: text(row.id), label: text(row.label), category: category(row.category) };
83
+ });
84
+ const projects = list(source.projects, entry => ({ label: text(record(entry, ['label']).label) }));
85
+ const sessions = list(source.sessions, entry => {
86
+ const row = record(entry, ['id', 'title', 'project', 'lastDay', 'extensions']);
87
+ return {
88
+ id: text(row.id), title: text(row.title), project: index(row.project, projects.length), lastDay: day(row.lastDay),
89
+ extensions: list(row.extensions, entry => ({ ...share(entry, ['extension']), extension: index(record(entry, [...SHARE_KEYS, 'extension']).extension, extensions.length) })),
90
+ };
91
+ });
92
+ const ranges = list(source.ranges, entry => rangeView(entry, extensions.length, sessions.length, projects.length));
93
+ if (ranges.length !== SAVINGS_RANGES.length || ranges.some((range, position) => range.id !== SAVINGS_RANGES[position]))
94
+ invalid();
95
+ const firstDay = source.firstDay === undefined ? undefined : day(source.firstDay);
96
+ const lastDay = source.lastDay === undefined ? undefined : day(source.lastDay);
97
+ if ((firstDay === undefined) !== (lastDay === undefined) || firstDay !== undefined && firstDay > lastDay)
98
+ invalid();
99
+ return {
100
+ generatedAt: instant(source.generatedAt),
101
+ ...(firstDay === undefined ? {} : { firstDay, lastDay: lastDay }),
102
+ initialRange: member(source.initialRange, SAVINGS_RANGES),
103
+ initialTab: member(source.initialTab, SAVINGS_TABS),
104
+ ...(source.currentSession === undefined ? {} : { currentSession: index(source.currentSession, sessions.length) }),
105
+ ...(source.currentProject === undefined ? {} : { currentProject: index(source.currentProject, projects.length) }),
106
+ extensions, sessions, projects, ranges,
107
+ diagnostics: list(source.diagnostics, entry => text(entry)),
108
+ };
109
+ }
110
+ function rangeView(value, extensionCount, sessionCount, projectCount) {
111
+ const source = record(value, ['id', 'label', 'to', 'totals', 'unpriced', 'currentSession', 'currentProject', 'categories', 'extensions',
112
+ 'sessions', 'projects', 'trend', 'omittedSessions'], ['from']);
113
+ const id = member(source.id, SAVINGS_RANGES);
114
+ const to = day(source.to);
115
+ const from = source.from === undefined ? undefined : day(source.from);
116
+ if ((from === undefined) !== (id === 'all') || from !== undefined && from > to)
117
+ invalid();
118
+ const unpriced = record(source.unpriced, ['decisions', 'savedTokens']);
119
+ const trend = record(source.trend, ['granularity', 'points']);
120
+ const granularity = member(trend.granularity, ['day', 'month']);
121
+ const points = list(trend.points, entry => {
122
+ const period = record(entry, [...AMOUNT_KEYS, 'period']).period;
123
+ return { ...amount(entry, ['period']), period: granularity === 'day' ? day(period) : month(period) };
124
+ });
125
+ if (points.some((point, position) => position > 0 && point.period <= points[position - 1].period))
126
+ invalid();
127
+ return {
128
+ id, label: text(source.label), ...(from === undefined ? {} : { from }), to,
129
+ totals: amount(source.totals), unpriced: { decisions: count(unpriced.decisions), savedTokens: signedCount(unpriced.savedTokens) },
130
+ currentSession: amount(source.currentSession), currentProject: amount(source.currentProject),
131
+ categories: list(source.categories, entry => ({ ...amount(entry, ['category']), category: category(record(entry, [...AMOUNT_KEYS, 'category']).category) })),
132
+ extensions: list(source.extensions, entry => {
133
+ const row = record(entry, [...AMOUNT_KEYS, 'extension', 'operations']);
134
+ return {
135
+ ...amount(entry, ['extension', 'operations']), extension: index(row.extension, extensionCount),
136
+ operations: list(row.operations, operation => {
137
+ const fields = record(operation, [...AMOUNT_KEYS, 'operation', 'basis', 'method']);
138
+ return {
139
+ ...amount(operation, ['operation', 'basis', 'method']), operation: text(fields.operation),
140
+ basis: member(fields.basis, ['observed-comparison', 'estimated-baseline']), method: text(fields.method),
141
+ };
142
+ }),
143
+ };
144
+ }),
145
+ sessions: list(source.sessions, entry => ({ ...share(entry, ['session']), session: index(record(entry, [...SHARE_KEYS, 'session']).session, sessionCount) })),
146
+ projects: list(source.projects, entry => {
147
+ const row = record(entry, [...AMOUNT_KEYS, 'project', 'sessions']);
148
+ return { ...amount(entry, ['project', 'sessions']), project: index(row.project, projectCount), sessions: count(row.sessions) };
149
+ }),
150
+ trend: { granularity, points },
151
+ omittedSessions: count(source.omittedSessions),
152
+ };
153
+ }
154
+ function amount(value, extra = []) {
155
+ const source = record(value, [...AMOUNT_KEYS, ...extra]);
156
+ return {
157
+ savedUsd: signed(source.savedUsd), baselineUsd: unsigned(source.baselineUsd), actualUsd: unsigned(source.actualUsd),
158
+ decisions: count(source.decisions), savedTokens: signedCount(source.savedTokens),
159
+ };
160
+ }
161
+ function share(value, extra) {
162
+ const source = record(value, [...SHARE_KEYS, ...extra]);
163
+ return { savedUsd: signed(source.savedUsd), decisions: count(source.decisions), savedTokens: signedCount(source.savedTokens) };
164
+ }
165
+ function category(value) { return member(value, SAVINGS_CATEGORIES); }
166
+ function member(value, options) {
167
+ if (typeof value !== 'string' || !options.includes(value))
168
+ invalid();
169
+ return value;
170
+ }
171
+ function record(value, required, optional = []) {
172
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
173
+ invalid();
174
+ if (Object.keys(value).some(key => !required.includes(key) && !optional.includes(key))
175
+ || required.some(key => !Object.hasOwn(value, key)))
176
+ invalid();
177
+ return value;
178
+ }
179
+ function list(value, item) {
180
+ if (!Array.isArray(value))
181
+ invalid();
182
+ return value.map(entry => item(entry));
183
+ }
184
+ function text(value, maxBytes = SAVINGS_TEXT_BYTES) {
185
+ if (typeof value !== 'string' || bytes(value) > maxBytes || CONTROL.test(value) || LONE_SURROGATE.test(value))
186
+ invalid();
187
+ return value;
188
+ }
189
+ function index(value, length) {
190
+ if (!Number.isSafeInteger(value) || value < 0 || value >= length)
191
+ invalid();
192
+ return value;
193
+ }
194
+ function count(value) {
195
+ if (!Number.isSafeInteger(value) || value < 0)
196
+ invalid();
197
+ return value;
198
+ }
199
+ function signedCount(value) {
200
+ if (!Number.isSafeInteger(value))
201
+ invalid();
202
+ return value;
203
+ }
204
+ function signed(value) {
205
+ if (typeof value !== 'number' || !Number.isFinite(value))
206
+ invalid();
207
+ return value;
208
+ }
209
+ function unsigned(value) {
210
+ if (signed(value) < 0)
211
+ invalid();
212
+ return value;
213
+ }
214
+ function instant(value) {
215
+ if (typeof value !== 'string' || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/u.test(value) || !Number.isFinite(Date.parse(value))
216
+ || new Date(value).toISOString() !== value)
217
+ invalid();
218
+ return value;
219
+ }
220
+ function day(value) {
221
+ if (typeof value !== 'string' || !/^\d{4}-\d\d-\d\d$/u.test(value))
222
+ invalid();
223
+ instant(`${value}T00:00:00.000Z`);
224
+ return value;
225
+ }
226
+ function month(value) {
227
+ if (typeof value !== 'string' || !/^\d{4}-\d\d$/u.test(value))
228
+ invalid();
229
+ day(`${value}-01`);
230
+ return value;
231
+ }
232
+ //# sourceMappingURL=data.js.map