lazyclaw 3.88.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.
@@ -0,0 +1,61 @@
1
+ // Structural integrity check for cfg.json. Distinct from runtime
2
+ // "doctor" checks (provider available, key works) — this is purely
3
+ // about shape: types, known providers, rate-card form.
4
+ //
5
+ // Shared between `lazyclaw config validate` (CLI) and
6
+ // `GET /config/validate` (daemon) so both produce bit-for-bit
7
+ // identical output.
8
+
9
+ const KNOWN_KEYS = new Set(['provider', 'model', 'api-key', 'rates']);
10
+
11
+ /**
12
+ * @param {Record<string, unknown>} cfg
13
+ * @param {Record<string, unknown>} providers Registered providers map (keys = provider names)
14
+ * @returns {{ ok: boolean, issues: string[], warnings: string[] }}
15
+ */
16
+ export function validateConfig(cfg, providers) {
17
+ const issues = [];
18
+ const warnings = [];
19
+ const knownProviderSet = new Set(Object.keys(providers || {}));
20
+ // provider: optional but if present must be in PROVIDERS.
21
+ if (cfg.provider !== undefined) {
22
+ if (typeof cfg.provider !== 'string') {
23
+ issues.push(`config.provider must be a string (got ${typeof cfg.provider})`);
24
+ } else if (!knownProviderSet.has(cfg.provider)) {
25
+ issues.push(`config.provider "${cfg.provider}" is not in registered providers (registered: ${[...knownProviderSet].join(', ')})`);
26
+ }
27
+ }
28
+ if (cfg.model !== undefined && typeof cfg.model !== 'string') {
29
+ issues.push(`config.model must be a string (got ${typeof cfg.model})`);
30
+ }
31
+ if (cfg['api-key'] !== undefined && typeof cfg['api-key'] !== 'string') {
32
+ issues.push(`config['api-key'] must be a string (got ${typeof cfg['api-key']})`);
33
+ }
34
+ if (cfg.rates !== undefined) {
35
+ if (typeof cfg.rates !== 'object' || cfg.rates === null || Array.isArray(cfg.rates)) {
36
+ issues.push(`config.rates must be an object (got ${Array.isArray(cfg.rates) ? 'array' : typeof cfg.rates})`);
37
+ } else {
38
+ for (const key of Object.keys(cfg.rates)) {
39
+ if (!key.includes('/')) {
40
+ issues.push(`config.rates["${key}"]: expected "provider/model" shape (slash required)`);
41
+ continue;
42
+ }
43
+ const card = cfg.rates[key];
44
+ if (!card || typeof card !== 'object') {
45
+ issues.push(`config.rates["${key}"]: value must be an object`);
46
+ continue;
47
+ }
48
+ for (const required of ['inputPer1M', 'outputPer1M']) {
49
+ const v = card[required];
50
+ if (typeof v !== 'number' || !Number.isFinite(v) || v < 0) {
51
+ issues.push(`config.rates["${key}"].${required} must be a non-negative finite number (got ${JSON.stringify(v)})`);
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ for (const key of Object.keys(cfg)) {
58
+ if (!KNOWN_KEYS.has(key)) warnings.push(`unknown top-level key: ${key} (allowed: ${[...KNOWN_KEYS].join(', ')})`);
59
+ }
60
+ return { ok: issues.length === 0, issues, warnings };
61
+ }