@warmdrift/kgauto-compiler 2.0.0-alpha.3 → 2.0.0-alpha.31

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,59 @@
1
+ import { G as GlassboxEvent } from '../types-BjrIFPGe.mjs';
2
+ export { A as AdvisoryFiredData, C as CompileDoneData, a as CompileStartData, E as ExecuteAttemptData, b as ExecuteSuccessData, F as FallbackWalkedData, c as GLASSBOX_STREAM_TTL_MS, d as GlassboxEventKind, e as GlassboxPubSub } from '../types-BjrIFPGe.mjs';
3
+ import '../ir-De2AQtlr.mjs';
4
+ import '../dialect.mjs';
5
+
6
+ /**
7
+ * subscribe(traceId) — public Glass-Box subscription export.
8
+ *
9
+ * Consumer route handlers (e.g. Vercel Edge `/api/glassbox/stream`) import
10
+ * this and bridge the ReadableStream to SSE. The browser-side panel then
11
+ * renders events in real time:
12
+ *
13
+ * import { subscribe } from '@warmdrift/kgauto-compiler/glassbox';
14
+ *
15
+ * export async function GET(req: Request) {
16
+ * const { searchParams } = new URL(req.url);
17
+ * const traceId = searchParams.get('traceId');
18
+ * if (!traceId) return new Response('missing traceId', { status: 400 });
19
+ * const stream = subscribe(traceId);
20
+ * // ... bridge ReadableStream<GlassboxEvent> → SSE here ...
21
+ * }
22
+ *
23
+ * Stream behavior:
24
+ * - Emits events for `traceId` as they're published.
25
+ * - Closes cleanly 60s after the last event (rolling TTL).
26
+ * - If no events arrive within 60s of subscription, closes empty.
27
+ * - Multiple subscribers on the same traceId all fan out.
28
+ *
29
+ * No replay: subscribe() picks up only events published AFTER subscription.
30
+ * Replay is the brain-poll surface's job (see design doc).
31
+ */
32
+
33
+ /**
34
+ * Subscribe to Glass-Box events for a traceId. Returns a ReadableStream
35
+ * that yields GlassboxEvent objects until the per-trace TTL elapses.
36
+ *
37
+ * Cancelling the stream (consumer disconnect, AbortController, etc.) tears
38
+ * down the subscription cleanly via the underlying adapter.
39
+ */
40
+ declare function subscribe(traceId: string): ReadableStream<GlassboxEvent>;
41
+ /**
42
+ * Subscribe to Glass-Box events for ALL traces under an appId — the
43
+ * "tail-all" surface (alpha.26). Backs the extension's default Live mode:
44
+ * rather than needing a specific traceId ahead of time, the panel sees
45
+ * every event for the configured consumer as calls happen.
46
+ *
47
+ * Per-app routing fires only when emit call-sites pass `appId` (the typed
48
+ * builders thread `ir.appId` automatically). Per-trace channel continues to
49
+ * fire for `subscribe(traceId)` callers — both arms are independent.
50
+ *
51
+ * Cross-tenant isolation: the consumer's `/api/glassbox/stream` uses the
52
+ * factory's configured appId — no caller-supplied `?appId=` URL parameter,
53
+ * so a consumer cannot subscribe to another consumer's stream.
54
+ */
55
+ declare function subscribeApp({ appId, }: {
56
+ appId: string;
57
+ }): ReadableStream<GlassboxEvent>;
58
+
59
+ export { GlassboxEvent, subscribe, subscribeApp };
@@ -0,0 +1,59 @@
1
+ import { G as GlassboxEvent } from '../types-D_JAhCv4.js';
2
+ export { A as AdvisoryFiredData, C as CompileDoneData, a as CompileStartData, E as ExecuteAttemptData, b as ExecuteSuccessData, F as FallbackWalkedData, c as GLASSBOX_STREAM_TTL_MS, d as GlassboxEventKind, e as GlassboxPubSub } from '../types-D_JAhCv4.js';
3
+ import '../ir-BIAT9gJk.js';
4
+ import '../dialect.js';
5
+
6
+ /**
7
+ * subscribe(traceId) — public Glass-Box subscription export.
8
+ *
9
+ * Consumer route handlers (e.g. Vercel Edge `/api/glassbox/stream`) import
10
+ * this and bridge the ReadableStream to SSE. The browser-side panel then
11
+ * renders events in real time:
12
+ *
13
+ * import { subscribe } from '@warmdrift/kgauto-compiler/glassbox';
14
+ *
15
+ * export async function GET(req: Request) {
16
+ * const { searchParams } = new URL(req.url);
17
+ * const traceId = searchParams.get('traceId');
18
+ * if (!traceId) return new Response('missing traceId', { status: 400 });
19
+ * const stream = subscribe(traceId);
20
+ * // ... bridge ReadableStream<GlassboxEvent> → SSE here ...
21
+ * }
22
+ *
23
+ * Stream behavior:
24
+ * - Emits events for `traceId` as they're published.
25
+ * - Closes cleanly 60s after the last event (rolling TTL).
26
+ * - If no events arrive within 60s of subscription, closes empty.
27
+ * - Multiple subscribers on the same traceId all fan out.
28
+ *
29
+ * No replay: subscribe() picks up only events published AFTER subscription.
30
+ * Replay is the brain-poll surface's job (see design doc).
31
+ */
32
+
33
+ /**
34
+ * Subscribe to Glass-Box events for a traceId. Returns a ReadableStream
35
+ * that yields GlassboxEvent objects until the per-trace TTL elapses.
36
+ *
37
+ * Cancelling the stream (consumer disconnect, AbortController, etc.) tears
38
+ * down the subscription cleanly via the underlying adapter.
39
+ */
40
+ declare function subscribe(traceId: string): ReadableStream<GlassboxEvent>;
41
+ /**
42
+ * Subscribe to Glass-Box events for ALL traces under an appId — the
43
+ * "tail-all" surface (alpha.26). Backs the extension's default Live mode:
44
+ * rather than needing a specific traceId ahead of time, the panel sees
45
+ * every event for the configured consumer as calls happen.
46
+ *
47
+ * Per-app routing fires only when emit call-sites pass `appId` (the typed
48
+ * builders thread `ir.appId` automatically). Per-trace channel continues to
49
+ * fire for `subscribe(traceId)` callers — both arms are independent.
50
+ *
51
+ * Cross-tenant isolation: the consumer's `/api/glassbox/stream` uses the
52
+ * factory's configured appId — no caller-supplied `?appId=` URL parameter,
53
+ * so a consumer cannot subscribe to another consumer's stream.
54
+ */
55
+ declare function subscribeApp({ appId, }: {
56
+ appId: string;
57
+ }): ReadableStream<GlassboxEvent>;
58
+
59
+ export { GlassboxEvent, subscribe, subscribeApp };
@@ -0,0 +1,312 @@
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/glassbox/index.ts
21
+ var glassbox_exports = {};
22
+ __export(glassbox_exports, {
23
+ GLASSBOX_STREAM_TTL_MS: () => GLASSBOX_STREAM_TTL_MS,
24
+ subscribe: () => subscribe,
25
+ subscribeApp: () => subscribeApp
26
+ });
27
+ module.exports = __toCommonJS(glassbox_exports);
28
+
29
+ // src/glassbox/types.ts
30
+ var GLASSBOX_STREAM_TTL_MS = 6e4;
31
+
32
+ // src/glassbox/pubsub-memory.ts
33
+ var MemoryPubSub = class {
34
+ subscribers = /* @__PURE__ */ new Map();
35
+ async publish(channelKey, event) {
36
+ const subs = this.subscribers.get(channelKey);
37
+ if (!subs || subs.size === 0) return;
38
+ for (const sub of subs) {
39
+ if (sub.closed) continue;
40
+ try {
41
+ sub.controller.enqueue(event);
42
+ } catch {
43
+ sub.closed = true;
44
+ continue;
45
+ }
46
+ this.refreshTtl(channelKey, sub);
47
+ }
48
+ }
49
+ subscribe(channelKey) {
50
+ const self = this;
51
+ let sub;
52
+ return new ReadableStream({
53
+ start(controller) {
54
+ sub = {
55
+ controller,
56
+ ttlTimer: setTimeout(() => {
57
+ self.closeSubscriber(channelKey, sub);
58
+ }, GLASSBOX_STREAM_TTL_MS),
59
+ closed: false
60
+ };
61
+ let set = self.subscribers.get(channelKey);
62
+ if (!set) {
63
+ set = /* @__PURE__ */ new Set();
64
+ self.subscribers.set(channelKey, set);
65
+ }
66
+ set.add(sub);
67
+ },
68
+ cancel() {
69
+ if (sub) self.removeSubscriber(channelKey, sub);
70
+ }
71
+ });
72
+ }
73
+ /**
74
+ * Refresh the rolling TTL for a subscriber after an event lands. Replaces
75
+ * the existing timer with a fresh 60s one.
76
+ */
77
+ refreshTtl(channelKey, sub) {
78
+ clearTimeout(sub.ttlTimer);
79
+ sub.ttlTimer = setTimeout(() => {
80
+ this.closeSubscriber(channelKey, sub);
81
+ }, GLASSBOX_STREAM_TTL_MS);
82
+ }
83
+ /**
84
+ * Close the subscriber's stream cleanly and remove from the fan-out set.
85
+ * Idempotent — safe to call multiple times.
86
+ */
87
+ closeSubscriber(channelKey, sub) {
88
+ if (sub.closed) return;
89
+ sub.closed = true;
90
+ clearTimeout(sub.ttlTimer);
91
+ try {
92
+ sub.controller.close();
93
+ } catch {
94
+ }
95
+ this.removeSubscriber(channelKey, sub);
96
+ }
97
+ removeSubscriber(channelKey, sub) {
98
+ clearTimeout(sub.ttlTimer);
99
+ const set = this.subscribers.get(channelKey);
100
+ if (!set) return;
101
+ set.delete(sub);
102
+ if (set.size === 0) this.subscribers.delete(channelKey);
103
+ }
104
+ /**
105
+ * Test-only reset. Tears down all subscribers, clears all state. Calling
106
+ * outside of tests is harmless but cancels every active stream.
107
+ */
108
+ _reset() {
109
+ for (const [, set] of this.subscribers) {
110
+ for (const sub of set) {
111
+ this.closeSubscriber("", sub);
112
+ }
113
+ }
114
+ this.subscribers.clear();
115
+ }
116
+ };
117
+
118
+ // src/glassbox/pubsub-upstash.ts
119
+ var UpstashPubSub = class {
120
+ url;
121
+ token;
122
+ fetchImpl;
123
+ blockMs;
124
+ maxLen;
125
+ constructor(cfg) {
126
+ this.url = cfg.url.replace(/\/$/, "");
127
+ this.token = cfg.token;
128
+ this.fetchImpl = cfg.fetchImpl ?? globalThis.fetch.bind(globalThis);
129
+ this.blockMs = cfg.blockMs ?? 100;
130
+ this.maxLen = cfg.maxLen ?? 100;
131
+ }
132
+ async publish(channelKey, event) {
133
+ const key = channelKey;
134
+ const payload = JSON.stringify(event);
135
+ await this.cmd([
136
+ "XADD",
137
+ key,
138
+ "MAXLEN",
139
+ "~",
140
+ String(this.maxLen),
141
+ "*",
142
+ "event",
143
+ payload
144
+ ]);
145
+ await this.cmd(["EXPIRE", key, String(Math.ceil(GLASSBOX_STREAM_TTL_MS / 1e3))]);
146
+ }
147
+ subscribe(channelKey) {
148
+ const key = channelKey;
149
+ const self = this;
150
+ let cursor = "$";
151
+ let cancelled = false;
152
+ let ttlDeadline = Date.now() + GLASSBOX_STREAM_TTL_MS;
153
+ return new ReadableStream({
154
+ async start(controller) {
155
+ try {
156
+ while (!cancelled && Date.now() < ttlDeadline) {
157
+ const resp = await self.cmd([
158
+ "XREAD",
159
+ "BLOCK",
160
+ String(self.blockMs),
161
+ "STREAMS",
162
+ key,
163
+ cursor
164
+ ]);
165
+ if (cancelled) break;
166
+ const parsed = parseXReadResult(resp.result);
167
+ if (parsed.entries.length === 0) {
168
+ continue;
169
+ }
170
+ for (const entry of parsed.entries) {
171
+ const evt = decodeEvent(entry.fields);
172
+ if (evt) {
173
+ try {
174
+ controller.enqueue(evt);
175
+ } catch {
176
+ cancelled = true;
177
+ break;
178
+ }
179
+ }
180
+ cursor = entry.id;
181
+ }
182
+ ttlDeadline = Date.now() + GLASSBOX_STREAM_TTL_MS;
183
+ }
184
+ } catch (err) {
185
+ if (!cancelled) {
186
+ try {
187
+ controller.error(err);
188
+ } catch {
189
+ }
190
+ return;
191
+ }
192
+ }
193
+ try {
194
+ controller.close();
195
+ } catch {
196
+ }
197
+ },
198
+ cancel() {
199
+ cancelled = true;
200
+ }
201
+ });
202
+ }
203
+ async cmd(args) {
204
+ const res = await this.fetchImpl(this.url, {
205
+ method: "POST",
206
+ headers: {
207
+ Authorization: `Bearer ${this.token}`,
208
+ "Content-Type": "application/json"
209
+ },
210
+ body: JSON.stringify(args)
211
+ });
212
+ if (!res.ok) {
213
+ throw new Error(`Upstash ${args[0]} failed: HTTP ${res.status}`);
214
+ }
215
+ const json = await res.json();
216
+ if (json.error) {
217
+ throw new Error(`Upstash ${args[0]} failed: ${json.error}`);
218
+ }
219
+ return json;
220
+ }
221
+ };
222
+ function traceChannel(traceId) {
223
+ return `glassbox:trace:${traceId}`;
224
+ }
225
+ function appChannel(appId) {
226
+ return `glassbox:app:${appId}`;
227
+ }
228
+ function decodeEvent(fields) {
229
+ const raw = fields["event"];
230
+ if (!raw) return void 0;
231
+ try {
232
+ const parsed = JSON.parse(raw);
233
+ if (typeof parsed.kind === "string" && typeof parsed.at === "number") {
234
+ return parsed;
235
+ }
236
+ return void 0;
237
+ } catch {
238
+ return void 0;
239
+ }
240
+ }
241
+ function parseXReadResult(raw) {
242
+ if (!Array.isArray(raw)) return { entries: [] };
243
+ const entries = [];
244
+ for (const stream of raw) {
245
+ if (!Array.isArray(stream) || stream.length < 2) continue;
246
+ const streamEntries = stream[1];
247
+ if (!Array.isArray(streamEntries)) continue;
248
+ for (const entry of streamEntries) {
249
+ if (!Array.isArray(entry) || entry.length < 2) continue;
250
+ const id = String(entry[0]);
251
+ const flat = entry[1];
252
+ if (!Array.isArray(flat)) continue;
253
+ const fields = {};
254
+ for (let i = 0; i < flat.length; i += 2) {
255
+ const k = flat[i];
256
+ const v = flat[i + 1];
257
+ if (typeof k === "string") fields[k] = String(v ?? "");
258
+ }
259
+ entries.push({ id, fields });
260
+ }
261
+ }
262
+ return { entries };
263
+ }
264
+
265
+ // src/glassbox/emit.ts
266
+ var activePubSub;
267
+ function getPubSub() {
268
+ if (activePubSub) return activePubSub;
269
+ const url = readEnv("UPSTASH_REDIS_URL");
270
+ const token = readEnv("UPSTASH_REDIS_TOKEN");
271
+ if (url && token) {
272
+ activePubSub = new UpstashPubSub({ url, token });
273
+ } else {
274
+ activePubSub = new MemoryPubSub();
275
+ }
276
+ return activePubSub;
277
+ }
278
+ function readEnv(key) {
279
+ try {
280
+ if (typeof process !== "undefined" && process.env) {
281
+ const v = process.env[key];
282
+ return v && v.trim() !== "" ? v : void 0;
283
+ }
284
+ } catch {
285
+ }
286
+ return void 0;
287
+ }
288
+
289
+ // src/glassbox/subscribe.ts
290
+ function emptyStream() {
291
+ return new ReadableStream({
292
+ start(controller) {
293
+ controller.close();
294
+ }
295
+ });
296
+ }
297
+ function subscribe(traceId) {
298
+ if (!traceId) return emptyStream();
299
+ return getPubSub().subscribe(traceChannel(traceId));
300
+ }
301
+ function subscribeApp({
302
+ appId
303
+ }) {
304
+ if (!appId) return emptyStream();
305
+ return getPubSub().subscribe(appChannel(appId));
306
+ }
307
+ // Annotate the CommonJS export names for ESM import in node:
308
+ 0 && (module.exports = {
309
+ GLASSBOX_STREAM_TTL_MS,
310
+ subscribe,
311
+ subscribeApp
312
+ });
@@ -0,0 +1,12 @@
1
+ import {
2
+ subscribe,
3
+ subscribeApp
4
+ } from "../chunk-RO22VFIF.mjs";
5
+ import {
6
+ GLASSBOX_STREAM_TTL_MS
7
+ } from "../chunk-NBO4R5PC.mjs";
8
+ export {
9
+ GLASSBOX_STREAM_TTL_MS,
10
+ subscribe,
11
+ subscribeApp
12
+ };
@@ -0,0 +1,242 @@
1
+ import { G as GlassboxEvent } from '../types-BjrIFPGe.mjs';
2
+ import { h as Adapter, u as SectionKind } from '../ir-De2AQtlr.mjs';
3
+ import '../dialect.mjs';
4
+
5
+ /**
6
+ * Internal config + hook types for createGlassboxRoutes().
7
+ *
8
+ * The public contract lives on `GlassboxRoutesConfig` in ./index.ts; these
9
+ * are the narrower per-handler shapes consumed by proxy.ts and stream.ts.
10
+ */
11
+
12
+ /**
13
+ * Wire contract for the Glass-Box Chrome extension's brain-poll endpoint.
14
+ *
15
+ * The list mode of `proxy(req)` returns `{ traces: TraceSummary[] }`. The
16
+ * detail mode (`?traceId=<id>`) returns a single `TraceDetail`. These are
17
+ * the camelCase shapes the extension renderer expects — distinct from the
18
+ * snake_case `compile_outcomes` row shape that PostgREST returns. The
19
+ * factory's typed `rowToSummary` / `rowToDetail` transformer is the single
20
+ * canonical boundary between the DB shape and the wire shape (see
21
+ * `feedback_typed_boundary_transformers.md` in kgauto memory for the rule).
22
+ */
23
+ interface TraceSummary {
24
+ traceId: string;
25
+ appId: string;
26
+ archetype: string;
27
+ target: string;
28
+ createdAt: string;
29
+ tokensIn: number;
30
+ tokensOut: number;
31
+ estimatedCostUsd: number;
32
+ }
33
+
34
+ interface AdvisoryRecord {
35
+ level: 'info' | 'warn' | 'critical';
36
+ /** Stable advisory identifier, e.g. "caching-off-on-claude". */
37
+ code: string;
38
+ /** Consumer-renderable message — no internal jargon ("L-040", "R3" etc.). */
39
+ message: string;
40
+ /** Optional secondary one-liner. Renders below `message` in italics. */
41
+ suggestion?: string;
42
+ /** Deep link to the relevant section of `interfaces/kgauto.md` or docs. */
43
+ docsUrl?: string;
44
+ /**
45
+ * alpha.28+ — closed-union adaptation hint surfaced by the advisor when
46
+ * the advisory can be auto-mitigated by a config knob. Renderer surfaces
47
+ * as `→ try toolOrchestration: 'sequential'` on the advisory row.
48
+ *
49
+ * MUST stay byte-identical to Builder C's
50
+ * `BestPracticeAdvisory.suggestedAdaptation` shape (verified at Phase 2
51
+ * integration; the Adapter type itself is the contract).
52
+ */
53
+ suggestedAdaptation?: Adapter;
54
+ }
55
+ /**
56
+ * Cost-equivalent alternative the chain could have served. Computed at
57
+ * detail-view time by `computeCounterfactuals()` against the served row's
58
+ * observed token counts + archetype + cache state. Up to 2 entries, sorted
59
+ * cheapest first.
60
+ */
61
+ interface TraceCounterfactual {
62
+ modelId: string;
63
+ estimatedCostUsd: number;
64
+ /** servedCostUsd - estimatedCostUsd (always > 0; only ≥10% savings kept). */
65
+ savingsUsd: number;
66
+ /** 0-100. */
67
+ savingsPercent: number;
68
+ /** Plain-English rationale tying archetype + perf score. */
69
+ reason: string;
70
+ }
71
+ /**
72
+ * Derived axis-health tri-state for the three Glass-Box dots
73
+ * (input-ratio · cache · fallback). Renderer reads; transformer computes.
74
+ *
75
+ * Thresholds (locked in design contract Phase 0):
76
+ * - inputRatio: green ≤ 0.65 · yellow 0.65–0.85 · red > 0.85
77
+ * - cache (only when historyCacheableTokens > 1000):
78
+ * green if inputCacheHitRatio ≥ 0.5
79
+ * yellow if 0.1 ≤ inputCacheHitRatio < 0.5
80
+ * red if inputCacheHitRatio < 0.1
81
+ * na if historyCacheableTokens ≤ 1000
82
+ * - fallback: red iff fellOverFrom !== undefined && fellOverFrom !== target
83
+ */
84
+ interface TraceHealth {
85
+ inputRatioStatus: 'green' | 'yellow' | 'red';
86
+ cacheStatus: 'green' | 'yellow' | 'red' | 'na';
87
+ fallbackStatus: 'green' | 'red';
88
+ }
89
+ /**
90
+ * alpha.29+ — wire-boundary representation of a translator section-rewrite.
91
+ *
92
+ * Distinct from the package-internal `SectionRewrite` type in `ir.ts`: this
93
+ * shape drops `originalText` + `transformedText` because those may carry
94
+ * consumer PII. The renderer shows the `rule` + `summary` only. Full text
95
+ * stays on `compile_outcomes.section_rewrites_applied` (Supabase row), gated
96
+ * by RLS for brain-side cross-app learning.
97
+ */
98
+ interface TraceSectionRewrite {
99
+ /** Stable id of the rewritten section. */
100
+ sectionId: string;
101
+ /** Section-kind discriminator that triggered the rewrite. */
102
+ kind: SectionKind;
103
+ /** Stable rule identifier (e.g. `'sequential-tool-cliff-below-floor'`). */
104
+ rule: string;
105
+ /**
106
+ * Plain-English one-liner describing what fired and why. Renderer surfaces
107
+ * this on the Coaching card; no internal jargon ("L-040", "below floor").
108
+ */
109
+ summary: string;
110
+ }
111
+ interface TraceDetail extends TraceSummary {
112
+ mutationsApplied: string[];
113
+ advisories: AdvisoryRecord[];
114
+ rawRequest?: string;
115
+ rawResponse?: string;
116
+ /** Set when consumer passed a forceModel / fallback fired. */
117
+ requestedModel?: string;
118
+ /** Provider finish reason — 'stop' / 'max_tokens' / 'tool_use' / etc. */
119
+ finishReason?: string;
120
+ /** Time to first token (ms); populated when provider surfaces it. */
121
+ ttftMs?: number;
122
+ /** End-to-end wall-clock (ms); from migration 018. */
123
+ totalMs?: number;
124
+ /** Tools kept after relevance pass. */
125
+ toolsCount?: number;
126
+ /** Number of history messages at compile time. */
127
+ historyDepth?: number;
128
+ /** Rendered system prompt size in characters. */
129
+ systemPromptChars?: number;
130
+ cacheReadInputTokens: number;
131
+ cacheCreationInputTokens: number;
132
+ historyCacheableTokens: number;
133
+ /** Derived: cacheReadInputTokens / max(tokensIn, 1). 0-1. */
134
+ inputCacheHitRatio: number;
135
+ fellOverFrom?: string;
136
+ fallbackReason?: 'rate_limit' | 'provider_auth_failed' | 'provider_error' | 'cliff' | 'cost_cap' | 'contract_violation';
137
+ /** Up to 2 alternatives. Empty array (not undefined) when none qualify. */
138
+ counterfactuals?: TraceCounterfactual[];
139
+ /** Undefined when 7d volume < 5/day (insufficient data). */
140
+ projectedDailyCostUsd?: number;
141
+ /**
142
+ * alpha.29+ — translator activity for this trace. Empty array (not
143
+ * undefined) when no rewrites fired or pre-019 row. Surfaced in the
144
+ * Glass-Box Coaching card as synthetic info-level rows. PII-safe by
145
+ * construction: only sectionId + kind + rule + summary cross the wire.
146
+ */
147
+ sectionRewritesApplied: TraceSectionRewrite[];
148
+ health: TraceHealth;
149
+ }
150
+
151
+ /**
152
+ * Public entry point for `@warmdrift/kgauto-compiler/glassbox-routes`.
153
+ *
154
+ * One factory call from a Vercel Edge consumer route handler gets you both
155
+ * the replay-query (`proxy`) and live-SSE (`stream`) endpoints that the
156
+ * Glass-Box Chrome panel reads. Wiring is ~6 lines per app:
157
+ *
158
+ * // app/api/glassbox/proxy/route.ts
159
+ * import { createGlassboxRoutes } from '@warmdrift/kgauto-compiler/glassbox-routes';
160
+ * const { proxy } = createGlassboxRoutes({
161
+ * installToken: process.env.GLASSBOX_INSTALL_TOKEN!,
162
+ * extensionId: process.env.GLASSBOX_EXTENSION_ID!,
163
+ * brainEndpoint: process.env.GLASSBOX_BRAIN_ENDPOINT!,
164
+ * brainJwt: process.env.GLASSBOX_BRAIN_JWT!, // scoped JWT (RLS via app_id claim)
165
+ * brainAnonKey: process.env.GLASSBOX_BRAIN_ANON_KEY!, // project anon/publishable key (Supabase apikey header)
166
+ * appId: 'playbacksam',
167
+ * });
168
+ * export { proxy as GET };
169
+ *
170
+ * Auth model (defense in depth):
171
+ * - Bearer install token → primary, constant-time compared
172
+ * - chrome-extension Origin → secondary CSRF gate
173
+ *
174
+ * Scrub: optional sanitization runs at the proxy boundary before events or
175
+ * rows leave the consumer's infrastructure. Per pii-scrubber-call-site-not-
176
+ * package: kgauto stays naive to PII policy; consumers pass scrub hooks
177
+ * that encode their own data-handling rules.
178
+ *
179
+ * Brain reads use the scoped JWT minted via migration 013. RLS enforces
180
+ * `app_id = jwt.claim.app_id`; the proxy filter is belt-and-suspenders for
181
+ * payload size + log legibility.
182
+ */
183
+
184
+ interface GlassboxRoutesConfig {
185
+ /** Bearer token validated on every request via constant-time compare. Required. */
186
+ installToken: string;
187
+ /** chrome-extension://<id> — exact match required on Origin header. Required. */
188
+ extensionId: string;
189
+ /** Brain endpoint base (e.g. https://kgauto-brain.supabase.co). Used by `proxy` for replay queries. */
190
+ brainEndpoint: string;
191
+ /** Scoped JWT for brain reads. Use the per-consumer JWT minted via migration 013 (claim: app_id). Drives RLS via the `app_id` claim; sent as `Authorization: Bearer <jwt>` only. */
192
+ brainJwt: string;
193
+ /**
194
+ * Anon/publishable key for the Supabase `apikey` header. Supabase requires
195
+ * `apikey` to be one of the project's known keys (anon or service_role) —
196
+ * the scoped JWT in `brainJwt` doesn't qualify there. Pass the project's
197
+ * legacy `anon` key (JWT format, role=anon) or the modern `sb_publishable_...`
198
+ * key. Safe to expose at the wire (that's what "publishable" means).
199
+ *
200
+ * Pre-alpha.24 this was missing and `brainJwt` was used as apikey too — first
201
+ * real call always 401'd against real Supabase. Catching this required a
202
+ * pre-publish smoke against the real brain; unit tests with mocked fetch
203
+ * couldn't surface it. See L-117 in command-center/learnings.md.
204
+ */
205
+ brainAnonKey: string;
206
+ /** App scope filter — must match the JWT's app_id claim. */
207
+ appId: string;
208
+ /**
209
+ * Optional sanitization hook. Called with each event/trace BEFORE it leaves the consumer.
210
+ * Default: identity (no scrub). PII policy lives at the call-site, not in the package.
211
+ */
212
+ scrub?: (event: GlassboxEvent | Record<string, unknown>) => GlassboxEvent | Record<string, unknown>;
213
+ /**
214
+ * Test-only seam: override the brain fetch implementation. Production
215
+ * code never sets this; tests use it to mock PostgREST responses.
216
+ */
217
+ fetch?: typeof fetch;
218
+ /**
219
+ * Test-only seam: override the per-trace live-stream subscriber. Production
220
+ * code resolves to the alpha.17 `subscribe()` export; tests inject a fake
221
+ * source ReadableStream<GlassboxEvent>.
222
+ */
223
+ subscribe?: (traceId: string) => ReadableStream<GlassboxEvent>;
224
+ /**
225
+ * Test-only seam: override the per-app "tail-all" subscriber (alpha.26).
226
+ * Production code resolves to the `subscribeApp()` export; tests inject
227
+ * a fake source. Backs the extension's default Live tab mode when no
228
+ * traceId is supplied in the URL.
229
+ */
230
+ subscribeApp?: (args: {
231
+ appId: string;
232
+ }) => ReadableStream<GlassboxEvent>;
233
+ }
234
+ interface GlassboxRoutes {
235
+ /** GET /api/glassbox/proxy?traceId=<id> OR ?limit=20 (recent traces) */
236
+ proxy: (req: Request) => Promise<Response>;
237
+ /** GET /api/glassbox/stream?traceId=<id> (SSE) */
238
+ stream: (req: Request) => Promise<Response>;
239
+ }
240
+ declare function createGlassboxRoutes(config: GlassboxRoutesConfig): GlassboxRoutes;
241
+
242
+ export { type GlassboxRoutes, type GlassboxRoutesConfig, type TraceDetail, type TraceSummary, createGlassboxRoutes };