@warmdrift/kgauto-compiler 2.0.0-alpha.15 → 2.0.0-alpha.17
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/dist/{chunk-SFF5EVTL.mjs → chunk-7MTHFSNY.mjs} +209 -0
- package/dist/chunk-NUTC7NUC.mjs +298 -0
- package/dist/glassbox/index.d.mts +159 -0
- package/dist/glassbox/index.d.ts +159 -0
- package/dist/glassbox/index.js +300 -0
- package/dist/glassbox/index.mjs +20 -0
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +624 -9
- package/dist/index.mjs +136 -10
- package/dist/{profiles-DTnIzGsA.d.mts → ir-C3P4gDt0.d.mts} +30 -134
- package/dist/{profiles-D0y6aLk0.d.ts → ir-CFHU3BUT.d.ts} +30 -134
- package/dist/profiles.d.mts +137 -2
- package/dist/profiles.d.ts +137 -2
- package/dist/profiles.js +209 -0
- package/dist/profiles.mjs +1 -1
- package/package.json +7 -2
|
@@ -615,6 +615,215 @@ var PROFILES_RAW = [
|
|
|
615
615
|
// sequential tools — same as V4-Flash
|
|
616
616
|
}
|
|
617
617
|
},
|
|
618
|
+
// ── OpenAI ──
|
|
619
|
+
// alpha.16 (2026-05-17): close the half-supported provider gap. env.ts
|
|
620
|
+
// already registered OPENAI_API_KEY + executeOpenAI + normalizeOpenAILike
|
|
621
|
+
// + lowerOpenAI all existed; profile entries were missing, so the
|
|
622
|
+
// alpha.10 auto-filter would mark openai-keyed models reachable but
|
|
623
|
+
// there were no profiles to filter IN. Half-supported is now fully
|
|
624
|
+
// supported. PB request `openai-provider-profiles` (2026-05-16).
|
|
625
|
+
//
|
|
626
|
+
// Profile data verified against developers.openai.com/api/docs/pricing
|
|
627
|
+
// + per-model pages 2026-05-17. L-049/L-081 step-zero: no AI-trained
|
|
628
|
+
// numbers — fetched live from OpenAI's docs. As of 2026-05, OpenAI's
|
|
629
|
+
// current flagship is gpt-5.5 (2025-12 cutoff); gpt-5.4-{base,mini,nano}
|
|
630
|
+
// are the workhorse family. gpt-4.1 + gpt-4o are legacy.
|
|
631
|
+
//
|
|
632
|
+
// Both 5.5 and 5.4 carry a 272K input-token pricing cliff (2x input,
|
|
633
|
+
// 1.5x output beyond that). Modeled as a `downgrade_quality_warning`
|
|
634
|
+
// cliff because it ranks the model down at large-context shapes — the
|
|
635
|
+
// semantics of "this model is now 2x more expensive" map onto the
|
|
636
|
+
// existing penalty mechanism. Cost-watcher will catch high-context
|
|
637
|
+
// spikes empirically; the cliff prevents naive routing into the doubled
|
|
638
|
+
// pricing zone.
|
|
639
|
+
{
|
|
640
|
+
id: "gpt-5.5",
|
|
641
|
+
verifiedAgainstDocs: "2026-05-17",
|
|
642
|
+
provider: "openai",
|
|
643
|
+
status: "current",
|
|
644
|
+
maxContextTokens: 105e4,
|
|
645
|
+
maxOutputTokens: 128e3,
|
|
646
|
+
maxTools: 64,
|
|
647
|
+
parallelToolCalls: true,
|
|
648
|
+
structuredOutput: "native",
|
|
649
|
+
systemPromptMode: "inline",
|
|
650
|
+
streaming: true,
|
|
651
|
+
cliffs: [
|
|
652
|
+
{
|
|
653
|
+
metric: "input_tokens",
|
|
654
|
+
threshold: 272e3,
|
|
655
|
+
action: "downgrade_quality_warning",
|
|
656
|
+
reason: "OpenAI pricing tier shift: >272K input tokens billed at 2x input + 1.5x output rates"
|
|
657
|
+
}
|
|
658
|
+
],
|
|
659
|
+
costInputPer1m: 5,
|
|
660
|
+
costOutputPer1m: 30,
|
|
661
|
+
lowering: {
|
|
662
|
+
system: { mode: "inline" },
|
|
663
|
+
// OpenAI caching is implicit (auto-applied to repeated prefixes
|
|
664
|
+
// ≥1024 tokens for prompt_tokens_details.cached_tokens). No
|
|
665
|
+
// wire-format marker. Discount: 10x for cached input ($0.50/$5.00).
|
|
666
|
+
cache: { strategy: "unsupported", minTokens: 1024, discount: 0.1 },
|
|
667
|
+
tools: { format: "openai" }
|
|
668
|
+
},
|
|
669
|
+
recovery: [
|
|
670
|
+
{ signal: "rate_limit", action: "escalate", reason: "429 \u2014 escalate to fallback chain" },
|
|
671
|
+
{ signal: "model_not_found", action: "escalate", reason: "Model deprecated/renamed \u2014 escalate (L-061)" }
|
|
672
|
+
],
|
|
673
|
+
strengths: ["reasoning", "agentic_coding", "long_context", "structured_output", "reliable_tool_use", "reasoning_effort_knob"],
|
|
674
|
+
weaknesses: ["cost", "pricing_cliff_at_272k"],
|
|
675
|
+
notes: "OpenAI frontier (2026-05). 1M context (1.05M total), 128K max output, 2025-12 cutoff. Reasoning effort knob (none/low/medium/high/xhigh). Pricing cliff at 272K input.",
|
|
676
|
+
// Frontier-tier perf hypothesis. Anchored to Opus 4.7 row (similar
|
|
677
|
+
// price/positioning). Brain evidence will refine; no telemetry yet.
|
|
678
|
+
archetypePerf: {
|
|
679
|
+
critique: 9,
|
|
680
|
+
plan: 9,
|
|
681
|
+
generate: 9,
|
|
682
|
+
ask: 9,
|
|
683
|
+
extract: 9,
|
|
684
|
+
transform: 9,
|
|
685
|
+
hunt: 8,
|
|
686
|
+
// parallel tool support good but cliff at 272K hurts deep multi-step
|
|
687
|
+
summarize: 7,
|
|
688
|
+
// overkill for tolerant archetype
|
|
689
|
+
classify: 7
|
|
690
|
+
// overkill; cheaper models cover this
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
id: "gpt-5.4",
|
|
695
|
+
verifiedAgainstDocs: "2026-05-17",
|
|
696
|
+
provider: "openai",
|
|
697
|
+
status: "current",
|
|
698
|
+
maxContextTokens: 105e4,
|
|
699
|
+
maxOutputTokens: 128e3,
|
|
700
|
+
maxTools: 64,
|
|
701
|
+
parallelToolCalls: true,
|
|
702
|
+
structuredOutput: "native",
|
|
703
|
+
systemPromptMode: "inline",
|
|
704
|
+
streaming: true,
|
|
705
|
+
cliffs: [
|
|
706
|
+
{
|
|
707
|
+
metric: "input_tokens",
|
|
708
|
+
threshold: 272e3,
|
|
709
|
+
action: "downgrade_quality_warning",
|
|
710
|
+
reason: "OpenAI pricing tier shift: >272K input tokens billed at 2x input + 1.5x output rates"
|
|
711
|
+
}
|
|
712
|
+
],
|
|
713
|
+
costInputPer1m: 2.5,
|
|
714
|
+
costOutputPer1m: 15,
|
|
715
|
+
lowering: {
|
|
716
|
+
system: { mode: "inline" },
|
|
717
|
+
cache: { strategy: "unsupported", minTokens: 1024, discount: 0.1 },
|
|
718
|
+
tools: { format: "openai" }
|
|
719
|
+
},
|
|
720
|
+
recovery: [
|
|
721
|
+
{ signal: "rate_limit", action: "escalate", reason: "429 \u2014 escalate to fallback chain" },
|
|
722
|
+
{ signal: "model_not_found", action: "escalate", reason: "Model deprecated/renamed \u2014 escalate (L-061)" }
|
|
723
|
+
],
|
|
724
|
+
strengths: ["reasoning", "long_context", "structured_output", "reliable_tool_use"],
|
|
725
|
+
weaknesses: ["pricing_cliff_at_272k"],
|
|
726
|
+
notes: "OpenAI workhorse (2026-05). 1M context (1.05M total), 128K max output, 2025-08 cutoff. Pricing cliff at 272K input. Pairs cleanly with Sonnet 4.6 on cost ($2.50/$15.00 vs $3.00/$15.00).",
|
|
727
|
+
// Anchored to Sonnet 4.6 row (similar price/positioning). Slight
|
|
728
|
+
// anthropic-side edge on agentic coding per master plan vibe.
|
|
729
|
+
archetypePerf: {
|
|
730
|
+
critique: 8,
|
|
731
|
+
plan: 8,
|
|
732
|
+
generate: 8,
|
|
733
|
+
ask: 8,
|
|
734
|
+
extract: 8,
|
|
735
|
+
transform: 8,
|
|
736
|
+
hunt: 7,
|
|
737
|
+
summarize: 7,
|
|
738
|
+
classify: 7
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
id: "gpt-5.4-mini",
|
|
743
|
+
verifiedAgainstDocs: "2026-05-17",
|
|
744
|
+
provider: "openai",
|
|
745
|
+
status: "current",
|
|
746
|
+
maxContextTokens: 4e5,
|
|
747
|
+
maxOutputTokens: 128e3,
|
|
748
|
+
maxTools: 64,
|
|
749
|
+
parallelToolCalls: true,
|
|
750
|
+
structuredOutput: "native",
|
|
751
|
+
systemPromptMode: "inline",
|
|
752
|
+
streaming: true,
|
|
753
|
+
cliffs: [],
|
|
754
|
+
costInputPer1m: 0.75,
|
|
755
|
+
costOutputPer1m: 4.5,
|
|
756
|
+
lowering: {
|
|
757
|
+
system: { mode: "inline" },
|
|
758
|
+
cache: { strategy: "unsupported", minTokens: 1024, discount: 0.1 },
|
|
759
|
+
tools: { format: "openai" }
|
|
760
|
+
},
|
|
761
|
+
recovery: [
|
|
762
|
+
{ signal: "rate_limit", action: "escalate", reason: "429 \u2014 escalate to fallback chain" },
|
|
763
|
+
{ signal: "model_not_found", action: "escalate", reason: "Model deprecated/renamed \u2014 escalate (L-061)" }
|
|
764
|
+
],
|
|
765
|
+
strengths: ["cost", "speed", "agentic_coding", "structured_output", "reliable_tool_use"],
|
|
766
|
+
weaknesses: ["reasoning_depth"],
|
|
767
|
+
notes: "OpenAI mini-tier (2026-05). 400K context, 128K max output, 2025-08 cutoff. OpenAI describes as 'strongest mini model for coding, computer use, subagents.' Cache discount 10x ($0.075 input).",
|
|
768
|
+
// Mini-tier hypothesis. Anchored to Haiku 4.5 + Flash row pricing.
|
|
769
|
+
// Cost is slightly higher than Haiku ($0.75 vs $0.50 input) but
|
|
770
|
+
// OpenAI claims strong coding/subagent perf.
|
|
771
|
+
archetypePerf: {
|
|
772
|
+
ask: 7,
|
|
773
|
+
generate: 7,
|
|
774
|
+
extract: 7,
|
|
775
|
+
transform: 7,
|
|
776
|
+
classify: 7,
|
|
777
|
+
summarize: 7,
|
|
778
|
+
hunt: 7,
|
|
779
|
+
plan: 6,
|
|
780
|
+
critique: 5
|
|
781
|
+
// reasoning depth gap — frontier models handle this
|
|
782
|
+
}
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
id: "gpt-5.4-nano",
|
|
786
|
+
verifiedAgainstDocs: "2026-05-17",
|
|
787
|
+
provider: "openai",
|
|
788
|
+
status: "current",
|
|
789
|
+
maxContextTokens: 4e5,
|
|
790
|
+
maxOutputTokens: 128e3,
|
|
791
|
+
maxTools: 64,
|
|
792
|
+
parallelToolCalls: true,
|
|
793
|
+
structuredOutput: "native",
|
|
794
|
+
systemPromptMode: "inline",
|
|
795
|
+
streaming: true,
|
|
796
|
+
cliffs: [],
|
|
797
|
+
costInputPer1m: 0.2,
|
|
798
|
+
costOutputPer1m: 1.25,
|
|
799
|
+
lowering: {
|
|
800
|
+
system: { mode: "inline" },
|
|
801
|
+
cache: { strategy: "unsupported", minTokens: 1024, discount: 0.1 },
|
|
802
|
+
tools: { format: "openai" }
|
|
803
|
+
},
|
|
804
|
+
recovery: [
|
|
805
|
+
{ signal: "rate_limit", action: "escalate", reason: "429 \u2014 escalate to fallback chain" },
|
|
806
|
+
{ signal: "model_not_found", action: "escalate", reason: "Model deprecated/renamed \u2014 escalate (L-061)" }
|
|
807
|
+
],
|
|
808
|
+
strengths: ["cost", "speed", "volume", "structured_output"],
|
|
809
|
+
weaknesses: ["reasoning_depth", "no_computer_use"],
|
|
810
|
+
notes: "OpenAI nano-tier (2026-05). 400K context, 128K max output, 2025-08 cutoff. 'Cheapest GPT-5.4-class for simple high-volume tasks.' No fine-tuning, no computer-use tools. Cache discount 10x.",
|
|
811
|
+
// Nano-tier. Anchored to Flash-Lite row ($0.10/$0.40 vs nano's
|
|
812
|
+
// $0.20/$1.25). Slightly more expensive than Flash-Lite but with
|
|
813
|
+
// OpenAI brand reliability. Good fit for classify/summarize floor.
|
|
814
|
+
archetypePerf: {
|
|
815
|
+
classify: 7,
|
|
816
|
+
summarize: 6,
|
|
817
|
+
ask: 6,
|
|
818
|
+
transform: 6,
|
|
819
|
+
extract: 6,
|
|
820
|
+
generate: 5,
|
|
821
|
+
hunt: 5,
|
|
822
|
+
plan: 4,
|
|
823
|
+
critique: 3
|
|
824
|
+
// not for reasoning archetypes
|
|
825
|
+
}
|
|
826
|
+
},
|
|
618
827
|
// ── Auto-onboarded (UNVERIFIED) ──
|
|
619
828
|
// Cloned by scripts/auto-onboard-models.mjs from a same-family template.
|
|
620
829
|
// Each entry's pricing/context/cliffs/lowering reflects the template, NOT
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// src/glassbox/types.ts
|
|
2
|
+
var GLASSBOX_STREAM_TTL_MS = 6e4;
|
|
3
|
+
|
|
4
|
+
// src/glassbox/pubsub-memory.ts
|
|
5
|
+
var MemoryPubSub = class {
|
|
6
|
+
subscribers = /* @__PURE__ */ new Map();
|
|
7
|
+
async publish(traceId, event) {
|
|
8
|
+
const subs = this.subscribers.get(traceId);
|
|
9
|
+
if (!subs || subs.size === 0) return;
|
|
10
|
+
for (const sub of subs) {
|
|
11
|
+
if (sub.closed) continue;
|
|
12
|
+
try {
|
|
13
|
+
sub.controller.enqueue(event);
|
|
14
|
+
} catch {
|
|
15
|
+
sub.closed = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
this.refreshTtl(traceId, sub);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
subscribe(traceId) {
|
|
22
|
+
const self = this;
|
|
23
|
+
let sub;
|
|
24
|
+
return new ReadableStream({
|
|
25
|
+
start(controller) {
|
|
26
|
+
sub = {
|
|
27
|
+
controller,
|
|
28
|
+
ttlTimer: setTimeout(() => {
|
|
29
|
+
self.closeSubscriber(traceId, sub);
|
|
30
|
+
}, GLASSBOX_STREAM_TTL_MS),
|
|
31
|
+
closed: false
|
|
32
|
+
};
|
|
33
|
+
let set = self.subscribers.get(traceId);
|
|
34
|
+
if (!set) {
|
|
35
|
+
set = /* @__PURE__ */ new Set();
|
|
36
|
+
self.subscribers.set(traceId, set);
|
|
37
|
+
}
|
|
38
|
+
set.add(sub);
|
|
39
|
+
},
|
|
40
|
+
cancel() {
|
|
41
|
+
if (sub) self.removeSubscriber(traceId, sub);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Refresh the rolling TTL for a subscriber after an event lands. Replaces
|
|
47
|
+
* the existing timer with a fresh 60s one.
|
|
48
|
+
*/
|
|
49
|
+
refreshTtl(traceId, sub) {
|
|
50
|
+
clearTimeout(sub.ttlTimer);
|
|
51
|
+
sub.ttlTimer = setTimeout(() => {
|
|
52
|
+
this.closeSubscriber(traceId, sub);
|
|
53
|
+
}, GLASSBOX_STREAM_TTL_MS);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Close the subscriber's stream cleanly and remove from the fan-out set.
|
|
57
|
+
* Idempotent — safe to call multiple times.
|
|
58
|
+
*/
|
|
59
|
+
closeSubscriber(traceId, sub) {
|
|
60
|
+
if (sub.closed) return;
|
|
61
|
+
sub.closed = true;
|
|
62
|
+
clearTimeout(sub.ttlTimer);
|
|
63
|
+
try {
|
|
64
|
+
sub.controller.close();
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
this.removeSubscriber(traceId, sub);
|
|
68
|
+
}
|
|
69
|
+
removeSubscriber(traceId, sub) {
|
|
70
|
+
clearTimeout(sub.ttlTimer);
|
|
71
|
+
const set = this.subscribers.get(traceId);
|
|
72
|
+
if (!set) return;
|
|
73
|
+
set.delete(sub);
|
|
74
|
+
if (set.size === 0) this.subscribers.delete(traceId);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Test-only reset. Tears down all subscribers, clears all state. Calling
|
|
78
|
+
* outside of tests is harmless but cancels every active stream.
|
|
79
|
+
*/
|
|
80
|
+
_reset() {
|
|
81
|
+
for (const [, set] of this.subscribers) {
|
|
82
|
+
for (const sub of set) {
|
|
83
|
+
this.closeSubscriber("", sub);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.subscribers.clear();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/glassbox/pubsub-upstash.ts
|
|
91
|
+
var UpstashPubSub = class {
|
|
92
|
+
url;
|
|
93
|
+
token;
|
|
94
|
+
fetchImpl;
|
|
95
|
+
blockMs;
|
|
96
|
+
maxLen;
|
|
97
|
+
constructor(cfg) {
|
|
98
|
+
this.url = cfg.url.replace(/\/$/, "");
|
|
99
|
+
this.token = cfg.token;
|
|
100
|
+
this.fetchImpl = cfg.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
101
|
+
this.blockMs = cfg.blockMs ?? 100;
|
|
102
|
+
this.maxLen = cfg.maxLen ?? 100;
|
|
103
|
+
}
|
|
104
|
+
async publish(traceId, event) {
|
|
105
|
+
const key = streamKey(traceId);
|
|
106
|
+
const payload = JSON.stringify(event);
|
|
107
|
+
await this.cmd([
|
|
108
|
+
"XADD",
|
|
109
|
+
key,
|
|
110
|
+
"MAXLEN",
|
|
111
|
+
"~",
|
|
112
|
+
String(this.maxLen),
|
|
113
|
+
"*",
|
|
114
|
+
"event",
|
|
115
|
+
payload
|
|
116
|
+
]);
|
|
117
|
+
await this.cmd(["EXPIRE", key, String(Math.ceil(GLASSBOX_STREAM_TTL_MS / 1e3))]);
|
|
118
|
+
}
|
|
119
|
+
subscribe(traceId) {
|
|
120
|
+
const key = streamKey(traceId);
|
|
121
|
+
const self = this;
|
|
122
|
+
let cursor = "$";
|
|
123
|
+
let cancelled = false;
|
|
124
|
+
let ttlDeadline = Date.now() + GLASSBOX_STREAM_TTL_MS;
|
|
125
|
+
return new ReadableStream({
|
|
126
|
+
async start(controller) {
|
|
127
|
+
try {
|
|
128
|
+
while (!cancelled && Date.now() < ttlDeadline) {
|
|
129
|
+
const resp = await self.cmd([
|
|
130
|
+
"XREAD",
|
|
131
|
+
"BLOCK",
|
|
132
|
+
String(self.blockMs),
|
|
133
|
+
"STREAMS",
|
|
134
|
+
key,
|
|
135
|
+
cursor
|
|
136
|
+
]);
|
|
137
|
+
if (cancelled) break;
|
|
138
|
+
const parsed = parseXReadResult(resp.result);
|
|
139
|
+
if (parsed.entries.length === 0) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
for (const entry of parsed.entries) {
|
|
143
|
+
const evt = decodeEvent(entry.fields);
|
|
144
|
+
if (evt) {
|
|
145
|
+
try {
|
|
146
|
+
controller.enqueue(evt);
|
|
147
|
+
} catch {
|
|
148
|
+
cancelled = true;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
cursor = entry.id;
|
|
153
|
+
}
|
|
154
|
+
ttlDeadline = Date.now() + GLASSBOX_STREAM_TTL_MS;
|
|
155
|
+
}
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (!cancelled) {
|
|
158
|
+
try {
|
|
159
|
+
controller.error(err);
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
controller.close();
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
cancel() {
|
|
171
|
+
cancelled = true;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async cmd(args) {
|
|
176
|
+
const res = await this.fetchImpl(this.url, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
headers: {
|
|
179
|
+
Authorization: `Bearer ${this.token}`,
|
|
180
|
+
"Content-Type": "application/json"
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify(args)
|
|
183
|
+
});
|
|
184
|
+
if (!res.ok) {
|
|
185
|
+
throw new Error(`Upstash ${args[0]} failed: HTTP ${res.status}`);
|
|
186
|
+
}
|
|
187
|
+
const json = await res.json();
|
|
188
|
+
if (json.error) {
|
|
189
|
+
throw new Error(`Upstash ${args[0]} failed: ${json.error}`);
|
|
190
|
+
}
|
|
191
|
+
return json;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
function streamKey(traceId) {
|
|
195
|
+
return `glassbox:trace:${traceId}`;
|
|
196
|
+
}
|
|
197
|
+
function decodeEvent(fields) {
|
|
198
|
+
const raw = fields["event"];
|
|
199
|
+
if (!raw) return void 0;
|
|
200
|
+
try {
|
|
201
|
+
const parsed = JSON.parse(raw);
|
|
202
|
+
if (typeof parsed.kind === "string" && typeof parsed.at === "number") {
|
|
203
|
+
return parsed;
|
|
204
|
+
}
|
|
205
|
+
return void 0;
|
|
206
|
+
} catch {
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function parseXReadResult(raw) {
|
|
211
|
+
if (!Array.isArray(raw)) return { entries: [] };
|
|
212
|
+
const entries = [];
|
|
213
|
+
for (const stream of raw) {
|
|
214
|
+
if (!Array.isArray(stream) || stream.length < 2) continue;
|
|
215
|
+
const streamEntries = stream[1];
|
|
216
|
+
if (!Array.isArray(streamEntries)) continue;
|
|
217
|
+
for (const entry of streamEntries) {
|
|
218
|
+
if (!Array.isArray(entry) || entry.length < 2) continue;
|
|
219
|
+
const id = String(entry[0]);
|
|
220
|
+
const flat = entry[1];
|
|
221
|
+
if (!Array.isArray(flat)) continue;
|
|
222
|
+
const fields = {};
|
|
223
|
+
for (let i = 0; i < flat.length; i += 2) {
|
|
224
|
+
const k = flat[i];
|
|
225
|
+
const v = flat[i + 1];
|
|
226
|
+
if (typeof k === "string") fields[k] = String(v ?? "");
|
|
227
|
+
}
|
|
228
|
+
entries.push({ id, fields });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return { entries };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/glassbox/emit.ts
|
|
235
|
+
var activePubSub;
|
|
236
|
+
function getPubSub() {
|
|
237
|
+
if (activePubSub) return activePubSub;
|
|
238
|
+
const url = readEnv("UPSTASH_REDIS_URL");
|
|
239
|
+
const token = readEnv("UPSTASH_REDIS_TOKEN");
|
|
240
|
+
if (url && token) {
|
|
241
|
+
activePubSub = new UpstashPubSub({ url, token });
|
|
242
|
+
} else {
|
|
243
|
+
activePubSub = new MemoryPubSub();
|
|
244
|
+
}
|
|
245
|
+
return activePubSub;
|
|
246
|
+
}
|
|
247
|
+
function readEnv(key) {
|
|
248
|
+
try {
|
|
249
|
+
if (typeof process !== "undefined" && process.env) {
|
|
250
|
+
const v = process.env[key];
|
|
251
|
+
return v && v.trim() !== "" ? v : void 0;
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
return void 0;
|
|
256
|
+
}
|
|
257
|
+
function emitGlassboxEvent(traceId, kind, data) {
|
|
258
|
+
if (!traceId) return;
|
|
259
|
+
const event = { kind, at: Date.now(), data };
|
|
260
|
+
const ps = getPubSub();
|
|
261
|
+
try {
|
|
262
|
+
const p = ps.publish(traceId, event);
|
|
263
|
+
if (p && typeof p.then === "function") {
|
|
264
|
+
p.catch(() => {
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function emitCompileStart(traceId, data) {
|
|
271
|
+
emitGlassboxEvent(traceId, "compile.start", data);
|
|
272
|
+
}
|
|
273
|
+
function emitCompileDone(traceId, data) {
|
|
274
|
+
emitGlassboxEvent(traceId, "compile.done", data);
|
|
275
|
+
}
|
|
276
|
+
function emitExecuteAttempt(traceId, data) {
|
|
277
|
+
emitGlassboxEvent(traceId, "execute.attempt", data);
|
|
278
|
+
}
|
|
279
|
+
function emitExecuteSuccess(traceId, data) {
|
|
280
|
+
emitGlassboxEvent(traceId, "execute.success", data);
|
|
281
|
+
}
|
|
282
|
+
function emitAdvisoryFired(traceId, data) {
|
|
283
|
+
emitGlassboxEvent(traceId, "advisory.fired", data);
|
|
284
|
+
}
|
|
285
|
+
function emitFallbackWalked(traceId, data) {
|
|
286
|
+
emitGlassboxEvent(traceId, "fallback.walked", data);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export {
|
|
290
|
+
GLASSBOX_STREAM_TTL_MS,
|
|
291
|
+
getPubSub,
|
|
292
|
+
emitCompileStart,
|
|
293
|
+
emitCompileDone,
|
|
294
|
+
emitExecuteAttempt,
|
|
295
|
+
emitExecuteSuccess,
|
|
296
|
+
emitAdvisoryFired,
|
|
297
|
+
emitFallbackWalked
|
|
298
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { j as MutationApplied, B as BestPracticeAdvisory, F as FallbackReason, g as CallAttempt } from '../ir-C3P4gDt0.mjs';
|
|
2
|
+
import '../dialect.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Glass-Box observability types (alpha.17).
|
|
6
|
+
*
|
|
7
|
+
* The substrate for kgauto's in-flight observability surface — a Chrome MV3
|
|
8
|
+
* side panel that renders compile + execute + advisor outputs in real-time.
|
|
9
|
+
* See design doc:
|
|
10
|
+
* ~/.gstack/projects/stue-kgauto/stgreen-claude-serene-williamson-51a105-design-20260518-175356.md
|
|
11
|
+
*
|
|
12
|
+
* Wire shape is intentionally minimal:
|
|
13
|
+
* { kind, at, data } — discriminated union by `kind`.
|
|
14
|
+
*
|
|
15
|
+
* Critical-path safety (L-086 derived): emit MUST NEVER fail the user's
|
|
16
|
+
* `call()` invocation. emit.ts wraps every adapter write in try/catch +
|
|
17
|
+
* silently drops on error. Consumers in Vercel Edge runtimes can lose one
|
|
18
|
+
* event without losing the response.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Discriminator. Six event kinds cover the v1 substrate. New kinds added
|
|
23
|
+
* additively; the side-panel renderer treats unknown kinds as "ignore".
|
|
24
|
+
*/
|
|
25
|
+
type GlassboxEventKind = 'compile.start' | 'compile.done' | 'execute.attempt' | 'execute.success' | 'advisory.fired' | 'fallback.walked';
|
|
26
|
+
/**
|
|
27
|
+
* Wire envelope. Every emit lands as one of these.
|
|
28
|
+
*
|
|
29
|
+
* - kind: discriminator
|
|
30
|
+
* - at: unix milliseconds (Date.now())
|
|
31
|
+
* - data: kind-specific payload
|
|
32
|
+
*
|
|
33
|
+
* `data` typed loosely as Record<string, unknown> for serialization-friendliness;
|
|
34
|
+
* the per-kind builders below populate it with the structured shape expected
|
|
35
|
+
* by the renderer. We deliberately do NOT typescript-narrow `data` by kind on
|
|
36
|
+
* the wire type — the side panel reads JSON from SSE and only the renderer
|
|
37
|
+
* needs the narrowed shape. Internal callers (emit.ts) get type assistance via
|
|
38
|
+
* the typed factory helpers in emit.ts.
|
|
39
|
+
*/
|
|
40
|
+
interface GlassboxEvent {
|
|
41
|
+
kind: GlassboxEventKind;
|
|
42
|
+
at: number;
|
|
43
|
+
data: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Per-kind data shapes. Surfaced as type aids; not enforced at the
|
|
47
|
+
* GlassboxEvent boundary (intentionally — see comment above).
|
|
48
|
+
*/
|
|
49
|
+
interface CompileStartData {
|
|
50
|
+
appId: string;
|
|
51
|
+
archetype: string;
|
|
52
|
+
models: string[];
|
|
53
|
+
}
|
|
54
|
+
interface CompileDoneData {
|
|
55
|
+
target: string;
|
|
56
|
+
provider: string;
|
|
57
|
+
fallbackChain: string[];
|
|
58
|
+
tokensIn: number;
|
|
59
|
+
estimatedCostUsd: number;
|
|
60
|
+
mutationsApplied: MutationApplied[];
|
|
61
|
+
advisories: BestPracticeAdvisory[];
|
|
62
|
+
}
|
|
63
|
+
interface ExecuteAttemptData {
|
|
64
|
+
model: string;
|
|
65
|
+
attemptIndex: number;
|
|
66
|
+
}
|
|
67
|
+
interface ExecuteSuccessData {
|
|
68
|
+
model: string;
|
|
69
|
+
tokensIn: number;
|
|
70
|
+
tokensOut: number;
|
|
71
|
+
latencyMs: number;
|
|
72
|
+
}
|
|
73
|
+
interface AdvisoryFiredData {
|
|
74
|
+
code: string;
|
|
75
|
+
message: string;
|
|
76
|
+
}
|
|
77
|
+
interface FallbackWalkedData {
|
|
78
|
+
from: string;
|
|
79
|
+
to: string;
|
|
80
|
+
reason: FallbackReason | 'unknown';
|
|
81
|
+
attempt: CallAttempt;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Pub/sub backend interface. Two adapters implement it: in-memory (dev /
|
|
85
|
+
* single-process) and Upstash Redis Streams (Vercel Edge multi-instance).
|
|
86
|
+
*
|
|
87
|
+
* The choice is made at module load (NOT per-call) based on env-var presence:
|
|
88
|
+
* if (UPSTASH_REDIS_URL && UPSTASH_REDIS_TOKEN) → Upstash
|
|
89
|
+
* else → in-memory
|
|
90
|
+
*
|
|
91
|
+
* Stream TTL: 60s after last event. After that, subscriber stream closes
|
|
92
|
+
* cleanly. The adapter owns its own TTL clock; subscribe() is agnostic.
|
|
93
|
+
*/
|
|
94
|
+
interface GlassboxPubSub {
|
|
95
|
+
/**
|
|
96
|
+
* Publish a single event for a traceId. Returns a promise that resolves
|
|
97
|
+
* after the event is durably appended (or rejects on adapter failure —
|
|
98
|
+
* callers in emit.ts always swallow rejections).
|
|
99
|
+
*/
|
|
100
|
+
publish(traceId: string, event: GlassboxEvent): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Subscribe to events for a traceId. Returns a ReadableStream that emits
|
|
103
|
+
* GlassboxEvent objects as they're published. The stream closes cleanly
|
|
104
|
+
* after the per-trace TTL elapses since the LAST event (rolling 60s).
|
|
105
|
+
*
|
|
106
|
+
* If the traceId has no publisher within 60s of subscription, the stream
|
|
107
|
+
* closes empty.
|
|
108
|
+
*/
|
|
109
|
+
subscribe(traceId: string): ReadableStream<GlassboxEvent>;
|
|
110
|
+
/**
|
|
111
|
+
* Test-only escape hatch. Clears any in-memory state. Upstash adapter
|
|
112
|
+
* no-ops (server-side state isn't accessible from here). Tests reach for
|
|
113
|
+
* this between cases to keep adapters hermetic.
|
|
114
|
+
*/
|
|
115
|
+
_reset?(): void;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* TTL applied to a stream after each event. The design doc names 60s; this
|
|
119
|
+
* constant centralizes it so both adapters + tests agree.
|
|
120
|
+
*/
|
|
121
|
+
declare const GLASSBOX_STREAM_TTL_MS = 60000;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* subscribe(traceId) — public Glass-Box subscription export.
|
|
125
|
+
*
|
|
126
|
+
* Consumer route handlers (e.g. Vercel Edge `/api/glassbox/stream`) import
|
|
127
|
+
* this and bridge the ReadableStream to SSE. The browser-side panel then
|
|
128
|
+
* renders events in real time:
|
|
129
|
+
*
|
|
130
|
+
* import { subscribe } from '@warmdrift/kgauto-compiler/glassbox';
|
|
131
|
+
*
|
|
132
|
+
* export async function GET(req: Request) {
|
|
133
|
+
* const { searchParams } = new URL(req.url);
|
|
134
|
+
* const traceId = searchParams.get('traceId');
|
|
135
|
+
* if (!traceId) return new Response('missing traceId', { status: 400 });
|
|
136
|
+
* const stream = subscribe(traceId);
|
|
137
|
+
* // ... bridge ReadableStream<GlassboxEvent> → SSE here ...
|
|
138
|
+
* }
|
|
139
|
+
*
|
|
140
|
+
* Stream behavior:
|
|
141
|
+
* - Emits events for `traceId` as they're published.
|
|
142
|
+
* - Closes cleanly 60s after the last event (rolling TTL).
|
|
143
|
+
* - If no events arrive within 60s of subscription, closes empty.
|
|
144
|
+
* - Multiple subscribers on the same traceId all fan out.
|
|
145
|
+
*
|
|
146
|
+
* No replay: subscribe() picks up only events published AFTER subscription.
|
|
147
|
+
* Replay is the brain-poll surface's job (see design doc).
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Subscribe to Glass-Box events for a traceId. Returns a ReadableStream
|
|
152
|
+
* that yields GlassboxEvent objects until the per-trace TTL elapses.
|
|
153
|
+
*
|
|
154
|
+
* Cancelling the stream (consumer disconnect, AbortController, etc.) tears
|
|
155
|
+
* down the subscription cleanly via the underlying adapter.
|
|
156
|
+
*/
|
|
157
|
+
declare function subscribe(traceId: string): ReadableStream<GlassboxEvent>;
|
|
158
|
+
|
|
159
|
+
export { type AdvisoryFiredData, type CompileDoneData, type CompileStartData, type ExecuteAttemptData, type ExecuteSuccessData, type FallbackWalkedData, GLASSBOX_STREAM_TTL_MS, type GlassboxEvent, type GlassboxEventKind, type GlassboxPubSub, subscribe };
|