@warmdrift/kgauto-compiler 2.0.0-alpha.9 → 2.0.0-alpha.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -46
- package/dist/brain-proxy.d.mts +113 -0
- package/dist/brain-proxy.d.ts +113 -0
- package/dist/brain-proxy.js +193 -0
- package/dist/brain-proxy.mjs +6 -0
- package/dist/chunk-54IXD5BT.mjs +858 -0
- package/dist/chunk-65ZMX5OT.mjs +169 -0
- package/dist/chunk-AUZTO6Q5.mjs +219 -0
- package/dist/{chunk-5TI6PNSK.mjs → chunk-BVEXV5KC.mjs} +11 -0
- package/dist/chunk-NBO4R5PC.mjs +313 -0
- package/dist/chunk-P3TOAEG4.mjs +56 -0
- package/dist/chunk-RO22VFIF.mjs +29 -0
- package/dist/chunk-T53ISC2F.mjs +2008 -0
- package/dist/dialect.d.mts +41 -3
- package/dist/dialect.d.ts +41 -3
- package/dist/dialect.js +14 -2
- package/dist/dialect.mjs +5 -3
- package/dist/glassbox/index.d.mts +59 -0
- package/dist/glassbox/index.d.ts +59 -0
- package/dist/glassbox/index.js +312 -0
- package/dist/glassbox/index.mjs +12 -0
- package/dist/glassbox-routes/format.d.mts +24 -0
- package/dist/glassbox-routes/format.d.ts +24 -0
- package/dist/glassbox-routes/format.js +86 -0
- package/dist/glassbox-routes/format.mjs +18 -0
- package/dist/glassbox-routes/index.d.mts +191 -0
- package/dist/glassbox-routes/index.d.ts +191 -0
- package/dist/glassbox-routes/index.js +3326 -0
- package/dist/glassbox-routes/index.mjs +668 -0
- package/dist/glassbox-routes/react/index.d.mts +74 -0
- package/dist/glassbox-routes/react/index.d.ts +74 -0
- package/dist/glassbox-routes/react/index.js +819 -0
- package/dist/glassbox-routes/react/index.mjs +754 -0
- package/dist/index.d.mts +3782 -99
- package/dist/index.d.ts +3782 -99
- package/dist/index.js +10970 -2043
- package/dist/index.mjs +6279 -276
- package/dist/ir-CTx026t0.d.ts +1887 -0
- package/dist/ir-DeYMLWge.d.mts +1887 -0
- package/dist/key-health.d.mts +166 -0
- package/dist/key-health.d.ts +166 -0
- package/dist/key-health.js +247 -0
- package/dist/key-health.mjs +12 -0
- package/dist/profiles.d.mts +352 -2
- package/dist/profiles.d.ts +352 -2
- package/dist/profiles.js +1412 -52
- package/dist/profiles.mjs +19 -1
- package/dist/types-BKbRtmUb.d.ts +131 -0
- package/dist/types-Cp9ot1HV.d.ts +142 -0
- package/dist/types-DD36cCbZ.d.mts +142 -0
- package/dist/types-cBzinzUR.d.mts +131 -0
- package/package.json +62 -9
- package/dist/chunk-3KVKELZN.mjs +0 -657
- package/dist/profiles-BYVOc1eW.d.ts +0 -700
- package/dist/profiles-NUZOIzGr.d.mts +0 -700
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@warmdrift/kgauto-compiler/key-health` — per-consumer provider-key health
|
|
3
|
+
* endpoint factory (alpha.58, KG-fleet-v1 phase 1).
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* Consumers (playbacksam, inspire-central, tt-intelligence, global-expansion)
|
|
8
|
+
* hold their own provider API keys in their own Vercel envs — the library
|
|
9
|
+
* never sees keys centrally, by design. That means the operator dashboard has
|
|
10
|
+
* no way to answer "is PB's DeepSeek key still valid?" without a per-consumer
|
|
11
|
+
* probe surface. This factory is that surface: the consumer mounts ONE route,
|
|
12
|
+
* the dashboard polls it with the consumer's existing `KGAUTO_INGEST_SECRET`
|
|
13
|
+
* bearer, and gets back a validity grid for every canonical provider key.
|
|
14
|
+
*
|
|
15
|
+
* Probes are the cheapest authenticated GETs each provider offers (model
|
|
16
|
+
* listings; DeepSeek's balance endpoint) — never a completion call, zero
|
|
17
|
+
* token cost. Keys themselves NEVER appear in any response: only presence,
|
|
18
|
+
* validity, HTTP status, latency, and (DeepSeek only) account balance.
|
|
19
|
+
*
|
|
20
|
+
* Same design contract as `createBrainForwardRoutes` (brain-proxy.ts):
|
|
21
|
+
* web-standard Request→Response, never throws (every path returns a
|
|
22
|
+
* `Response`), bearer gate, runs on Edge/Workers/Node.
|
|
23
|
+
*
|
|
24
|
+
* ## Mounting recipes
|
|
25
|
+
*
|
|
26
|
+
* ### (a) Next.js app-router route handler — ONE file
|
|
27
|
+
*
|
|
28
|
+
* // app/api/kgauto/v2/keyhealth/route.ts
|
|
29
|
+
* import { createKeyHealthRoute } from '@warmdrift/kgauto-compiler/key-health';
|
|
30
|
+
* const routes = createKeyHealthRoute({
|
|
31
|
+
* appId: 'playbacksam',
|
|
32
|
+
* ingestSecret: process.env.KGAUTO_INGEST_SECRET!,
|
|
33
|
+
* });
|
|
34
|
+
* export const GET = (req: Request) => routes.handle(req);
|
|
35
|
+
*
|
|
36
|
+
* // middleware.ts — if the app gates routes behind auth middleware, the
|
|
37
|
+
* // existing brain-proxy public-path prefix ('/api/kgauto/v2/') already
|
|
38
|
+
* // covers this route. If the allowlist is per-path, add
|
|
39
|
+
* // '/api/kgauto/v2/keyhealth' or server-to-server GETs 307 to /login
|
|
40
|
+
* // (the tt-intel middleware-drift shape — see brain-proxy.ts).
|
|
41
|
+
*
|
|
42
|
+
* ### (b) Plain per-file Vercel function
|
|
43
|
+
*
|
|
44
|
+
* // api/kgauto/keyhealth.ts
|
|
45
|
+
* export const config = { runtime: 'edge' };
|
|
46
|
+
* import { createKeyHealthRoute } from '@warmdrift/kgauto-compiler/key-health';
|
|
47
|
+
* const routes = createKeyHealthRoute({
|
|
48
|
+
* appId: 'inspire-central',
|
|
49
|
+
* ingestSecret: process.env.KGAUTO_INGEST_SECRET!,
|
|
50
|
+
* });
|
|
51
|
+
* export default (req: Request) => routes.handle(req);
|
|
52
|
+
*
|
|
53
|
+
* ## Wire shape (LOCKED CONTRACT with the operator dashboard — do not rename)
|
|
54
|
+
*
|
|
55
|
+
* 200 {
|
|
56
|
+
* app_id: string,
|
|
57
|
+
* checked_at: string, // ISO timestamp
|
|
58
|
+
* library_version: string, // installed @warmdrift/kgauto-compiler
|
|
59
|
+
* // version (alpha.60+) — the dashboard's
|
|
60
|
+
* // live vendor-drift signal; absent on
|
|
61
|
+
* // pre-alpha.60 mounts, render "—"
|
|
62
|
+
* keys: [ // sorted by provider name
|
|
63
|
+
* {
|
|
64
|
+
* provider: 'anthropic' | 'deepseek' | 'google' | 'openai',
|
|
65
|
+
* env: string, // canonical env var name probed
|
|
66
|
+
* present: boolean, // key present (non-empty after trim)
|
|
67
|
+
* valid: boolean | null, // true 2xx / false 401·403 / null unknown
|
|
68
|
+
* status?: number, // probe HTTP status when a response landed
|
|
69
|
+
* latency_ms?: number, // probe round-trip
|
|
70
|
+
* balance_usd?: number, // DeepSeek only, when currency is USD
|
|
71
|
+
* detail?: string, // e.g. 'timeout', 'http_500', 'key_absent'
|
|
72
|
+
* }
|
|
73
|
+
* ]
|
|
74
|
+
* }
|
|
75
|
+
*/
|
|
76
|
+
type KeyHealthProvider = 'anthropic' | 'deepseek' | 'google' | 'moonshot' | 'openai';
|
|
77
|
+
interface KeyHealthConfig {
|
|
78
|
+
/** Consumer app id echoed as `app_id` in the response (e.g. 'playbacksam'). */
|
|
79
|
+
appId: string;
|
|
80
|
+
/** Bearer token the poller must present (consumer's existing KGAUTO_INGEST_SECRET). */
|
|
81
|
+
ingestSecret: string;
|
|
82
|
+
/** Optional fetch impl for tests. */
|
|
83
|
+
fetchImpl?: typeof fetch;
|
|
84
|
+
/** Env source; defaults to process.env. Test injection point. */
|
|
85
|
+
env?: Record<string, string | undefined>;
|
|
86
|
+
/** Per-provider probe timeout in ms. Default 3000. */
|
|
87
|
+
timeoutMs?: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* alpha.81 — a NON-REVERSIBLE identity for a provider key, so the fleet can
|
|
91
|
+
* answer "are two consumers on the same key?" without any consumer, or the
|
|
92
|
+
* brain, ever holding a key value.
|
|
93
|
+
*
|
|
94
|
+
* Origin: PB's `one-shared-provider-key-across-four-consumers` (s83). All four
|
|
95
|
+
* provider keys are Vercel team-level Shared Env Vars across four projects, so
|
|
96
|
+
* one exhausted account is a four-consumer outage that each consumer diagnoses
|
|
97
|
+
* locally as its own, and provider spend is a single undifferentiated number.
|
|
98
|
+
* PB's observation that only kgauto can see this is correct: the library runs
|
|
99
|
+
* inside every consumer's process and resolves the key there; no consumer can
|
|
100
|
+
* compare its key against a peer's.
|
|
101
|
+
*
|
|
102
|
+
* **Why truncation is the safety property, not an optimization.** The digest is
|
|
103
|
+
* cut to 12 hex chars (48 bits) — enough that a collision between the handful
|
|
104
|
+
* of keys in one portfolio is negligible, far too little to verify a guessed
|
|
105
|
+
* key against. Provider keys are high-entropy random strings, so even the full
|
|
106
|
+
* digest would not be brute-forceable; the truncation means the stored value is
|
|
107
|
+
* not a verification oracle even if the key space were later reduced. The
|
|
108
|
+
* domain-separation prefix keeps these digests useless against any hash
|
|
109
|
+
* computed for another purpose.
|
|
110
|
+
*
|
|
111
|
+
* Never log, return, or persist the key itself. This function is the only
|
|
112
|
+
* sanctioned way a key becomes a value that may leave the process.
|
|
113
|
+
*/
|
|
114
|
+
declare const KEY_FINGERPRINT_DOMAIN = "kgauto-key-fingerprint-v1:";
|
|
115
|
+
declare const KEY_FINGERPRINT_LENGTH = 12;
|
|
116
|
+
declare function keyFingerprint(key: string): Promise<string | undefined>;
|
|
117
|
+
interface KeyHealthResult {
|
|
118
|
+
provider: KeyHealthProvider;
|
|
119
|
+
/** Canonical env var name probed (the resolved one for Google). */
|
|
120
|
+
env: string;
|
|
121
|
+
/** Key present (non-empty after trim) in env. */
|
|
122
|
+
present: boolean;
|
|
123
|
+
/** true = provider accepted the key (2xx); false = rejected (401/403); null = unknown (absent / timeout / 5xx / 429 / network). */
|
|
124
|
+
valid: boolean | null;
|
|
125
|
+
/** HTTP status of the probe, when a response landed. */
|
|
126
|
+
status?: number;
|
|
127
|
+
/** Probe round-trip in ms, when a probe ran. */
|
|
128
|
+
latency_ms?: number;
|
|
129
|
+
/** DeepSeek only: account balance in USD when the balance API reports USD. */
|
|
130
|
+
balance_usd?: number;
|
|
131
|
+
/** Failure class when valid is null with a probe attempted (e.g. 'timeout', 'http_500') or 'key_absent'. */
|
|
132
|
+
detail?: string;
|
|
133
|
+
/**
|
|
134
|
+
* alpha.81 — truncated, domain-separated SHA-256 of the resolved key. NEVER
|
|
135
|
+
* the key. Identical values across two consumers mean they resolve the same
|
|
136
|
+
* credential; that is the only question it can answer. Absent when the key is
|
|
137
|
+
* absent or the runtime exposes no WebCrypto. See {@link keyFingerprint}.
|
|
138
|
+
*/
|
|
139
|
+
key_fingerprint?: string;
|
|
140
|
+
}
|
|
141
|
+
interface KeyHealthResponseBody {
|
|
142
|
+
app_id: string;
|
|
143
|
+
checked_at: string;
|
|
144
|
+
/**
|
|
145
|
+
* alpha.60 — installed library version (`LIBRARY_VERSION`). Lets the
|
|
146
|
+
* operator dashboard render per-consumer vendor drift from the same poll
|
|
147
|
+
* it already makes; absent on pre-alpha.60 mounts.
|
|
148
|
+
*/
|
|
149
|
+
library_version: string;
|
|
150
|
+
keys: KeyHealthResult[];
|
|
151
|
+
}
|
|
152
|
+
interface KeyHealthRoute {
|
|
153
|
+
/**
|
|
154
|
+
* Web-standard handler. GET only. Never throws — every error path returns
|
|
155
|
+
* a `Response`.
|
|
156
|
+
*/
|
|
157
|
+
handle(req: Request): Promise<Response>;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Create the per-consumer key-health route. See module docstring for the
|
|
161
|
+
* mounting recipes ((a) Next.js route handler, (b) per-file Vercel function)
|
|
162
|
+
* and the locked wire shape.
|
|
163
|
+
*/
|
|
164
|
+
declare function createKeyHealthRoute(config: KeyHealthConfig): KeyHealthRoute;
|
|
165
|
+
|
|
166
|
+
export { KEY_FINGERPRINT_DOMAIN, KEY_FINGERPRINT_LENGTH, type KeyHealthConfig, type KeyHealthProvider, type KeyHealthResponseBody, type KeyHealthResult, type KeyHealthRoute, createKeyHealthRoute, keyFingerprint };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@warmdrift/kgauto-compiler/key-health` — per-consumer provider-key health
|
|
3
|
+
* endpoint factory (alpha.58, KG-fleet-v1 phase 1).
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* Consumers (playbacksam, inspire-central, tt-intelligence, global-expansion)
|
|
8
|
+
* hold their own provider API keys in their own Vercel envs — the library
|
|
9
|
+
* never sees keys centrally, by design. That means the operator dashboard has
|
|
10
|
+
* no way to answer "is PB's DeepSeek key still valid?" without a per-consumer
|
|
11
|
+
* probe surface. This factory is that surface: the consumer mounts ONE route,
|
|
12
|
+
* the dashboard polls it with the consumer's existing `KGAUTO_INGEST_SECRET`
|
|
13
|
+
* bearer, and gets back a validity grid for every canonical provider key.
|
|
14
|
+
*
|
|
15
|
+
* Probes are the cheapest authenticated GETs each provider offers (model
|
|
16
|
+
* listings; DeepSeek's balance endpoint) — never a completion call, zero
|
|
17
|
+
* token cost. Keys themselves NEVER appear in any response: only presence,
|
|
18
|
+
* validity, HTTP status, latency, and (DeepSeek only) account balance.
|
|
19
|
+
*
|
|
20
|
+
* Same design contract as `createBrainForwardRoutes` (brain-proxy.ts):
|
|
21
|
+
* web-standard Request→Response, never throws (every path returns a
|
|
22
|
+
* `Response`), bearer gate, runs on Edge/Workers/Node.
|
|
23
|
+
*
|
|
24
|
+
* ## Mounting recipes
|
|
25
|
+
*
|
|
26
|
+
* ### (a) Next.js app-router route handler — ONE file
|
|
27
|
+
*
|
|
28
|
+
* // app/api/kgauto/v2/keyhealth/route.ts
|
|
29
|
+
* import { createKeyHealthRoute } from '@warmdrift/kgauto-compiler/key-health';
|
|
30
|
+
* const routes = createKeyHealthRoute({
|
|
31
|
+
* appId: 'playbacksam',
|
|
32
|
+
* ingestSecret: process.env.KGAUTO_INGEST_SECRET!,
|
|
33
|
+
* });
|
|
34
|
+
* export const GET = (req: Request) => routes.handle(req);
|
|
35
|
+
*
|
|
36
|
+
* // middleware.ts — if the app gates routes behind auth middleware, the
|
|
37
|
+
* // existing brain-proxy public-path prefix ('/api/kgauto/v2/') already
|
|
38
|
+
* // covers this route. If the allowlist is per-path, add
|
|
39
|
+
* // '/api/kgauto/v2/keyhealth' or server-to-server GETs 307 to /login
|
|
40
|
+
* // (the tt-intel middleware-drift shape — see brain-proxy.ts).
|
|
41
|
+
*
|
|
42
|
+
* ### (b) Plain per-file Vercel function
|
|
43
|
+
*
|
|
44
|
+
* // api/kgauto/keyhealth.ts
|
|
45
|
+
* export const config = { runtime: 'edge' };
|
|
46
|
+
* import { createKeyHealthRoute } from '@warmdrift/kgauto-compiler/key-health';
|
|
47
|
+
* const routes = createKeyHealthRoute({
|
|
48
|
+
* appId: 'inspire-central',
|
|
49
|
+
* ingestSecret: process.env.KGAUTO_INGEST_SECRET!,
|
|
50
|
+
* });
|
|
51
|
+
* export default (req: Request) => routes.handle(req);
|
|
52
|
+
*
|
|
53
|
+
* ## Wire shape (LOCKED CONTRACT with the operator dashboard — do not rename)
|
|
54
|
+
*
|
|
55
|
+
* 200 {
|
|
56
|
+
* app_id: string,
|
|
57
|
+
* checked_at: string, // ISO timestamp
|
|
58
|
+
* library_version: string, // installed @warmdrift/kgauto-compiler
|
|
59
|
+
* // version (alpha.60+) — the dashboard's
|
|
60
|
+
* // live vendor-drift signal; absent on
|
|
61
|
+
* // pre-alpha.60 mounts, render "—"
|
|
62
|
+
* keys: [ // sorted by provider name
|
|
63
|
+
* {
|
|
64
|
+
* provider: 'anthropic' | 'deepseek' | 'google' | 'openai',
|
|
65
|
+
* env: string, // canonical env var name probed
|
|
66
|
+
* present: boolean, // key present (non-empty after trim)
|
|
67
|
+
* valid: boolean | null, // true 2xx / false 401·403 / null unknown
|
|
68
|
+
* status?: number, // probe HTTP status when a response landed
|
|
69
|
+
* latency_ms?: number, // probe round-trip
|
|
70
|
+
* balance_usd?: number, // DeepSeek only, when currency is USD
|
|
71
|
+
* detail?: string, // e.g. 'timeout', 'http_500', 'key_absent'
|
|
72
|
+
* }
|
|
73
|
+
* ]
|
|
74
|
+
* }
|
|
75
|
+
*/
|
|
76
|
+
type KeyHealthProvider = 'anthropic' | 'deepseek' | 'google' | 'moonshot' | 'openai';
|
|
77
|
+
interface KeyHealthConfig {
|
|
78
|
+
/** Consumer app id echoed as `app_id` in the response (e.g. 'playbacksam'). */
|
|
79
|
+
appId: string;
|
|
80
|
+
/** Bearer token the poller must present (consumer's existing KGAUTO_INGEST_SECRET). */
|
|
81
|
+
ingestSecret: string;
|
|
82
|
+
/** Optional fetch impl for tests. */
|
|
83
|
+
fetchImpl?: typeof fetch;
|
|
84
|
+
/** Env source; defaults to process.env. Test injection point. */
|
|
85
|
+
env?: Record<string, string | undefined>;
|
|
86
|
+
/** Per-provider probe timeout in ms. Default 3000. */
|
|
87
|
+
timeoutMs?: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* alpha.81 — a NON-REVERSIBLE identity for a provider key, so the fleet can
|
|
91
|
+
* answer "are two consumers on the same key?" without any consumer, or the
|
|
92
|
+
* brain, ever holding a key value.
|
|
93
|
+
*
|
|
94
|
+
* Origin: PB's `one-shared-provider-key-across-four-consumers` (s83). All four
|
|
95
|
+
* provider keys are Vercel team-level Shared Env Vars across four projects, so
|
|
96
|
+
* one exhausted account is a four-consumer outage that each consumer diagnoses
|
|
97
|
+
* locally as its own, and provider spend is a single undifferentiated number.
|
|
98
|
+
* PB's observation that only kgauto can see this is correct: the library runs
|
|
99
|
+
* inside every consumer's process and resolves the key there; no consumer can
|
|
100
|
+
* compare its key against a peer's.
|
|
101
|
+
*
|
|
102
|
+
* **Why truncation is the safety property, not an optimization.** The digest is
|
|
103
|
+
* cut to 12 hex chars (48 bits) — enough that a collision between the handful
|
|
104
|
+
* of keys in one portfolio is negligible, far too little to verify a guessed
|
|
105
|
+
* key against. Provider keys are high-entropy random strings, so even the full
|
|
106
|
+
* digest would not be brute-forceable; the truncation means the stored value is
|
|
107
|
+
* not a verification oracle even if the key space were later reduced. The
|
|
108
|
+
* domain-separation prefix keeps these digests useless against any hash
|
|
109
|
+
* computed for another purpose.
|
|
110
|
+
*
|
|
111
|
+
* Never log, return, or persist the key itself. This function is the only
|
|
112
|
+
* sanctioned way a key becomes a value that may leave the process.
|
|
113
|
+
*/
|
|
114
|
+
declare const KEY_FINGERPRINT_DOMAIN = "kgauto-key-fingerprint-v1:";
|
|
115
|
+
declare const KEY_FINGERPRINT_LENGTH = 12;
|
|
116
|
+
declare function keyFingerprint(key: string): Promise<string | undefined>;
|
|
117
|
+
interface KeyHealthResult {
|
|
118
|
+
provider: KeyHealthProvider;
|
|
119
|
+
/** Canonical env var name probed (the resolved one for Google). */
|
|
120
|
+
env: string;
|
|
121
|
+
/** Key present (non-empty after trim) in env. */
|
|
122
|
+
present: boolean;
|
|
123
|
+
/** true = provider accepted the key (2xx); false = rejected (401/403); null = unknown (absent / timeout / 5xx / 429 / network). */
|
|
124
|
+
valid: boolean | null;
|
|
125
|
+
/** HTTP status of the probe, when a response landed. */
|
|
126
|
+
status?: number;
|
|
127
|
+
/** Probe round-trip in ms, when a probe ran. */
|
|
128
|
+
latency_ms?: number;
|
|
129
|
+
/** DeepSeek only: account balance in USD when the balance API reports USD. */
|
|
130
|
+
balance_usd?: number;
|
|
131
|
+
/** Failure class when valid is null with a probe attempted (e.g. 'timeout', 'http_500') or 'key_absent'. */
|
|
132
|
+
detail?: string;
|
|
133
|
+
/**
|
|
134
|
+
* alpha.81 — truncated, domain-separated SHA-256 of the resolved key. NEVER
|
|
135
|
+
* the key. Identical values across two consumers mean they resolve the same
|
|
136
|
+
* credential; that is the only question it can answer. Absent when the key is
|
|
137
|
+
* absent or the runtime exposes no WebCrypto. See {@link keyFingerprint}.
|
|
138
|
+
*/
|
|
139
|
+
key_fingerprint?: string;
|
|
140
|
+
}
|
|
141
|
+
interface KeyHealthResponseBody {
|
|
142
|
+
app_id: string;
|
|
143
|
+
checked_at: string;
|
|
144
|
+
/**
|
|
145
|
+
* alpha.60 — installed library version (`LIBRARY_VERSION`). Lets the
|
|
146
|
+
* operator dashboard render per-consumer vendor drift from the same poll
|
|
147
|
+
* it already makes; absent on pre-alpha.60 mounts.
|
|
148
|
+
*/
|
|
149
|
+
library_version: string;
|
|
150
|
+
keys: KeyHealthResult[];
|
|
151
|
+
}
|
|
152
|
+
interface KeyHealthRoute {
|
|
153
|
+
/**
|
|
154
|
+
* Web-standard handler. GET only. Never throws — every error path returns
|
|
155
|
+
* a `Response`.
|
|
156
|
+
*/
|
|
157
|
+
handle(req: Request): Promise<Response>;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Create the per-consumer key-health route. See module docstring for the
|
|
161
|
+
* mounting recipes ((a) Next.js route handler, (b) per-file Vercel function)
|
|
162
|
+
* and the locked wire shape.
|
|
163
|
+
*/
|
|
164
|
+
declare function createKeyHealthRoute(config: KeyHealthConfig): KeyHealthRoute;
|
|
165
|
+
|
|
166
|
+
export { KEY_FINGERPRINT_DOMAIN, KEY_FINGERPRINT_LENGTH, type KeyHealthConfig, type KeyHealthProvider, type KeyHealthResponseBody, type KeyHealthResult, type KeyHealthRoute, createKeyHealthRoute, keyFingerprint };
|
|
@@ -0,0 +1,247 @@
|
|
|
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
|
+
|
|
20
|
+
// src/key-health.ts
|
|
21
|
+
var key_health_exports = {};
|
|
22
|
+
__export(key_health_exports, {
|
|
23
|
+
KEY_FINGERPRINT_DOMAIN: () => KEY_FINGERPRINT_DOMAIN,
|
|
24
|
+
KEY_FINGERPRINT_LENGTH: () => KEY_FINGERPRINT_LENGTH,
|
|
25
|
+
createKeyHealthRoute: () => createKeyHealthRoute,
|
|
26
|
+
keyFingerprint: () => keyFingerprint
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(key_health_exports);
|
|
29
|
+
|
|
30
|
+
// src/version.ts
|
|
31
|
+
var LIBRARY_VERSION = "2.0.0-alpha.91";
|
|
32
|
+
|
|
33
|
+
// src/key-health.ts
|
|
34
|
+
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
35
|
+
var KEY_FINGERPRINT_DOMAIN = "kgauto-key-fingerprint-v1:";
|
|
36
|
+
var KEY_FINGERPRINT_LENGTH = 12;
|
|
37
|
+
async function keyFingerprint(key) {
|
|
38
|
+
const trimmed = key?.trim();
|
|
39
|
+
if (!trimmed) return void 0;
|
|
40
|
+
const subtle = globalThis.crypto?.subtle;
|
|
41
|
+
if (!subtle) return void 0;
|
|
42
|
+
const bytes = new TextEncoder().encode(KEY_FINGERPRINT_DOMAIN + trimmed);
|
|
43
|
+
const digest = await subtle.digest("SHA-256", bytes);
|
|
44
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, KEY_FINGERPRINT_LENGTH);
|
|
45
|
+
}
|
|
46
|
+
function jsonResponse(status, body) {
|
|
47
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
|
|
48
|
+
}
|
|
49
|
+
function bearerOf(req) {
|
|
50
|
+
const header = req.headers.get("Authorization") ?? "";
|
|
51
|
+
const match = /^Bearer\s+(.+)$/i.exec(header);
|
|
52
|
+
return match?.[1]?.trim() ?? "";
|
|
53
|
+
}
|
|
54
|
+
function requireString(name, value) {
|
|
55
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
56
|
+
throw new Error(`createKeyHealthRoute: ${name} is required`);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
function envKey(env, name) {
|
|
61
|
+
const v = env[name];
|
|
62
|
+
if (typeof v !== "string") return void 0;
|
|
63
|
+
const trimmed = v.trim();
|
|
64
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
65
|
+
}
|
|
66
|
+
var PROBE_SPECS = [
|
|
67
|
+
{
|
|
68
|
+
provider: "anthropic",
|
|
69
|
+
canonicalEnvName: "ANTHROPIC_API_KEY",
|
|
70
|
+
buildRequest: (key) => ({
|
|
71
|
+
url: "https://api.anthropic.com/v1/models?limit=1",
|
|
72
|
+
headers: { "x-api-key": key, "anthropic-version": "2023-06-01" }
|
|
73
|
+
})
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
provider: "deepseek",
|
|
77
|
+
canonicalEnvName: "DEEPSEEK_API_KEY",
|
|
78
|
+
buildRequest: (key) => ({
|
|
79
|
+
url: "https://api.deepseek.com/user/balance",
|
|
80
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
81
|
+
}),
|
|
82
|
+
parseBalanceUsd: (body) => {
|
|
83
|
+
const infos = body?.balance_infos;
|
|
84
|
+
if (!Array.isArray(infos) || infos.length === 0) return void 0;
|
|
85
|
+
const first = infos[0];
|
|
86
|
+
if (first?.currency !== "USD") return void 0;
|
|
87
|
+
const n = Number(first.total_balance);
|
|
88
|
+
return Number.isFinite(n) ? n : void 0;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
provider: "moonshot",
|
|
93
|
+
canonicalEnvName: "MOONSHOT_API_KEY",
|
|
94
|
+
buildRequest: (key) => ({
|
|
95
|
+
url: "https://api.moonshot.ai/v1/models",
|
|
96
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
97
|
+
})
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
provider: "google",
|
|
101
|
+
canonicalEnvName: "GEMINI_API_KEY",
|
|
102
|
+
buildRequest: (key) => ({
|
|
103
|
+
url: `https://generativelanguage.googleapis.com/v1beta/models?pageSize=1&key=${encodeURIComponent(key)}`,
|
|
104
|
+
headers: {}
|
|
105
|
+
})
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
provider: "openai",
|
|
109
|
+
canonicalEnvName: "OPENAI_API_KEY",
|
|
110
|
+
buildRequest: (key) => ({
|
|
111
|
+
url: "https://api.openai.com/v1/models",
|
|
112
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
];
|
|
116
|
+
function createKeyHealthRoute(config) {
|
|
117
|
+
const appId = requireString("appId", config.appId);
|
|
118
|
+
const ingestSecret = requireString("ingestSecret", config.ingestSecret);
|
|
119
|
+
const fetchFn = config.fetchImpl ?? fetch;
|
|
120
|
+
const timeoutMs = config.timeoutMs ?? 3e3;
|
|
121
|
+
async function probeProvider(spec, env) {
|
|
122
|
+
let key;
|
|
123
|
+
let envName = spec.canonicalEnvName;
|
|
124
|
+
if (spec.provider === "google") {
|
|
125
|
+
const gemini = envKey(env, "GEMINI_API_KEY");
|
|
126
|
+
const google = envKey(env, "GOOGLE_API_KEY");
|
|
127
|
+
const aiSdk = envKey(env, "GOOGLE_GENERATIVE_AI_API_KEY");
|
|
128
|
+
key = gemini ?? google ?? aiSdk;
|
|
129
|
+
envName = gemini ? "GEMINI_API_KEY" : google ? "GOOGLE_API_KEY" : aiSdk ? "GOOGLE_GENERATIVE_AI_API_KEY" : "GEMINI_API_KEY";
|
|
130
|
+
} else if (spec.provider === "moonshot") {
|
|
131
|
+
const moonshot = envKey(env, "MOONSHOT_API_KEY");
|
|
132
|
+
const kimi = envKey(env, "KIMI_API_KEY");
|
|
133
|
+
key = moonshot ?? kimi;
|
|
134
|
+
envName = moonshot ? "MOONSHOT_API_KEY" : kimi ? "KIMI_API_KEY" : "MOONSHOT_API_KEY";
|
|
135
|
+
} else {
|
|
136
|
+
key = envKey(env, spec.canonicalEnvName);
|
|
137
|
+
}
|
|
138
|
+
if (!key) {
|
|
139
|
+
return {
|
|
140
|
+
provider: spec.provider,
|
|
141
|
+
env: envName,
|
|
142
|
+
present: false,
|
|
143
|
+
valid: null,
|
|
144
|
+
detail: "key_absent"
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const fingerprint = await keyFingerprint(key);
|
|
148
|
+
const base = {
|
|
149
|
+
provider: spec.provider,
|
|
150
|
+
env: envName,
|
|
151
|
+
present: true,
|
|
152
|
+
valid: null,
|
|
153
|
+
...fingerprint ? { key_fingerprint: fingerprint } : {}
|
|
154
|
+
};
|
|
155
|
+
const { url, headers } = spec.buildRequest(key);
|
|
156
|
+
const started = Date.now();
|
|
157
|
+
let res;
|
|
158
|
+
try {
|
|
159
|
+
res = await fetchFn(url, {
|
|
160
|
+
method: "GET",
|
|
161
|
+
headers,
|
|
162
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
163
|
+
});
|
|
164
|
+
} catch (err) {
|
|
165
|
+
const isTimeout = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
166
|
+
return {
|
|
167
|
+
...base,
|
|
168
|
+
latency_ms: Date.now() - started,
|
|
169
|
+
// NEVER echo err.message — provider errors could theoretically carry
|
|
170
|
+
// request context; a fixed vocabulary keeps key material impossible.
|
|
171
|
+
detail: isTimeout ? "timeout" : "network_error"
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const latencyMs = Date.now() - started;
|
|
175
|
+
if (res.ok) {
|
|
176
|
+
const result = {
|
|
177
|
+
...base,
|
|
178
|
+
valid: true,
|
|
179
|
+
status: res.status,
|
|
180
|
+
latency_ms: latencyMs
|
|
181
|
+
};
|
|
182
|
+
if (spec.parseBalanceUsd) {
|
|
183
|
+
try {
|
|
184
|
+
const body = await res.json();
|
|
185
|
+
const balance = spec.parseBalanceUsd(body);
|
|
186
|
+
if (balance !== void 0) result.balance_usd = balance;
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
if (res.status === 401 || res.status === 403) {
|
|
193
|
+
return { ...base, valid: false, status: res.status, latency_ms: latencyMs };
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
...base,
|
|
197
|
+
valid: null,
|
|
198
|
+
status: res.status,
|
|
199
|
+
latency_ms: latencyMs,
|
|
200
|
+
detail: `http_${res.status}`
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
async function handle(req) {
|
|
204
|
+
try {
|
|
205
|
+
if (req.method !== "GET") {
|
|
206
|
+
return jsonResponse(405, { error: "method_not_allowed" });
|
|
207
|
+
}
|
|
208
|
+
if (bearerOf(req) !== ingestSecret) {
|
|
209
|
+
return jsonResponse(401, { error: "unauthorized" });
|
|
210
|
+
}
|
|
211
|
+
const env = config.env ?? process.env;
|
|
212
|
+
const settled = await Promise.allSettled(
|
|
213
|
+
PROBE_SPECS.map((spec) => probeProvider(spec, env))
|
|
214
|
+
);
|
|
215
|
+
const keys = settled.map((s, i) => {
|
|
216
|
+
if (s.status === "fulfilled") return s.value;
|
|
217
|
+
const spec = PROBE_SPECS[i];
|
|
218
|
+
return {
|
|
219
|
+
provider: spec.provider,
|
|
220
|
+
env: spec.canonicalEnvName,
|
|
221
|
+
present: true,
|
|
222
|
+
valid: null,
|
|
223
|
+
detail: "probe_failed"
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
keys.sort((a, b) => a.provider.localeCompare(b.provider));
|
|
227
|
+
const body = {
|
|
228
|
+
app_id: appId,
|
|
229
|
+
checked_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
230
|
+
library_version: LIBRARY_VERSION,
|
|
231
|
+
keys
|
|
232
|
+
};
|
|
233
|
+
return jsonResponse(200, body);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
void err;
|
|
236
|
+
return jsonResponse(500, { error: "key_health_internal_error" });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return { handle };
|
|
240
|
+
}
|
|
241
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
242
|
+
0 && (module.exports = {
|
|
243
|
+
KEY_FINGERPRINT_DOMAIN,
|
|
244
|
+
KEY_FINGERPRINT_LENGTH,
|
|
245
|
+
createKeyHealthRoute,
|
|
246
|
+
keyFingerprint
|
|
247
|
+
});
|