@warmdrift/kgauto-compiler 2.0.0-alpha.4 → 2.0.0-alpha.42

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 (40) hide show
  1. package/README.md +87 -3
  2. package/dist/chunk-JQGRWJZO.mjs +1216 -0
  3. package/dist/chunk-KCNQUMHN.mjs +694 -0
  4. package/dist/chunk-NBO4R5PC.mjs +313 -0
  5. package/dist/chunk-P3TOAEG4.mjs +56 -0
  6. package/dist/chunk-RO22VFIF.mjs +29 -0
  7. package/dist/glassbox/index.d.mts +59 -0
  8. package/dist/glassbox/index.d.ts +59 -0
  9. package/dist/glassbox/index.js +312 -0
  10. package/dist/glassbox/index.mjs +12 -0
  11. package/dist/glassbox-routes/format.d.mts +24 -0
  12. package/dist/glassbox-routes/format.d.ts +24 -0
  13. package/dist/glassbox-routes/format.js +86 -0
  14. package/dist/glassbox-routes/format.mjs +18 -0
  15. package/dist/glassbox-routes/index.d.mts +191 -0
  16. package/dist/glassbox-routes/index.d.ts +191 -0
  17. package/dist/glassbox-routes/index.js +2473 -0
  18. package/dist/glassbox-routes/index.mjs +663 -0
  19. package/dist/glassbox-routes/react/index.d.mts +74 -0
  20. package/dist/glassbox-routes/react/index.d.ts +74 -0
  21. package/dist/glassbox-routes/react/index.js +819 -0
  22. package/dist/glassbox-routes/react/index.mjs +754 -0
  23. package/dist/index.d.mts +1796 -9
  24. package/dist/index.d.ts +1796 -9
  25. package/dist/index.js +4771 -312
  26. package/dist/index.mjs +2951 -275
  27. package/dist/ir-BG70lg9b.d.mts +1229 -0
  28. package/dist/ir-BmumS1Du.d.ts +1229 -0
  29. package/dist/profiles.d.mts +164 -2
  30. package/dist/profiles.d.ts +164 -2
  31. package/dist/profiles.js +820 -11
  32. package/dist/profiles.mjs +5 -1
  33. package/dist/types-Cs_hsBGm.d.ts +131 -0
  34. package/dist/types-DIl0tCDX.d.mts +149 -0
  35. package/dist/types-eKKDeADI.d.mts +131 -0
  36. package/dist/types-kFsIDC_5.d.ts +149 -0
  37. package/package.json +42 -6
  38. package/dist/chunk-MBEI5UOM.mjs +0 -409
  39. package/dist/profiles-CDttLtaD.d.ts +0 -521
  40. package/dist/profiles-CH_nKPjp.d.mts +0 -521
package/dist/index.mjs CHANGED
@@ -9,13 +9,48 @@ import {
9
9
  isArchetype,
10
10
  learningKey
11
11
  } from "./chunk-5TI6PNSK.mjs";
12
+ import {
13
+ ABSOLUTE_FLOOR,
14
+ ARCHETYPE_FLOOR_DEFAULT,
15
+ PROVIDER_ENV_KEYS,
16
+ configureBrainQuery,
17
+ createBrainQueryCache,
18
+ ensureCrossProviderTail,
19
+ getAllStarterChains,
20
+ getAllStarterChainsWithGrounding,
21
+ getDefaultFallbackChain,
22
+ getDefaultFallbackChainWithGrounding,
23
+ getModelCompatibility,
24
+ getPerAxisMetrics,
25
+ getReachabilityDiagnostic,
26
+ getSequentialStarterChain,
27
+ getSequentialStarterChainWithGrounding,
28
+ getStarterChain,
29
+ getStarterChainWithGrounding,
30
+ isBrainQueryActiveFor,
31
+ isModelReachable,
32
+ isProviderReachable,
33
+ loadChainsFromBrain,
34
+ readBrainReadEnv,
35
+ resolveProviderKey
36
+ } from "./chunk-KCNQUMHN.mjs";
12
37
  import {
13
38
  ALIASES,
39
+ _setProfileBrainHook,
14
40
  allProfiles,
41
+ allProfilesRaw,
15
42
  getProfile,
16
43
  profilesByProvider,
17
44
  tryGetProfile
18
- } from "./chunk-MBEI5UOM.mjs";
45
+ } from "./chunk-JQGRWJZO.mjs";
46
+ import {
47
+ emitAdvisoryFired,
48
+ emitCompileDone,
49
+ emitCompileStart,
50
+ emitExecuteAttempt,
51
+ emitExecuteSuccess,
52
+ emitFallbackWalked
53
+ } from "./chunk-NBO4R5PC.mjs";
19
54
 
20
55
  // src/tokenizer.ts
21
56
  var tokenizerImpl = defaultCharBasedCounter;
@@ -120,38 +155,96 @@ function passToolRelevance(ir, opts = {}) {
120
155
  ]
121
156
  };
122
157
  }
158
+ function totalHistoryTokens(history) {
159
+ let total = 0;
160
+ for (const m of history) {
161
+ if (typeof m.content === "string") total += countTokens(m.content);
162
+ }
163
+ return total;
164
+ }
123
165
  function passCompressHistory(ir, opts = {}) {
124
166
  const history = ir.history;
125
- if (!history || history.length === 0) return { value: ir, mutations: [] };
167
+ if (!history || history.length === 0) {
168
+ return { value: ir, mutations: [], historyTokensTotal: 0 };
169
+ }
126
170
  const keepRecent = opts.keepRecent ?? 4;
127
171
  const summarizeOlderThan = opts.summarizeOlderThan ?? 8;
128
- if (history.length <= summarizeOlderThan) return { value: ir, mutations: [] };
129
- const cutIndex = history.length - keepRecent;
130
- const old = history.slice(0, cutIndex);
131
- const recent = history.slice(cutIndex);
132
- const userTurns = old.filter((m) => m.role === "user");
133
- const firstUserLine = userTurns[0]?.content.split("\n")[0]?.slice(0, 200) ?? "";
134
- const summary = {
135
- role: "system",
136
- content: `[Earlier conversation: ${old.length} turns omitted. First user message: "${firstUserLine}"]`
137
- };
138
- return {
139
- value: { ...ir, history: [summary, ...recent] },
140
- mutations: [
141
- {
142
- id: `compress-history-${old.length}`,
143
- source: "static_pass",
144
- passName: "compress_history",
145
- description: `Compressed ${old.length} old turns into 1 summary line (kept ${keepRecent} recent)`
172
+ const summarizeAboveTokens = opts.summarizeAboveTokens;
173
+ const historyTokensTotal = totalHistoryTokens(history);
174
+ const countThresholdHit = history.length > summarizeOlderThan;
175
+ const tokenThresholdHit = summarizeAboveTokens !== void 0 && historyTokensTotal > summarizeAboveTokens;
176
+ if (!countThresholdHit && !tokenThresholdHit) {
177
+ return { value: ir, mutations: [], historyTokensTotal };
178
+ }
179
+ if (history.length > keepRecent) {
180
+ const cutIndex = history.length - keepRecent;
181
+ const old = history.slice(0, cutIndex);
182
+ const recent = history.slice(cutIndex);
183
+ const userTurns = old.filter((m) => m.role === "user");
184
+ const firstUserLine = userTurns[0]?.content.split("\n")[0]?.slice(0, 200) ?? "";
185
+ const oldTokens = totalHistoryTokens(old);
186
+ const trigger = tokenThresholdHit && !countThresholdHit ? "tokens" : "count";
187
+ const summary = {
188
+ role: "system",
189
+ content: `[Earlier conversation: ${old.length} turns omitted (~${oldTokens} tokens). First user message: "${firstUserLine}"]`
190
+ };
191
+ return {
192
+ value: { ...ir, history: [summary, ...recent] },
193
+ mutations: [
194
+ {
195
+ id: `compress-history-${old.length}`,
196
+ source: "static_pass",
197
+ passName: "compress_history",
198
+ description: trigger === "tokens" ? `Compressed ${old.length} old turns (~${oldTokens} tokens) into 1 summary \u2014 token threshold ${summarizeAboveTokens} exceeded (kept ${keepRecent} recent)` : `Compressed ${old.length} old turns into 1 summary (kept ${keepRecent} recent)`
199
+ }
200
+ ],
201
+ historyTokensTotal
202
+ };
203
+ }
204
+ if (tokenThresholdHit) {
205
+ let fattestIdx = -1;
206
+ let fattestTokens = 0;
207
+ for (let i = 0; i < history.length; i++) {
208
+ const m = history[i];
209
+ if (!m || typeof m.content !== "string") continue;
210
+ const t = countTokens(m.content);
211
+ if (t > fattestTokens) {
212
+ fattestTokens = t;
213
+ fattestIdx = i;
146
214
  }
147
- ]
148
- };
215
+ }
216
+ const FAT_DOMINANCE_FLOOR = 0.3;
217
+ const fattest = fattestIdx >= 0 ? history[fattestIdx] : void 0;
218
+ if (fattest && historyTokensTotal > 0 && fattestTokens / historyTokensTotal >= FAT_DOMINANCE_FLOOR) {
219
+ const firstLine = fattest.content.split("\n")[0]?.slice(0, 200) ?? "";
220
+ const newContent = `[Earlier ${fattest.role} message content omitted: ~${fattestTokens} tokens. Preview: "${firstLine}"]`;
221
+ const newHistory = history.slice();
222
+ newHistory[fattestIdx] = { ...fattest, content: newContent };
223
+ return {
224
+ value: { ...ir, history: newHistory },
225
+ mutations: [
226
+ {
227
+ id: `compress-fat-message-${fattestIdx}`,
228
+ source: "static_pass",
229
+ passName: "compress_history",
230
+ description: `Replaced fat ${fattest.role} message #${fattestIdx} content (~${fattestTokens} of ${historyTokensTotal} tokens, ${Math.round(fattestTokens / historyTokensTotal * 100)}% of history) with summary stub \u2014 token threshold ${summarizeAboveTokens} exceeded (history.length ${history.length} <= keepRecent ${keepRecent}, slice not possible)`
231
+ }
232
+ ],
233
+ historyTokensTotal
234
+ };
235
+ }
236
+ }
237
+ return { value: ir, mutations: [], historyTokensTotal };
149
238
  }
150
239
  function passApplyCliffs(ir, profile, estimatedInputTokens) {
151
240
  const mutations = [];
152
241
  const hints = { qualityWarning: [] };
153
242
  let nextIR = ir;
243
+ const sequentialMode = nextIR.constraints?.toolOrchestration === "sequential";
154
244
  for (const cliff of profile.cliffs) {
245
+ if (sequentialMode && cliff.reason.includes("L-040")) {
246
+ continue;
247
+ }
155
248
  let triggered = false;
156
249
  switch (cliff.metric) {
157
250
  case "input_tokens":
@@ -250,7 +343,8 @@ function passScoreTargets(ir, opts) {
250
343
  const preferredSet = new Set(policy.preferredModels ?? []);
251
344
  const scores = [];
252
345
  const policyMutations = [];
253
- for (const modelId of ir.models) {
346
+ const modelIds = ir.models.filter((m) => typeof m === "string");
347
+ for (const modelId of modelIds) {
254
348
  let profile;
255
349
  try {
256
350
  profile = opts.profilesById(modelId);
@@ -294,7 +388,7 @@ function passScoreTargets(ir, opts) {
294
388
  }
295
389
  const baseQuality = profile.strengths.includes("reasoning") ? 0.85 : profile.strengths.includes("quality") ? 0.8 : 0.6;
296
390
  const qualityScore = Math.max(0, baseQuality - qualityPenalty);
297
- const callerOrderBoost = (ir.models.length - ir.models.indexOf(modelId)) * 0.1;
391
+ const callerOrderBoost = (modelIds.length - modelIds.indexOf(modelId)) * 0.1;
298
392
  const costPenalty = estimatedCostUsd * 5;
299
393
  const preferredBoost = preferredSet.has(modelId) ? 0.5 : 0;
300
394
  const rank = qualityScore + callerOrderBoost - costPenalty - reasons.length * 10 + preferredBoost;
@@ -374,10 +468,16 @@ function lower(ir, profile, hints = {}) {
374
468
  }
375
469
  function lowerAnthropic(ir, profile, hints) {
376
470
  const systemBlocks = buildAnthropicSystemBlocks(ir.sections, profile);
377
- const messages = buildAnthropicMessages(ir.history ?? [], ir.currentTurn);
471
+ const history = (ir.history ?? []).filter((m) => m.role !== "system");
472
+ const policy = ir.historyCachePolicy;
473
+ const markIndex = resolveHistoryMarkIndex(history.length, policy);
474
+ const messages = buildAnthropicMessages(history, ir.currentTurn, markIndex);
378
475
  const tools = ir.tools ? toAnthropicTools(ir.tools) : void 0;
379
476
  const cacheableTokens = computeCacheableTokens(systemBlocks);
380
- const cacheSavings = cacheableTokens / 1e6 * profile.costInputPer1m * (1 - (profile.lowering.cache.discount ?? 0.1));
477
+ const historyCacheableTokens = markIndex >= 0 ? sumHistoryTokens(history, markIndex) : 0;
478
+ const totalCacheableTokens = cacheableTokens + historyCacheableTokens;
479
+ const cacheSavings = totalCacheableTokens / 1e6 * profile.costInputPer1m * (1 - (profile.lowering.cache.discount ?? 0.1));
480
+ const toolChoice = hints.wireOverrides?.parallelToolCalls === false && tools && tools.length > 0 ? { type: "auto", disable_parallel_tool_use: true } : void 0;
381
481
  return {
382
482
  request: {
383
483
  provider: "anthropic",
@@ -385,10 +485,16 @@ function lowerAnthropic(ir, profile, hints) {
385
485
  system: systemBlocks,
386
486
  messages,
387
487
  tools,
388
- max_tokens: hints.forceTerseOutput ? 200 : Math.min(profile.maxOutputTokens, 4096)
488
+ // alpha.8: trust profile.maxOutputTokens. The historical Math.min(_, 4096)
489
+ // floor surprised every consumer once (PB-Cairn contract-gaps brief, Gap 3).
490
+ // Profile is the single source of truth; consumers wanting a tighter
491
+ // budget can pass providerOverrides.anthropic.max_tokens explicitly.
492
+ max_tokens: hints.forceTerseOutput ? 200 : profile.maxOutputTokens,
493
+ tool_choice: toolChoice
389
494
  },
390
495
  diagnostics: {
391
496
  cacheableTokens,
497
+ historyCacheableTokens,
392
498
  estimatedCacheSavingsUsd: cacheSavings
393
499
  }
394
500
  };
@@ -421,17 +527,64 @@ function buildAnthropicSystemBlocks(sections, profile) {
421
527
  }
422
528
  return blocks;
423
529
  }
424
- function buildAnthropicMessages(history, currentTurn) {
530
+ function buildAnthropicMessages(history, currentTurn, markIndex) {
425
531
  const out = [];
426
- for (const m of history) {
532
+ for (let i = 0; i < history.length; i++) {
533
+ const m = history[i];
427
534
  if (m.role === "system") continue;
428
- out.push({ role: m.role, content: m.parts ?? m.content });
535
+ const shouldMark = i === markIndex;
536
+ out.push({
537
+ role: m.role,
538
+ content: shouldMark ? attachAnthropicCacheControl(m) : m.parts ?? m.content
539
+ });
429
540
  }
430
541
  if (currentTurn && currentTurn.role !== "system") {
431
542
  out.push({ role: currentTurn.role, content: currentTurn.parts ?? currentTurn.content });
432
543
  }
433
544
  return out;
434
545
  }
546
+ function attachAnthropicCacheControl(m) {
547
+ if (Array.isArray(m.parts) && m.parts.length > 0) {
548
+ const blocks = m.parts;
549
+ const last = blocks[blocks.length - 1];
550
+ const withMarker = {
551
+ ...last,
552
+ cache_control: { type: "ephemeral" }
553
+ };
554
+ return [...blocks.slice(0, -1), withMarker];
555
+ }
556
+ return [
557
+ {
558
+ type: "text",
559
+ text: m.content,
560
+ cache_control: { type: "ephemeral" }
561
+ }
562
+ ];
563
+ }
564
+ function resolveHistoryMarkIndex(historyLen, policy) {
565
+ if (!policy || policy.strategy === "none") return -1;
566
+ if (historyLen === 0) return -1;
567
+ if (policy.strategy === "all-but-latest") {
568
+ return historyLen - 1;
569
+ }
570
+ const idx = historyLen - 1 - policy.suffix;
571
+ return idx >= 0 ? idx : -1;
572
+ }
573
+ function sumHistoryTokens(history, throughIndex) {
574
+ let total = 0;
575
+ for (let i = 0; i <= throughIndex && i < history.length; i++) {
576
+ const m = history[i];
577
+ if (m.role === "system") continue;
578
+ if (Array.isArray(m.parts)) {
579
+ for (const p of m.parts) {
580
+ if (typeof p.text === "string") total += countTokens(p.text);
581
+ }
582
+ } else if (typeof m.content === "string") {
583
+ total += countTokens(m.content);
584
+ }
585
+ }
586
+ return total;
587
+ }
435
588
  function toAnthropicTools(tools) {
436
589
  return tools.map((t) => ({
437
590
  name: t.name,
@@ -466,6 +619,9 @@ function lowerGoogle(ir, profile, hints) {
466
619
  const minTokens = profile.lowering.cache.minTokens ?? 4096;
467
620
  const meetsMin = cacheableTokens >= minTokens;
468
621
  const cacheSavings = meetsMin ? cacheableTokens / 1e6 * profile.costInputPer1m * (1 - (profile.lowering.cache.discount ?? 0.25)) : 0;
622
+ const history = (ir.history ?? []).filter((m) => m.role !== "system");
623
+ const histMarkIndex = resolveHistoryMarkIndex(history.length, ir.historyCachePolicy);
624
+ const historyCacheableTokens = histMarkIndex >= 0 ? sumHistoryTokens(history, histMarkIndex) : 0;
469
625
  return {
470
626
  request: {
471
627
  provider: "google",
@@ -477,6 +633,7 @@ function lowerGoogle(ir, profile, hints) {
477
633
  },
478
634
  diagnostics: {
479
635
  cacheableTokens: meetsMin ? cacheableTokens : 0,
636
+ historyCacheableTokens,
480
637
  estimatedCacheSavingsUsd: cacheSavings
481
638
  }
482
639
  };
@@ -524,6 +681,10 @@ function lowerOpenAI(ir, profile, hints) {
524
681
  content: ir.currentTurn.parts ?? ir.currentTurn.content
525
682
  });
526
683
  }
684
+ const history = (ir.history ?? []).filter((m) => m.role !== "system");
685
+ const histMarkIndex = resolveHistoryMarkIndex(history.length, ir.historyCachePolicy);
686
+ const historyCacheableTokens = histMarkIndex >= 0 ? sumHistoryTokens(history, histMarkIndex) : 0;
687
+ const openaiParallelToolCalls = hints.wireOverrides?.parallelToolCalls === false && ir.tools && ir.tools.length > 0 ? false : void 0;
527
688
  return {
528
689
  request: {
529
690
  provider: "openai",
@@ -531,9 +692,14 @@ function lowerOpenAI(ir, profile, hints) {
531
692
  messages,
532
693
  tools: ir.tools && ir.tools.length > 0 ? toOpenAITools(ir.tools) : void 0,
533
694
  response_format: ir.constraints?.structuredOutput ? { type: "json_object" } : void 0,
534
- reasoning_effort: hints.forceTerseOutput ? "low" : void 0
695
+ reasoning_effort: hints.forceTerseOutput ? "low" : void 0,
696
+ parallel_tool_calls: openaiParallelToolCalls
535
697
  },
536
- diagnostics: { cacheableTokens: 0, estimatedCacheSavingsUsd: 0 }
698
+ diagnostics: {
699
+ cacheableTokens: 0,
700
+ historyCacheableTokens,
701
+ estimatedCacheSavingsUsd: 0
702
+ }
537
703
  };
538
704
  }
539
705
  function toOpenAITools(tools) {
@@ -560,6 +726,9 @@ function lowerDeepSeek(ir, profile) {
560
726
  content: ir.currentTurn.parts ?? ir.currentTurn.content
561
727
  });
562
728
  }
729
+ const history = (ir.history ?? []).filter((m) => m.role !== "system");
730
+ const histMarkIndex = resolveHistoryMarkIndex(history.length, ir.historyCachePolicy);
731
+ const historyCacheableTokens = histMarkIndex >= 0 ? sumHistoryTokens(history, histMarkIndex) : 0;
563
732
  return {
564
733
  request: {
565
734
  provider: "deepseek",
@@ -574,7 +743,11 @@ function lowerDeepSeek(ir, profile) {
574
743
  }
575
744
  })) : void 0
576
745
  },
577
- diagnostics: { cacheableTokens: 0, estimatedCacheSavingsUsd: 0 }
746
+ diagnostics: {
747
+ cacheableTokens: 0,
748
+ historyCacheableTokens,
749
+ estimatedCacheSavingsUsd: 0
750
+ }
578
751
  };
579
752
  }
580
753
  function sortSections(sections) {
@@ -597,204 +770,1667 @@ function setNestedField(obj, path, value) {
597
770
  cursor[parts[parts.length - 1]] = value;
598
771
  }
599
772
 
600
- // src/compile.ts
601
- var counter = 0;
602
- function makeHandle() {
603
- counter = (counter + 1) % 1e6;
604
- return `c${Date.now().toString(36)}-${counter.toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
773
+ // src/archetype-perf-brain.ts
774
+ function isPerfRow(x) {
775
+ if (!x || typeof x !== "object") return false;
776
+ const r = x;
777
+ return typeof r.model_id === "string" && typeof r.archetype === "string" && typeof r.perf_score === "number";
778
+ }
779
+ function mapRowsToPerfMap(rows) {
780
+ const out = /* @__PURE__ */ new Map();
781
+ for (const row of rows) {
782
+ if (!isPerfRow(row)) continue;
783
+ const existing = out.get(row.model_id) ?? {};
784
+ existing[row.archetype] = row.perf_score;
785
+ out.set(row.model_id, existing);
786
+ }
787
+ return out;
605
788
  }
606
- function compile(ir, opts = {}) {
607
- const resolver = opts.profileResolver ?? getProfile;
608
- validateIR(ir);
609
- const sliced = passSlice(ir);
610
- const deduped = passDedupe(sliced.value);
611
- const toolFiltered = passToolRelevance(deduped.value, {
612
- threshold: opts.toolRelevanceThreshold
613
- });
614
- const compressed = passCompressHistory(toolFiltered.value, {
615
- summarizeOlderThan: opts.compressHistoryAfter
616
- });
617
- let workingIR = compressed.value;
618
- const accumulatedMutations = [
619
- ...sliced.mutations,
620
- ...deduped.mutations,
621
- ...toolFiltered.mutations,
622
- ...compressed.mutations
623
- ];
624
- const inputTokens = estimateInputTokens(workingIR);
625
- const scores = passScoreTargets(workingIR, {
626
- estimatedInputTokens: inputTokens,
627
- profilesById: resolver,
628
- policy: opts.policy
629
- });
630
- accumulatedMutations.push(...scores.mutations);
631
- const target = pickTarget(workingIR, scores.value);
632
- if (!target) {
633
- throw new Error(
634
- `compile(): no allowed model fits the request. Scores: ${JSON.stringify(scores.value, null, 2)}`
635
- );
789
+ function mapRowsToNMap(rows) {
790
+ const out = /* @__PURE__ */ new Map();
791
+ for (const row of rows) {
792
+ if (!isPerfRow(row)) continue;
793
+ if (typeof row.n !== "number") continue;
794
+ const existing = out.get(row.model_id) ?? {};
795
+ existing[row.archetype] = row.n;
796
+ out.set(row.model_id, existing);
636
797
  }
637
- const profile = resolver(target.modelId);
638
- const fallbackChain = scores.value.filter((s) => s.modelId !== target.modelId && s.fits).sort((a, b) => b.rank - a.rank).map((s) => s.modelId);
639
- const cliffs = passApplyCliffs(workingIR, profile, inputTokens);
640
- workingIR = cliffs.value.ir;
641
- accumulatedMutations.push(...cliffs.mutations);
642
- const lowered = lower(workingIR, profile, {
643
- forceThinkingZero: cliffs.value.loweringHints.forceThinkingZero,
644
- forceTerseOutput: cliffs.value.loweringHints.forceTerseOutput
645
- });
646
- validateFinalFit(workingIR, profile, inputTokens);
647
- const handle = makeHandle();
648
- const finalShape = computeShape(workingIR, inputTokens);
649
- const _learningKey = learningKey(ir.intent.archetype, profile.id, finalShape);
650
- return {
651
- handle,
652
- target: profile.id,
653
- provider: profile.provider,
654
- request: lowered.request,
655
- tokensIn: inputTokens,
656
- estimatedCostUsd: target.estimatedCostUsd,
657
- mutationsApplied: accumulatedMutations,
658
- fallbackChain,
659
- diagnostics: {
660
- sectionsKept: workingIR.sections.length,
661
- sectionsDropped: ir.sections.length - workingIR.sections.length,
662
- toolsKept: workingIR.tools?.length ?? 0,
663
- toolsDropped: (ir.tools?.length ?? 0) - (workingIR.tools?.length ?? 0),
664
- historyKept: workingIR.history?.length ?? 0,
665
- historyDropped: (ir.history?.length ?? 0) - (workingIR.history?.length ?? 0),
666
- cacheableTokens: lowered.diagnostics.cacheableTokens,
667
- estimatedCacheSavingsUsd: lowered.diagnostics.estimatedCacheSavingsUsd
798
+ return out;
799
+ }
800
+ function bundledArchetypePerf() {
801
+ const out = /* @__PURE__ */ new Map();
802
+ for (const profile of allProfiles()) {
803
+ if (profile.archetypePerf) out.set(profile.id, profile.archetypePerf);
804
+ }
805
+ return out;
806
+ }
807
+ function bundledArchetypePerfN() {
808
+ return /* @__PURE__ */ new Map();
809
+ }
810
+ var loadArchetypePerfFromBrain = createBrainQueryCache({
811
+ table: "kgauto_archetype_perf",
812
+ mapRows: mapRowsToPerfMap,
813
+ bundledFallback: bundledArchetypePerf
814
+ });
815
+ var loadArchetypePerfNFromBrain = createBrainQueryCache(
816
+ {
817
+ table: "kgauto_archetype_perf",
818
+ mapRows: mapRowsToNMap,
819
+ bundledFallback: bundledArchetypePerfN
820
+ }
821
+ );
822
+ var MEASURED_GROUNDING_MIN_N = 10;
823
+ function getArchetypePerfScore(modelId, archetype) {
824
+ const score = loadArchetypePerfFromBrain().get(modelId)?.[archetype] ?? 5;
825
+ const n = loadArchetypePerfNFromBrain().get(modelId)?.[archetype] ?? 0;
826
+ const grounding = n >= MEASURED_GROUNDING_MIN_N ? "measured" : "judgment";
827
+ return { score, n, grounding };
828
+ }
829
+
830
+ // src/exclusion-findings-brain.ts
831
+ function isValidVerdict(v) {
832
+ return v === "recommend-probe" || v === "unblock" || v === "stay-excluded" || v === "inconclusive";
833
+ }
834
+ function isValidConfidence(v) {
835
+ return v === "high" || v === "medium" || v === "low";
836
+ }
837
+ function isRawFindingRow(x) {
838
+ if (!x || typeof x !== "object") return false;
839
+ const r = x;
840
+ return typeof r.intent_archetype === "string" && typeof r.excluded_model === "string" && typeof r.excluded_provider === "string" && typeof r.verdict === "string" && typeof r.message === "string" && typeof r.suggestion === "string" && typeof r.confidence === "string";
841
+ }
842
+ function mapRowsToFindings(rows) {
843
+ const out = [];
844
+ for (const row of rows) {
845
+ if (!isRawFindingRow(row)) continue;
846
+ if (!isValidVerdict(row.verdict)) continue;
847
+ if (!isValidConfidence(row.confidence)) continue;
848
+ let savings = null;
849
+ if (typeof row.estimated_savings_usd_30d === "number") {
850
+ savings = Number.isFinite(row.estimated_savings_usd_30d) ? row.estimated_savings_usd_30d : null;
851
+ } else if (typeof row.estimated_savings_usd_30d === "string") {
852
+ const n = Number(row.estimated_savings_usd_30d);
853
+ savings = Number.isFinite(n) ? n : null;
668
854
  }
669
- };
855
+ out.push({
856
+ archetype: row.intent_archetype,
857
+ excludedModel: row.excluded_model,
858
+ excludedProvider: row.excluded_provider,
859
+ verdict: row.verdict,
860
+ estimatedSavingsUsd30d: savings,
861
+ message: row.message,
862
+ suggestion: row.suggestion,
863
+ confidence: row.confidence,
864
+ evidence: row.evidence && typeof row.evidence === "object" ? row.evidence : void 0
865
+ });
866
+ }
867
+ return out;
670
868
  }
671
- function validateIR(ir) {
672
- if (!ir.appId) throw new Error("compile(): ir.appId is required");
673
- if (!ir.intent || !ir.intent.archetype) {
674
- throw new Error("compile(): ir.intent.archetype is required (use a dialect-v1 archetype)");
869
+ var snapshots = /* @__PURE__ */ new Map();
870
+ var runtime;
871
+ var warnedOnce = false;
872
+ var DEFAULT_FINDINGS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/findings/exclusions";
873
+ function configureExclusionFindingsBrain(rt) {
874
+ runtime = rt;
875
+ snapshots.clear();
876
+ warnedOnce = false;
877
+ }
878
+ function isExclusionFindingsBrainActive() {
879
+ return runtime !== void 0;
880
+ }
881
+ function getStaleExclusionFindings(opts) {
882
+ const rt = runtime;
883
+ if (!rt) return [];
884
+ const appId = opts.appId;
885
+ if (!appId) return [];
886
+ let snap = snapshots.get(appId);
887
+ if (!snap) {
888
+ snap = { data: [], expiresAt: 0, refreshing: false };
889
+ snapshots.set(appId, snap);
675
890
  }
676
- if (!Array.isArray(ir.models) || ir.models.length === 0) {
677
- throw new Error("compile(): ir.models must be a non-empty array");
891
+ const now = Date.now();
892
+ const stale = snap.expiresAt <= now;
893
+ if (stale && !snap.refreshing) {
894
+ snap.refreshing = true;
895
+ void asyncRefresh(rt, appId);
678
896
  }
679
- if (!Array.isArray(ir.sections)) {
680
- throw new Error("compile(): ir.sections must be an array");
897
+ if (opts.archetype) {
898
+ return snap.data.filter((f) => f.archetype === opts.archetype);
681
899
  }
900
+ return snap.data;
682
901
  }
683
- function pickTarget(ir, scores) {
684
- if (ir.constraints?.forceModel) {
685
- const forced = scores.find((s) => s.modelId === ir.constraints.forceModel);
686
- if (forced && forced.fits) return forced;
687
- if (forced) {
688
- throw new Error(
689
- `compile(): forceModel="${ir.constraints.forceModel}" does not fit: ${forced.rejectReasons.join("; ")}`
690
- );
902
+ var pendingRefreshes = /* @__PURE__ */ new Map();
903
+ async function asyncRefresh(rt, appId) {
904
+ const promise = doRefresh(rt, appId);
905
+ pendingRefreshes.set(appId, promise);
906
+ try {
907
+ await promise;
908
+ } finally {
909
+ if (pendingRefreshes.get(appId) === promise) {
910
+ pendingRefreshes.delete(appId);
691
911
  }
692
912
  }
693
- const fitting = scores.filter((s) => s.fits).sort((a, b) => b.rank - a.rank);
694
- return fitting[0];
695
913
  }
696
- function validateFinalFit(ir, profile, tokens) {
697
- if (tokens > profile.maxContextTokens) {
698
- throw new Error(
699
- `compile(): final IR is ${tokens} tokens, exceeds ${profile.id} context (${profile.maxContextTokens})`
700
- );
914
+ async function doRefresh(rt, appId) {
915
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
916
+ let snap = snapshots.get(appId);
917
+ if (!snap) {
918
+ snap = { data: [], expiresAt: 0, refreshing: false };
919
+ snapshots.set(appId, snap);
701
920
  }
702
- if ((ir.tools?.length ?? 0) > profile.maxTools) {
703
- throw new Error(
704
- `compile(): final IR has ${ir.tools?.length} tools, exceeds ${profile.id} maxTools (${profile.maxTools})`
705
- );
921
+ try {
922
+ const res = await rt.fetchImpl(url, { method: "GET" });
923
+ if (!res.ok) {
924
+ throw new Error(`findings ${res.status}: ${res.statusText}`);
925
+ }
926
+ const body = await res.json();
927
+ if (runtime !== rt) return;
928
+ const rows = Array.isArray(body) ? mapRowsToFindings(body) : [];
929
+ snap.data = rows;
930
+ snap.expiresAt = Date.now() + rt.ttlMs;
931
+ snap.refreshing = false;
932
+ } catch (err) {
933
+ if (runtime !== rt) return;
934
+ snap.refreshing = false;
935
+ snap.expiresAt = Date.now() + rt.ttlMs;
936
+ if (!warnedOnce) {
937
+ warnedOnce = true;
938
+ (rt.onError ?? defaultOnError)(err);
939
+ }
706
940
  }
707
941
  }
942
+ function defaultOnError(err) {
943
+ console.warn(
944
+ "[kgauto] exclusion-findings fetch failed (using empty fallback):",
945
+ err
946
+ );
947
+ }
708
948
 
709
- // src/brain.ts
710
- var activeConfig;
711
- function configureBrain(config) {
712
- const endpoint = config.endpoint.replace(/\/outcomes\/?$/, "");
713
- activeConfig = { ...config, endpoint };
949
+ // src/promote-ready-brain.ts
950
+ function isRawPromoteReadyRow(x) {
951
+ if (!x || typeof x !== "object") return false;
952
+ const r = x;
953
+ return typeof r.intent_archetype === "string" && typeof r.family === "string" && typeof r.candidate_model === "string" && typeof r.current_model === "string" && typeof r.detected_at === "string";
954
+ }
955
+ function coerceNumber(v) {
956
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
957
+ if (typeof v === "string") {
958
+ const n = Number(v);
959
+ return Number.isFinite(n) ? n : null;
960
+ }
961
+ return null;
714
962
  }
715
- function clearBrain() {
716
- activeConfig = void 0;
963
+ function mapRowsToFindings2(rows) {
964
+ const out = [];
965
+ for (const row of rows) {
966
+ if (!isRawPromoteReadyRow(row)) continue;
967
+ const sampleN = coerceNumber(row.sample_n);
968
+ const passRate = coerceNumber(row.judge_pass_rate);
969
+ const avgScore = coerceNumber(row.judge_avg_score);
970
+ if (sampleN === null || passRate === null || avgScore === null) continue;
971
+ out.push({
972
+ archetype: row.intent_archetype,
973
+ family: row.family,
974
+ candidateModel: row.candidate_model,
975
+ currentModel: row.current_model,
976
+ sampleN,
977
+ judgePassRate: passRate,
978
+ judgeAvgScore: avgScore,
979
+ costDeltaPct: coerceNumber(row.cost_delta_pct),
980
+ detectedAt: row.detected_at
981
+ });
982
+ }
983
+ return out;
717
984
  }
718
- var compileRegistry = /* @__PURE__ */ new Map();
719
- var REGISTRY_MAX_ENTRIES = 1e4;
720
- function registerCompile(appId, archetype, ir, result) {
721
- if (compileRegistry.size >= REGISTRY_MAX_ENTRIES) {
722
- const cutoff = Math.floor(REGISTRY_MAX_ENTRIES * 0.25);
723
- let evicted = 0;
724
- for (const k of compileRegistry.keys()) {
725
- compileRegistry.delete(k);
726
- if (++evicted >= cutoff) break;
985
+ var snapshots2 = /* @__PURE__ */ new Map();
986
+ var runtime2;
987
+ var warnedOnce2 = false;
988
+ function isPromoteReadyBrainActive() {
989
+ return runtime2 !== void 0;
990
+ }
991
+ function loadPromoteReadyFindings(opts) {
992
+ const rt = runtime2;
993
+ if (!rt) return [];
994
+ const appId = opts.appId;
995
+ if (!appId) return [];
996
+ let snap = snapshots2.get(appId);
997
+ if (!snap) {
998
+ snap = { data: [], expiresAt: 0, refreshing: false };
999
+ snapshots2.set(appId, snap);
1000
+ }
1001
+ const now = Date.now();
1002
+ const stale = snap.expiresAt <= now;
1003
+ if (stale && !snap.refreshing) {
1004
+ snap.refreshing = true;
1005
+ void asyncRefresh2(rt, appId);
1006
+ }
1007
+ let rows = snap.data;
1008
+ if (opts.archetype) {
1009
+ rows = rows.filter((f) => f.archetype === opts.archetype);
1010
+ }
1011
+ if (opts.family) {
1012
+ rows = rows.filter((f) => f.family === opts.family);
1013
+ }
1014
+ return rows;
1015
+ }
1016
+ var pendingRefreshes2 = /* @__PURE__ */ new Map();
1017
+ async function asyncRefresh2(rt, appId) {
1018
+ const promise = doRefresh2(rt, appId);
1019
+ pendingRefreshes2.set(appId, promise);
1020
+ try {
1021
+ await promise;
1022
+ } finally {
1023
+ if (pendingRefreshes2.get(appId) === promise) {
1024
+ pendingRefreshes2.delete(appId);
727
1025
  }
728
1026
  }
729
- const tokens = result.tokensIn;
730
- const shape = computeShape(
731
- {
732
- appId,
733
- intent: { name: archetype, archetype },
734
- sections: ir.sections,
735
- tools: ir.tools,
736
- history: ir.history,
737
- models: ir.models,
738
- constraints: ir.constraints
739
- },
740
- tokens
741
- );
742
- const shapeKey = `${shape.contextBucket}-${shape.toolCountBucket}-${shape.historyDepth}-${shape.outputMode}`;
743
- compileRegistry.set(result.handle, {
744
- appId,
745
- archetype,
746
- model: result.target,
747
- provider: result.provider,
748
- shapeKey,
749
- learningKey: learningKey(archetype, result.target, shape),
750
- estimatedTokensIn: tokens,
751
- mutationsApplied: result.mutationsApplied.map((m) => m.id),
752
- startedAt: Date.now()
753
- });
754
1027
  }
755
- async function record(input) {
756
- const reg = compileRegistry.get(input.handle);
757
- if (reg) compileRegistry.delete(input.handle);
758
- if (!activeConfig) {
759
- return;
1028
+ async function doRefresh2(rt, appId) {
1029
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
1030
+ let snap = snapshots2.get(appId);
1031
+ if (!snap) {
1032
+ snap = { data: [], expiresAt: 0, refreshing: false };
1033
+ snapshots2.set(appId, snap);
760
1034
  }
761
- const payload = buildPayload(input, reg);
762
- const config = activeConfig;
763
- const fetchFn = config.fetchImpl ?? fetch;
764
- const send = async () => {
765
- try {
766
- const res = await fetchFn(`${config.endpoint}/outcomes`, {
767
- method: "POST",
768
- headers: {
769
- "Content-Type": "application/json",
770
- ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
771
- },
772
- body: JSON.stringify(payload)
773
- });
774
- if (!res.ok) {
775
- const text = await res.text().catch(() => "<no body>");
776
- throw new Error(`brain ${res.status}: ${text}`);
777
- }
778
- } catch (err) {
779
- (config.onError ?? defaultOnError)(err);
1035
+ try {
1036
+ const res = await rt.fetchImpl(url, { method: "GET" });
1037
+ if (!res.ok) {
1038
+ throw new Error(`promote-ready ${res.status}: ${res.statusText}`);
1039
+ }
1040
+ const body = await res.json();
1041
+ if (runtime2 !== rt) return;
1042
+ const rows = Array.isArray(body) ? mapRowsToFindings2(body) : [];
1043
+ snap.data = rows;
1044
+ snap.expiresAt = Date.now() + rt.ttlMs;
1045
+ snap.refreshing = false;
1046
+ } catch (err) {
1047
+ if (runtime2 !== rt) return;
1048
+ snap.refreshing = false;
1049
+ snap.expiresAt = Date.now() + rt.ttlMs;
1050
+ if (!warnedOnce2) {
1051
+ warnedOnce2 = true;
1052
+ (rt.onError ?? defaultOnError2)(err);
780
1053
  }
781
- };
782
- if (config.sync) {
783
- await send();
784
- } else {
785
- void send();
786
1054
  }
787
1055
  }
788
- function defaultOnError(err) {
789
- console.warn("[kgauto] brain record failed:", err);
1056
+ function defaultOnError2(err) {
1057
+ console.warn(
1058
+ "[kgauto] promote-ready fetch failed (using empty fallback):",
1059
+ err
1060
+ );
790
1061
  }
791
- function buildPayload(input, reg) {
792
- const compileTarget = reg?.model;
793
- const actual = input.actualModel ?? compileTarget;
794
- const requested = input.actualModel && compileTarget && input.actualModel !== compileTarget ? compileTarget : void 0;
795
- const mutationsApplied = input.mutationsApplied ?? reg?.mutationsApplied ?? [];
796
- const costModel = actual;
797
- const costUsdActual = costModel ? computeCostUsd(costModel, input.tokensIn, input.tokensOut) : void 0;
1062
+ function resolveFetchImpl(injected) {
1063
+ return injected ?? ((...args) => globalThis.fetch(...args));
1064
+ }
1065
+ function normalizeEndpoint(endpoint) {
1066
+ return endpoint.replace(/\/+$/, "");
1067
+ }
1068
+ async function markPromoteReadyHandled(opts) {
1069
+ const {
1070
+ appId,
1071
+ archetype,
1072
+ family,
1073
+ resolution,
1074
+ resolutionNote,
1075
+ brainEndpoint,
1076
+ brainJwt,
1077
+ brainAnonKey,
1078
+ fetch: injectedFetch
1079
+ } = opts;
1080
+ if (!appId) return { ok: false, reason: "app_id_required" };
1081
+ if (!archetype) return { ok: false, reason: "archetype_required" };
1082
+ if (!family) return { ok: false, reason: "family_required" };
1083
+ if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
1084
+ return { ok: false, reason: "resolution_invalid" };
1085
+ }
1086
+ const doFetch = resolveFetchImpl(injectedFetch);
1087
+ const base = normalizeEndpoint(brainEndpoint);
1088
+ const url = `${base}/rest/v1/promote_ready_findings?app_id=eq.${encodeURIComponent(appId)}&intent_archetype=eq.${encodeURIComponent(archetype)}&family=eq.${encodeURIComponent(family)}&resolved_at=is.null`;
1089
+ const patchBody = {
1090
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
1091
+ resolution
1092
+ };
1093
+ if (resolutionNote !== void 0) {
1094
+ patchBody.resolution_note = resolutionNote;
1095
+ }
1096
+ let res;
1097
+ try {
1098
+ res = await doFetch(url, {
1099
+ method: "PATCH",
1100
+ headers: {
1101
+ Authorization: `Bearer ${brainJwt}`,
1102
+ apikey: brainAnonKey,
1103
+ "Content-Type": "application/json",
1104
+ Accept: "application/json",
1105
+ Prefer: "return=minimal"
1106
+ },
1107
+ body: JSON.stringify(patchBody)
1108
+ });
1109
+ } catch (err) {
1110
+ const msg = err instanceof Error ? err.message : String(err);
1111
+ return { ok: false, reason: `network_error:${msg}` };
1112
+ }
1113
+ if (res.status === 401 || res.status === 403) {
1114
+ return { ok: false, reason: "brain_auth_misconfig" };
1115
+ }
1116
+ if (res.status >= 500) {
1117
+ return { ok: false, reason: "brain_unavailable" };
1118
+ }
1119
+ if (!res.ok) {
1120
+ return { ok: false, reason: `patch_failed:${res.status}` };
1121
+ }
1122
+ return { ok: true };
1123
+ }
1124
+
1125
+ // src/advisor-rules/promote-ready.ts
1126
+ var PROMOTE_READY_THRESHOLDS = {
1127
+ minPassRate: 0.8,
1128
+ minAvgScore: 4
1129
+ };
1130
+ function shouldFirePromoteReady(finding, resolvedPrimary) {
1131
+ if (finding.currentModel !== resolvedPrimary) return false;
1132
+ if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
1133
+ if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
1134
+ return true;
1135
+ }
1136
+ function deriveFamilyLocal(modelId) {
1137
+ if (modelId.startsWith("claude-opus-")) return "claude-opus";
1138
+ if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
1139
+ if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
1140
+ if (/^gemini-.*-flash-lite/.test(modelId)) return "gemini-flash-lite";
1141
+ if (/^gemini-.*-flash/.test(modelId)) return "gemini-flash";
1142
+ if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
1143
+ if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
1144
+ if (modelId.startsWith("deepseek-")) return "deepseek-chat";
1145
+ if (modelId.startsWith("gpt-")) return "openai-gpt";
1146
+ return null;
1147
+ }
1148
+ function advisorRulePromoteReady(ctx) {
1149
+ if (!isPromoteReadyBrainActive()) return [];
1150
+ if (!ctx.appId) return [];
1151
+ if (!ctx.resolvedPrimary) return [];
1152
+ const family = deriveFamilyLocal(ctx.resolvedPrimary);
1153
+ if (!family) return [];
1154
+ const findings = loadPromoteReadyFindings({
1155
+ appId: ctx.appId,
1156
+ archetype: ctx.archetype,
1157
+ family
1158
+ });
1159
+ if (findings.length === 0) return [];
1160
+ const qualifying = findings.filter(
1161
+ (f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
1162
+ );
1163
+ if (qualifying.length === 0) return [];
1164
+ qualifying.sort((a, b) => {
1165
+ if (a.judgeAvgScore !== b.judgeAvgScore) {
1166
+ return b.judgeAvgScore - a.judgeAvgScore;
1167
+ }
1168
+ return b.judgePassRate - a.judgePassRate;
1169
+ });
1170
+ const top = qualifying[0];
1171
+ const pctPass = Math.round(top.judgePassRate * 100);
1172
+ const score = top.judgeAvgScore.toFixed(2);
1173
+ let costClause = "";
1174
+ if (top.costDeltaPct !== null) {
1175
+ const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
1176
+ const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
1177
+ costClause = `, cost ${magnitude}% ${sign}`;
1178
+ }
1179
+ const message = `Probe found ${top.candidateModel} produces equivalent-or-better outputs vs ${top.currentModel} on ${top.sampleN} recent ${top.archetype} prompts (pass rate ${pctPass}%, avg score ${score}/5${costClause}). Consider promoting via markPromoteReadyHandled.`;
1180
+ return [
1181
+ {
1182
+ level: "info",
1183
+ code: "promote-ready",
1184
+ message,
1185
+ suggestion: `Migrate ${top.archetype} traffic from ${top.currentModel} to ${top.candidateModel}, then call markPromoteReadyHandled({ appId, archetype: '${top.archetype}', family: '${top.family}', resolution: 'promoted' }) to silence this advisory.`,
1186
+ // alpha.36 architectural field — not a no-ai-needed case.
1187
+ recommendedArchitecture: void 0,
1188
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1189
+ }
1190
+ ];
1191
+ }
1192
+
1193
+ // src/advisor-rules/consumer-on-stale-model.ts
1194
+ function isStaleStatus(v) {
1195
+ return v === "legacy" || v === "deprecated";
1196
+ }
1197
+ function asString(v) {
1198
+ return typeof v === "string" && v.length > 0 ? v : void 0;
1199
+ }
1200
+ function mapRowsToFindings3(rows) {
1201
+ const out = [];
1202
+ for (const raw of rows) {
1203
+ if (!raw || typeof raw !== "object") continue;
1204
+ const r = raw;
1205
+ const archetype = asString(r.intent_archetype) ?? asString(r.applies_to_archetype);
1206
+ const staleModel = asString(r.stale_model) ?? asString(r.applies_to_model);
1207
+ const staleProvider = asString(r.stale_provider);
1208
+ const recommendedModel = asString(r.recommended_model);
1209
+ const family = asString(r.family);
1210
+ const message = asString(r.message);
1211
+ if (!archetype || !staleModel || !recommendedModel || !family || !message) {
1212
+ continue;
1213
+ }
1214
+ if (!isStaleStatus(r.stale_status)) continue;
1215
+ const row = {
1216
+ archetype,
1217
+ staleModel,
1218
+ staleProvider: staleProvider ?? "unknown",
1219
+ staleStatus: r.stale_status,
1220
+ recommendedModel,
1221
+ family,
1222
+ message
1223
+ };
1224
+ const suggestion = asString(r.suggestion);
1225
+ if (suggestion) row.suggestion = suggestion;
1226
+ if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
1227
+ row.observationCount = r.observation_count;
1228
+ }
1229
+ out.push(row);
1230
+ }
1231
+ return out;
1232
+ }
1233
+ var snapshots3 = /* @__PURE__ */ new Map();
1234
+ var runtime3;
1235
+ var warnedOnce3 = false;
1236
+ var pendingRefreshes3 = /* @__PURE__ */ new Map();
1237
+ function isStaleModelFindingsBrainActive() {
1238
+ return runtime3 !== void 0;
1239
+ }
1240
+ function getStaleModelFindings(opts) {
1241
+ const rt = runtime3;
1242
+ if (!rt) return [];
1243
+ const appId = opts.appId;
1244
+ if (!appId) return [];
1245
+ let snap = snapshots3.get(appId);
1246
+ if (!snap) {
1247
+ snap = { data: [], expiresAt: 0, refreshing: false };
1248
+ snapshots3.set(appId, snap);
1249
+ }
1250
+ const now = Date.now();
1251
+ const stale = snap.expiresAt <= now;
1252
+ if (stale && !snap.refreshing) {
1253
+ snap.refreshing = true;
1254
+ void asyncRefresh3(rt, appId);
1255
+ }
1256
+ if (opts.archetype) {
1257
+ return snap.data.filter((f) => f.archetype === opts.archetype);
1258
+ }
1259
+ return snap.data;
1260
+ }
1261
+ async function asyncRefresh3(rt, appId) {
1262
+ const promise = doRefresh3(rt, appId);
1263
+ pendingRefreshes3.set(appId, promise);
1264
+ try {
1265
+ await promise;
1266
+ } finally {
1267
+ if (pendingRefreshes3.get(appId) === promise) {
1268
+ pendingRefreshes3.delete(appId);
1269
+ }
1270
+ }
1271
+ }
1272
+ async function doRefresh3(rt, appId) {
1273
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
1274
+ let snap = snapshots3.get(appId);
1275
+ if (!snap) {
1276
+ snap = { data: [], expiresAt: 0, refreshing: false };
1277
+ snapshots3.set(appId, snap);
1278
+ }
1279
+ try {
1280
+ const res = await rt.fetchImpl(url, { method: "GET" });
1281
+ if (!res.ok) {
1282
+ throw new Error(`stale-model findings ${res.status}: ${res.statusText}`);
1283
+ }
1284
+ const body = await res.json();
1285
+ if (runtime3 !== rt) return;
1286
+ const rows = Array.isArray(body) ? mapRowsToFindings3(body) : [];
1287
+ snap.data = rows;
1288
+ snap.expiresAt = Date.now() + rt.ttlMs;
1289
+ snap.refreshing = false;
1290
+ } catch (err) {
1291
+ if (runtime3 !== rt) return;
1292
+ snap.refreshing = false;
1293
+ snap.expiresAt = Date.now() + rt.ttlMs;
1294
+ if (!warnedOnce3) {
1295
+ warnedOnce3 = true;
1296
+ (rt.onError ?? defaultOnError3)(err);
1297
+ }
1298
+ }
1299
+ }
1300
+ function defaultOnError3(err) {
1301
+ console.warn(
1302
+ "[kgauto] stale-model findings fetch failed (using empty fallback):",
1303
+ err
1304
+ );
1305
+ }
1306
+ var CONSUMER_ON_STALE_MODEL_RULE_CODE = "consumer-on-stale-model";
1307
+ function advisorRuleConsumerOnStaleModel(ir) {
1308
+ if (!isStaleModelFindingsBrainActive()) return [];
1309
+ if (!ir.appId) return [];
1310
+ const findings = getStaleModelFindings({
1311
+ appId: ir.appId,
1312
+ archetype: ir.intent.archetype
1313
+ });
1314
+ if (findings.length === 0) return [];
1315
+ const ranked = [...findings].sort((a, b) => {
1316
+ if (a.staleStatus !== b.staleStatus) {
1317
+ return a.staleStatus === "deprecated" ? -1 : 1;
1318
+ }
1319
+ return a.staleModel.localeCompare(b.staleModel);
1320
+ });
1321
+ const top = ranked[0];
1322
+ const extraCount = findings.length - 1;
1323
+ const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
1324
+ return [
1325
+ {
1326
+ level: "warn",
1327
+ code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
1328
+ message: `${top.message}${extraNote}`,
1329
+ suggestion: top.suggestion ?? `Migrate ${top.staleModel} \u2192 ${top.recommendedModel} for archetype "${top.archetype}". The newer model is the current latest in the "${top.family}" family; the stale one is ${top.staleStatus}.`,
1330
+ recommendationType: "model-swap",
1331
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1332
+ }
1333
+ ];
1334
+ }
1335
+
1336
+ // src/advisor.ts
1337
+ var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
1338
+ var TIER_DOWN_COST_RATIO = 0.5;
1339
+ var COST_MISMATCHED_CHOSEN_SCORE_CEILING = 7;
1340
+ var PRODUCER_OWNED_RULE_CODES = Object.freeze(
1341
+ /* @__PURE__ */ new Set(["model-stale-evidence", "promote-ready"])
1342
+ );
1343
+ function deriveOwnership(code, selfDeclared) {
1344
+ if (selfDeclared) return selfDeclared;
1345
+ return PRODUCER_OWNED_RULE_CODES.has(code) ? "producer-owned" : "consumer-actionable";
1346
+ }
1347
+ function runAdvisor(ir, result, profile, policy, phase2) {
1348
+ const out = [];
1349
+ out.push(...detectCachingOff(ir, profile));
1350
+ out.push(...detectSingleChunkSystem(ir, profile));
1351
+ out.push(...detectToolBloat(ir, result));
1352
+ out.push(...detectHistoryUncached(ir, profile));
1353
+ out.push(...detectSingleModelArray(ir, policy));
1354
+ if (policy?.posture !== "locked") {
1355
+ out.push(...detectCostMismatchedArchetype(ir, profile, phase2));
1356
+ out.push(...detectModelStaleEvidence(ir, profile));
1357
+ out.push(...detectTierDown(ir, profile, phase2));
1358
+ }
1359
+ if (!translatorClearedToolCallCliff(phase2)) {
1360
+ out.push(...detectArchetypePerfFloorBreach(ir, profile));
1361
+ }
1362
+ if (policy?.posture !== "locked") {
1363
+ out.push(...detectStaleExclusionCandidate(ir));
1364
+ }
1365
+ if (policy?.posture !== "locked" && ir.appId) {
1366
+ out.push(
1367
+ ...advisorRulePromoteReady({
1368
+ appId: ir.appId,
1369
+ archetype: ir.intent.archetype,
1370
+ resolvedPrimary: profile.id
1371
+ })
1372
+ );
1373
+ out.push(...advisorRuleConsumerOnStaleModel(ir));
1374
+ }
1375
+ return out;
1376
+ }
1377
+ function translatorClearedToolCallCliff(phase2) {
1378
+ const rewrites = phase2?.sectionRewritesApplied;
1379
+ if (!rewrites || rewrites.length === 0) return false;
1380
+ for (const rw of rewrites) {
1381
+ if (rw.kind === "tool_call_contract") return true;
1382
+ }
1383
+ return false;
1384
+ }
1385
+ function detectCachingOff(ir, profile) {
1386
+ if (profile.provider !== "anthropic") return [];
1387
+ const totalChars = ir.sections.reduce((s, sec) => s + sec.text.length, 0);
1388
+ if (totalChars < 2e3) return [];
1389
+ const anyCacheable = ir.sections.some((s) => s.cacheable === true);
1390
+ if (anyCacheable) return [];
1391
+ return [
1392
+ {
1393
+ level: "warn",
1394
+ code: "caching-off-on-claude",
1395
+ message: `System prompt is ${totalChars} chars on Anthropic but no PromptSection has cacheable=true. Anthropic prompt caching cuts cached-prefix input cost by ~90% on subsequent calls; without it, every turn re-pays full price for the static system context.`,
1396
+ suggestion: "Mark stable system sections (role, persona, tool policy) with `cacheable: true`. The lowering pass concatenates cacheable sections into a single cache-controlled block before the dynamic ones.",
1397
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1398
+ }
1399
+ ];
1400
+ }
1401
+ function detectSingleChunkSystem(ir, profile) {
1402
+ if (profile.provider !== "anthropic") return [];
1403
+ if (ir.sections.length !== 1) return [];
1404
+ const only = ir.sections[0];
1405
+ if (!only || only.text.length <= 1e3) return [];
1406
+ return [
1407
+ {
1408
+ level: "info",
1409
+ code: "single-chunk-system",
1410
+ message: `System prompt is a single ${only.text.length}-char chunk. Splitting into NamedChunks (static role/persona vs dynamic context) gives the lowering pass a finer cache-marker boundary \u2014 only the static portion needs to be byte-stable for the cache to hit.`,
1411
+ suggestion: "Refactor the system builder to return an array of `PromptSection` shaped { id, text, cacheable?: boolean }. Static chunks (role, persona, tool policy) get `cacheable: true`; dynamic ones (current context, today's date) don't.",
1412
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1413
+ }
1414
+ ];
1415
+ }
1416
+ function detectToolBloat(ir, result) {
1417
+ const SHORT_OUTPUT = /* @__PURE__ */ new Set([
1418
+ "classify",
1419
+ "extract",
1420
+ "summarize",
1421
+ "transform",
1422
+ "critique"
1423
+ ]);
1424
+ if (!ir.tools || ir.tools.length === 0) return [];
1425
+ const toolsKept = result.diagnostics.toolsKept;
1426
+ if (toolsKept <= 10) return [];
1427
+ if (!SHORT_OUTPUT.has(ir.intent.archetype)) return [];
1428
+ return [
1429
+ {
1430
+ level: "warn",
1431
+ code: "tool-bloat",
1432
+ message: `${toolsKept} tools kept after the relevance pass for archetype="${ir.intent.archetype}" (consumer declared ${ir.tools.length}). This archetype is short-output and rarely needs more than 3 tools; each tool definition eats ~350 tokens of context budget.`,
1433
+ suggestion: "Tighten `relevanceByIntent: { [archetype]: 0..1 }` per ToolDefinition. Tools below `toolRelevanceThreshold` (default 0.2) get dropped. Without `relevanceByIntent`, every tool defaults to neutral (0.5) and stays.",
1434
+ docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
1435
+ }
1436
+ ];
1437
+ }
1438
+ function detectHistoryUncached(ir, profile) {
1439
+ if (profile.provider !== "anthropic") return [];
1440
+ if (!ir.history || ir.history.length < 2) return [];
1441
+ if (ir.historyCachePolicy && ir.historyCachePolicy.strategy !== "none") {
1442
+ return [];
1443
+ }
1444
+ return [
1445
+ {
1446
+ level: "warn",
1447
+ code: "history-uncached-on-claude",
1448
+ message: `${ir.history.length} history messages on Anthropic with no historyCachePolicy. Every turn re-pays for the full conversation context; with caching, subsequent turns hit the cache at ~10% the input cost.`,
1449
+ suggestion: "Set `historyCachePolicy: { strategy: 'all-but-latest' }` on this IR. The lowering pass marks the message immediately preceding currentTurn with cache_control; subsequent turns whose history prefix matches byte-for-byte hit the cache.",
1450
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1451
+ }
1452
+ ];
1453
+ }
1454
+ function detectSingleModelArray(ir, policy) {
1455
+ if (ir.models.length !== 1) return [];
1456
+ if (policy?.posture === "locked") return [];
1457
+ const only = ir.models[0];
1458
+ return [
1459
+ {
1460
+ level: "warn",
1461
+ code: "single-model-array",
1462
+ message: `\`ir.models\` has length 1 (only "${only}") and posture is not 'locked'. A single-model chain has no safety net \u2014 the first 429 / 5xx / cliff hits the user as a failure. Master plan \xA71.2 closes the reliability gap with a 2-step minimum.`,
1463
+ suggestion: "Use `getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' })` for a user-anchored chain, or `getDefaultFallbackChain({ archetype, posture: 'open' })` for library-picked. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.",
1464
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
1465
+ }
1466
+ ];
1467
+ }
1468
+ function detectCostMismatchedArchetype(ir, profile, phase2) {
1469
+ if (!phase2 || phase2.fallbackChain.length === 0) return [];
1470
+ if (!phase2.profileResolver) return [];
1471
+ const archetype = ir.intent.archetype;
1472
+ const chosenScore = getArchetypePerfScore(profile.id, archetype);
1473
+ const chosenHasRoomToGrow = chosenScore.grounding === "judgment" || chosenScore.score < COST_MISMATCHED_CHOSEN_SCORE_CEILING;
1474
+ if (!chosenHasRoomToGrow) return [];
1475
+ let bestAlt = null;
1476
+ for (const altId of phase2.fallbackChain) {
1477
+ const altProfile = phase2.profileResolver(altId);
1478
+ if (!altProfile) continue;
1479
+ if (altProfile.id === profile.id) continue;
1480
+ const altScore = getArchetypePerfScore(altProfile.id, archetype);
1481
+ if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
1482
+ if (altScore.score < chosenScore.score) continue;
1483
+ if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
1484
+ if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
1485
+ bestAlt = { id: altId, profile: altProfile, score: altScore };
1486
+ }
1487
+ }
1488
+ if (!bestAlt) return [];
1489
+ const tierDownWouldFire = bestAlt.score.grounding === "measured" && bestAlt.profile.costInputPer1m <= profile.costInputPer1m * TIER_DOWN_COST_RATIO;
1490
+ if (tierDownWouldFire) return [];
1491
+ const chosenGrounding = chosenScore.grounding === "judgment" ? `archetypePerf.${archetype}=judgment` : `archetypePerf.${archetype}=${chosenScore.score}`;
1492
+ const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
1493
+ return [
1494
+ {
1495
+ level: "warn",
1496
+ code: "cost-mismatched-archetype",
1497
+ message: `Cost-mismatched-archetype: target=${profile.id} (${chosenGrounding}) selected for ${archetype}. Alternative ${bestAlt.id} (${altGrounding}) is cheaper ($${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} per 1M) at equal-or-better quality.`,
1498
+ suggestion: `Consider declaring \`${bestAlt.id}\` as the primary model for this archetype, or relax to posture='open' to let kgauto select among the chain. If the chosen model is required for compliance/brand reasons, set \`policy.posture = 'locked'\` to silence this rule.`,
1499
+ recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
1500
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1501
+ }
1502
+ ];
1503
+ }
1504
+ function detectModelStaleEvidence(ir, profile) {
1505
+ if (!isBrainQueryActiveFor("kgauto_archetype_perf")) return [];
1506
+ const archetype = ir.intent.archetype;
1507
+ const chosen = getArchetypePerfScore(profile.id, archetype);
1508
+ if (chosen.grounding !== "judgment") return [];
1509
+ return [
1510
+ {
1511
+ level: "info",
1512
+ code: "model-stale-evidence",
1513
+ message: `Model-stale-evidence: target=${profile.id} archetype=${archetype} is judgment-grounded (n=${chosen.n}) despite brain-query mode being active. Measurement substrate is wired but the brain hasn't accumulated >=10 outcomes for this (model, archetype) tuple yet \u2014 routing decisions remain pre-measured for this slot.`,
1514
+ suggestion: "Verify that `record()` is being called on every call() outcome with the appropriate `actualModel` and `mutationsApplied` fields. Once the brain accumulates n>=10 rows on this tuple, the score promotes from judgment to measured automatically (5-min SWR cache). No code change required from your side \u2014 this is the substrate signaling the gap.",
1515
+ recommendationType: "prompt-fix",
1516
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1517
+ }
1518
+ ];
1519
+ }
1520
+ function detectTierDown(ir, profile, phase2) {
1521
+ if (!phase2 || phase2.fallbackChain.length === 0) return [];
1522
+ if (!phase2.profileResolver) return [];
1523
+ const archetype = ir.intent.archetype;
1524
+ const chosenScore = getArchetypePerfScore(profile.id, archetype);
1525
+ const chosenCost = profile.costInputPer1m;
1526
+ let bestAlt = null;
1527
+ for (const altId of phase2.fallbackChain) {
1528
+ const altProfile = phase2.profileResolver(altId);
1529
+ if (!altProfile) continue;
1530
+ if (altProfile.id === profile.id) continue;
1531
+ const altScore = getArchetypePerfScore(altProfile.id, archetype);
1532
+ if (altScore.grounding !== "measured") continue;
1533
+ if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
1534
+ if (altScore.score < chosenScore.score) continue;
1535
+ if (altProfile.costInputPer1m > chosenCost * TIER_DOWN_COST_RATIO) continue;
1536
+ if (!bestAlt || altProfile.costInputPer1m < bestAlt.profile.costInputPer1m || altProfile.costInputPer1m === bestAlt.profile.costInputPer1m && altScore.score > bestAlt.score.score) {
1537
+ bestAlt = { id: altId, profile: altProfile, score: altScore };
1538
+ }
1539
+ }
1540
+ if (!bestAlt) return [];
1541
+ const chosenDesc = chosenScore.grounding === "measured" ? `archetypePerf.${archetype}=${chosenScore.score} (measured, n=${chosenScore.n})` : `archetypePerf.${archetype}=${chosenScore.score} (${chosenScore.grounding})`;
1542
+ return [
1543
+ {
1544
+ level: "warn",
1545
+ code: "tier-down",
1546
+ message: `Tier-down: target=${profile.id} (${chosenDesc}) selected for ${archetype}. Brain shows ${bestAlt.id} delivers equal-or-better quality (archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}) at $${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} per 1M vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} \u2014 a measured tier-down opportunity.`,
1547
+ suggestion: `Move \`${bestAlt.id}\` to primary for this archetype. The brain has n=${bestAlt.score.n} measured outcomes backing the recommendation; this is data, not opinion. If posture='locked' is required (compliance/brand promise), set it explicitly to silence this rule.`,
1548
+ recommendationType: "tier-down",
1549
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1550
+ }
1551
+ ];
1552
+ }
1553
+ function detectArchetypePerfFloorBreach(ir, profile) {
1554
+ const compat = getModelCompatibility(profile.id, {
1555
+ archetype: ir.intent.archetype,
1556
+ toolOrchestration: ir.constraints?.toolOrchestration
1557
+ });
1558
+ if (compat.status === "compatible") return [];
1559
+ if (compat.status === "requires-adapter") {
1560
+ return [
1561
+ {
1562
+ level: "warn",
1563
+ code: "archetype-perf-floor-breach",
1564
+ message: `${profile.id} sits below the archetype floor for ${ir.intent.archetype} (score ${compat.archetypePerf}/10, floor ${6}). A known adapter would lift it: ${compat.adapter.parameter}=${compat.adapter.value}. ${compat.adapter.consequence}`,
1565
+ suggestion: `Pass \`ir.constraints.${compat.adapter.parameter} = '${compat.adapter.value}'\` for this call, OR pick a model whose archetypePerf for ${ir.intent.archetype} already clears the floor (call \`getModelCompatibility(modelId, { archetype: '${ir.intent.archetype}' })\` to check). Estimated post-adapter score: ${compat.archetypePerfWithAdapter}/10.`,
1566
+ recommendationType: "prompt-fix",
1567
+ suggestedAdaptation: compat.adapter,
1568
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1569
+ }
1570
+ ];
1571
+ }
1572
+ return [
1573
+ {
1574
+ level: "critical",
1575
+ code: "archetype-perf-floor-breach",
1576
+ message: `${profile.id} sits below the archetype floor for ${ir.intent.archetype} (score ${compat.archetypePerf}/10, floor ${6}) and no known adapter would lift it. ${compat.reason}`,
1577
+ suggestion: `Swap to a model whose archetypePerf for ${ir.intent.archetype} clears the floor. Use \`getModelCompatibility(candidateId, { archetype: '${ir.intent.archetype}' })\` to vet candidates, or \`getDefaultFallbackChain({ archetype: '${ir.intent.archetype}', posture: 'open' })\` for a library-picked chain that respects the floor by construction.`,
1578
+ recommendationType: "model-swap",
1579
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1580
+ }
1581
+ ];
1582
+ }
1583
+ function detectStaleExclusionCandidate(ir) {
1584
+ if (!isExclusionFindingsBrainActive()) return [];
1585
+ if (!ir.appId) return [];
1586
+ const findings = getStaleExclusionFindings({
1587
+ appId: ir.appId,
1588
+ archetype: ir.intent.archetype
1589
+ });
1590
+ if (findings.length === 0) return [];
1591
+ const ranked = [...findings].sort((a, b) => {
1592
+ const sa = a.estimatedSavingsUsd30d ?? -Infinity;
1593
+ const sb = b.estimatedSavingsUsd30d ?? -Infinity;
1594
+ if (sa !== sb) return sb - sa;
1595
+ return confidenceRank(b.confidence) - confidenceRank(a.confidence);
1596
+ });
1597
+ const top = ranked[0];
1598
+ const extraCount = findings.length - 1;
1599
+ const extraNote = extraCount > 0 ? ` (+ ${extraCount} more excluded model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
1600
+ return [
1601
+ {
1602
+ level: "info",
1603
+ code: "stale-exclusion-candidate",
1604
+ message: `${top.message}${extraNote}`,
1605
+ suggestion: top.suggestion,
1606
+ recommendationType: "tier-down",
1607
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1608
+ }
1609
+ ];
1610
+ }
1611
+ function confidenceRank(c) {
1612
+ if (c === "high") return 3;
1613
+ if (c === "medium") return 2;
1614
+ return 1;
1615
+ }
1616
+
1617
+ // src/translator.ts
1618
+ var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
1619
+ var RULE_SEQUENTIAL_TOOL_CLIFF = "sequential-tool-cliff-below-floor";
1620
+ var RULE_NARRATION_DRIFT_ANTHROPIC = "narration-drift-anthropic";
1621
+ var RULE_NARRATION_THINKING_LEAK_DEEPSEEK = "narration-thinking-leak-deepseek";
1622
+ var SEQUENTIAL_TOOL_PREAMBLE = "IMPORTANT: Use one tool call per response. Wait for the tool result before deciding the next tool. Do NOT batch tool calls in parallel.";
1623
+ var NARRATION_DRIFT_ANTHROPIC_PREAMBLE = "Output ONLY the requested content. Do not narrate your thought process. Each line \u2264 12 words.";
1624
+ var NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE = "Reasoning is internal. Output ONLY the requested content; do not emit <thinking> blocks or internal monologue as user-facing text.";
1625
+ function matchRule(kind, profile, archetype) {
1626
+ if (kind === "tool_call_contract") {
1627
+ if (!profile.archetypePerf) return null;
1628
+ const archetypeScore = profile.archetypePerf[archetype];
1629
+ if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
1630
+ return null;
1631
+ }
1632
+ return {
1633
+ id: RULE_SEQUENTIAL_TOOL_CLIFF,
1634
+ preamble: SEQUENTIAL_TOOL_PREAMBLE,
1635
+ wireOverrides: { parallelToolCalls: false }
1636
+ };
1637
+ }
1638
+ if (kind === "narration_contract") {
1639
+ if (profile.provider === "anthropic") {
1640
+ return {
1641
+ id: RULE_NARRATION_DRIFT_ANTHROPIC,
1642
+ preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
1643
+ };
1644
+ }
1645
+ if (profile.provider === "deepseek") {
1646
+ return {
1647
+ id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
1648
+ preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
1649
+ };
1650
+ }
1651
+ return null;
1652
+ }
1653
+ return null;
1654
+ }
1655
+ function applySectionRewrites(args) {
1656
+ const { ir, profile, archetype } = args;
1657
+ if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
1658
+ return { rewrittenIR: ir, rewrites: [] };
1659
+ }
1660
+ const rewrites = [];
1661
+ const newSections = ir.sections.map((section) => {
1662
+ if (!section.kind || section.kind === "arbitrary") return section;
1663
+ const rule = matchRule(section.kind, profile, archetype);
1664
+ if (!rule) return section;
1665
+ const originalText = section.text;
1666
+ const transformedText = `${rule.preamble}
1667
+
1668
+ ${originalText}`;
1669
+ rewrites.push({
1670
+ sectionId: section.id,
1671
+ kind: section.kind,
1672
+ rule: rule.id,
1673
+ originalText,
1674
+ transformedText,
1675
+ ...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
1676
+ });
1677
+ return { ...section, text: transformedText };
1678
+ });
1679
+ if (rewrites.length === 0) {
1680
+ return { rewrittenIR: ir, rewrites: [] };
1681
+ }
1682
+ const rewrittenIR = { ...ir, sections: newSections };
1683
+ return { rewrittenIR, rewrites };
1684
+ }
1685
+
1686
+ // src/models-brain.ts
1687
+ function isModelRow(x) {
1688
+ if (!x || typeof x !== "object") return false;
1689
+ const r = x;
1690
+ return typeof r.model_id === "string" && typeof r.provider === "string";
1691
+ }
1692
+ function isAliasRow(x) {
1693
+ if (!x || typeof x !== "object") return false;
1694
+ const r = x;
1695
+ return typeof r.alias_id === "string" && typeof r.canonical_id === "string";
1696
+ }
1697
+ function rowToProfile(row) {
1698
+ try {
1699
+ if (row.cliffs !== void 0 && row.cliffs !== null && !Array.isArray(row.cliffs)) {
1700
+ return null;
1701
+ }
1702
+ if (row.recovery !== void 0 && row.recovery !== null && !Array.isArray(row.recovery)) {
1703
+ return null;
1704
+ }
1705
+ if (row.lowering !== void 0 && row.lowering !== null && (typeof row.lowering !== "object" || Array.isArray(row.lowering))) {
1706
+ return null;
1707
+ }
1708
+ return {
1709
+ id: row.model_id,
1710
+ provider: row.provider,
1711
+ status: row.status ?? "current",
1712
+ maxContextTokens: row.max_context_tokens ?? 0,
1713
+ maxOutputTokens: row.max_output_tokens ?? 0,
1714
+ maxTools: row.max_tools ?? 0,
1715
+ parallelToolCalls: row.parallel_tool_calls ?? false,
1716
+ structuredOutput: row.structured_output ?? "none",
1717
+ systemPromptMode: row.system_prompt_mode ?? "inline",
1718
+ streaming: row.streaming ?? true,
1719
+ cliffs: row.cliffs ?? [],
1720
+ costInputPer1m: row.cost_input_per_1m ?? 0,
1721
+ costOutputPer1m: row.cost_output_per_1m ?? 0,
1722
+ lowering: row.lowering ?? { system: { mode: "inline" }, cache: { strategy: "unsupported" } },
1723
+ recovery: row.recovery ?? [],
1724
+ strengths: row.strengths ?? [],
1725
+ weaknesses: row.weaknesses ?? [],
1726
+ notes: row.notes ?? void 0,
1727
+ verifiedAgainstDocs: row.verified_against_docs ?? void 0,
1728
+ archetypePerf: row.archetype_perf ?? void 0,
1729
+ // alpha.41 — family-resolution fields. `family` may be null pre-
1730
+ // migration 024; runtime falls back to deriveFamilyFromModelId at
1731
+ // resolution time (see family-resolution.ts, G1).
1732
+ family: row.family ?? void 0,
1733
+ versionAdded: row.version_added ?? void 0,
1734
+ active: row.active ?? void 0
1735
+ };
1736
+ } catch {
1737
+ return null;
1738
+ }
1739
+ }
1740
+ function profileToRow(profile, opts = {}) {
1741
+ const row = {
1742
+ model_id: profile.id,
1743
+ provider: profile.provider,
1744
+ status: profile.status,
1745
+ max_context_tokens: profile.maxContextTokens,
1746
+ max_output_tokens: profile.maxOutputTokens,
1747
+ max_tools: profile.maxTools,
1748
+ parallel_tool_calls: profile.parallelToolCalls,
1749
+ structured_output: profile.structuredOutput,
1750
+ system_prompt_mode: profile.systemPromptMode,
1751
+ streaming: profile.streaming,
1752
+ cliffs: profile.cliffs,
1753
+ cost_input_per_1m: profile.costInputPer1m,
1754
+ cost_output_per_1m: profile.costOutputPer1m,
1755
+ lowering: profile.lowering,
1756
+ recovery: profile.recovery,
1757
+ strengths: profile.strengths,
1758
+ weaknesses: profile.weaknesses,
1759
+ notes: profile.notes ?? null,
1760
+ archetype_perf: profile.archetypePerf ?? null,
1761
+ active: opts.active ?? profile.active ?? true,
1762
+ // alpha.41 — round-trip family + version_added when present on profile.
1763
+ // version_added is operator-controlled via opts; profile-side value is
1764
+ // used only when opts didn't override.
1765
+ family: profile.family ?? null
1766
+ };
1767
+ if (opts.verifiedAgainstDocs !== void 0) {
1768
+ row.verified_against_docs = opts.verifiedAgainstDocs;
1769
+ } else if (profile.verifiedAgainstDocs !== void 0) {
1770
+ const v = profile.verifiedAgainstDocs;
1771
+ row.verified_against_docs = /^\d{4}-\d{2}-\d{2}/.test(v) ? v : null;
1772
+ }
1773
+ if (opts.versionAdded !== void 0) row.version_added = opts.versionAdded;
1774
+ else if (profile.versionAdded !== void 0) row.version_added = profile.versionAdded;
1775
+ if (opts.versionRemoved !== void 0) row.version_removed = opts.versionRemoved;
1776
+ return row;
1777
+ }
1778
+ function mapRowsToModels(rows) {
1779
+ const out = /* @__PURE__ */ new Map();
1780
+ for (const row of rows) {
1781
+ if (!isModelRow(row)) continue;
1782
+ const profile = rowToProfile(row);
1783
+ if (profile) out.set(profile.id, profile);
1784
+ }
1785
+ return out;
1786
+ }
1787
+ function mapRowsToAliases(rows) {
1788
+ const out = {};
1789
+ for (const row of rows) {
1790
+ if (!isAliasRow(row)) continue;
1791
+ out[row.alias_id] = row.canonical_id;
1792
+ }
1793
+ return out;
1794
+ }
1795
+ function bundledModels() {
1796
+ return new Map(allProfilesRaw().map((p) => [p.id, p]));
1797
+ }
1798
+ function bundledAliases() {
1799
+ return { ...ALIASES };
1800
+ }
1801
+ var loadModelsFromBrain = createBrainQueryCache({
1802
+ table: "kgauto_models",
1803
+ mapRows: mapRowsToModels,
1804
+ bundledFallback: bundledModels
1805
+ });
1806
+ var loadAliasesFromBrain = createBrainQueryCache({
1807
+ table: "kgauto_aliases",
1808
+ mapRows: mapRowsToAliases,
1809
+ bundledFallback: bundledAliases
1810
+ });
1811
+ _setProfileBrainHook({
1812
+ getProfile: (canonical) => loadModelsFromBrain().get(canonical),
1813
+ resolveAlias: (id) => loadAliasesFromBrain()[id]
1814
+ });
1815
+
1816
+ // src/family-resolution.ts
1817
+ var FamilyResolutionError = class extends Error {
1818
+ family;
1819
+ cause;
1820
+ constructor(family, cause) {
1821
+ super(
1822
+ `Family "${family}" did not resolve to any current+active model. ${cause}. Pass a literal model id in ir.models, or call getRecommendedPrimary({ family, fallback }) at IR-construction time.`
1823
+ );
1824
+ this.name = "FamilyResolutionError";
1825
+ this.family = family;
1826
+ this.cause = cause;
1827
+ }
1828
+ };
1829
+ function deriveFamilyFromModelId(modelId) {
1830
+ if (typeof modelId !== "string" || modelId.length === 0) return null;
1831
+ if (modelId.startsWith("claude-opus-")) return "claude-opus";
1832
+ if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
1833
+ if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
1834
+ if (modelId.startsWith("gemini-") && modelId.includes("flash-lite")) {
1835
+ return "gemini-flash-lite";
1836
+ }
1837
+ if (modelId.startsWith("gemini-") && modelId.includes("flash")) {
1838
+ return "gemini-flash";
1839
+ }
1840
+ if (modelId.startsWith("gemini-") && modelId.includes("pro")) {
1841
+ return "gemini-pro";
1842
+ }
1843
+ if (modelId.startsWith("deepseek-") && modelId.includes("pro")) {
1844
+ return "deepseek-reasoner";
1845
+ }
1846
+ if (modelId.startsWith("deepseek-")) return "deepseek-chat";
1847
+ if (modelId.startsWith("gpt-")) return "openai-gpt";
1848
+ return null;
1849
+ }
1850
+ function familyOf(profile) {
1851
+ return profile.family ?? deriveFamilyFromModelId(profile.id);
1852
+ }
1853
+ function archetypePerfFor(profile, archetype) {
1854
+ if (!archetype) return 0;
1855
+ const score = profile.archetypePerf?.[archetype];
1856
+ return typeof score === "number" ? score : 5;
1857
+ }
1858
+ function selectCandidates(registry, family, archetype, appId) {
1859
+ const out = [];
1860
+ const excluded = /* @__PURE__ */ new Set();
1861
+ if (appId && archetype) {
1862
+ const findings = getStaleExclusionFindings({ appId, archetype });
1863
+ for (const f of findings) {
1864
+ if (f.verdict === "stay-excluded") excluded.add(f.excludedModel);
1865
+ }
1866
+ }
1867
+ for (const profile of registry.values()) {
1868
+ if (familyOf(profile) !== family) continue;
1869
+ if (profile.status !== "current") continue;
1870
+ if (profile.active === false) continue;
1871
+ if (archetype) {
1872
+ if (archetypePerfFor(profile, archetype) < ARCHETYPE_FLOOR_DEFAULT) continue;
1873
+ }
1874
+ if (excluded.has(profile.id)) continue;
1875
+ out.push(profile);
1876
+ }
1877
+ return out;
1878
+ }
1879
+ function sortCandidates(candidates, archetype) {
1880
+ const sorted = [...candidates];
1881
+ sorted.sort((a, b) => {
1882
+ const perfA = archetypePerfFor(a, archetype);
1883
+ const perfB = archetypePerfFor(b, archetype);
1884
+ if (perfA !== perfB) return perfB - perfA;
1885
+ const vA = a.versionAdded ?? "";
1886
+ const vB = b.versionAdded ?? "";
1887
+ if (vA !== vB) return vA < vB ? 1 : -1;
1888
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
1889
+ });
1890
+ return sorted;
1891
+ }
1892
+ function getRecommendedPrimary(opts) {
1893
+ if (opts.posture === "locked") return opts.fallback;
1894
+ const registry = loadModelsFromBrain();
1895
+ const candidates = selectCandidates(
1896
+ registry,
1897
+ opts.family,
1898
+ opts.archetype,
1899
+ opts.appId
1900
+ );
1901
+ if (candidates.length === 0) return opts.fallback;
1902
+ const sorted = sortCandidates(candidates, opts.archetype);
1903
+ return sorted[0]?.id ?? opts.fallback;
1904
+ }
1905
+ function resolveFamilyEntry(family, ctx) {
1906
+ if (typeof family !== "string" || family.length === 0) {
1907
+ throw new FamilyResolutionError(
1908
+ String(family),
1909
+ "Family tag must be a non-empty string"
1910
+ );
1911
+ }
1912
+ const registry = loadModelsFromBrain();
1913
+ const candidates = selectCandidates(
1914
+ registry,
1915
+ family,
1916
+ ctx.archetype,
1917
+ ctx.appId
1918
+ );
1919
+ if (candidates.length === 0) {
1920
+ let anyInFamily = false;
1921
+ for (const profile of registry.values()) {
1922
+ if (familyOf(profile) === family) {
1923
+ anyInFamily = true;
1924
+ break;
1925
+ }
1926
+ }
1927
+ const cause = anyInFamily ? `Family has registered models but none are current+active${ctx.archetype ? ` at archetype-perf >= ${ARCHETYPE_FLOOR_DEFAULT} for "${ctx.archetype}"` : ""}${ctx.appId ? ` and not excluded for app "${ctx.appId}"` : ""}` : `No models in brain registry carry family="${family}". Check the taxonomy table in family-resolution.ts or migration 024`;
1928
+ throw new FamilyResolutionError(family, cause);
1929
+ }
1930
+ const sorted = sortCandidates(candidates, ctx.archetype);
1931
+ const winner = sorted[0];
1932
+ if (!winner) {
1933
+ throw new FamilyResolutionError(
1934
+ family,
1935
+ "Candidates non-empty but sort returned undefined \u2014 internal invariant violation"
1936
+ );
1937
+ }
1938
+ return winner.id;
1939
+ }
1940
+
1941
+ // src/compile.ts
1942
+ var counter = 0;
1943
+ function makeHandle() {
1944
+ counter = (counter + 1) % 1e6;
1945
+ return `c${Date.now().toString(36)}-${counter.toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
1946
+ }
1947
+ function compile(ir, opts = {}) {
1948
+ const resolver = opts.profileResolver ?? getProfile;
1949
+ validateIR(ir);
1950
+ ir = resolveModelEntries(ir);
1951
+ const sliced = passSlice(ir);
1952
+ const deduped = passDedupe(sliced.value);
1953
+ const toolFiltered = passToolRelevance(deduped.value, {
1954
+ threshold: opts.toolRelevanceThreshold
1955
+ });
1956
+ const compressed = passCompressHistory(toolFiltered.value, {
1957
+ summarizeOlderThan: opts.compressHistoryAfter,
1958
+ summarizeAboveTokens: opts.compressHistoryAboveTokens
1959
+ });
1960
+ let workingIR = compressed.value;
1961
+ const accumulatedMutations = [
1962
+ ...sliced.mutations,
1963
+ ...deduped.mutations,
1964
+ ...toolFiltered.mutations,
1965
+ ...compressed.mutations
1966
+ ];
1967
+ const inputTokens = estimateInputTokens(workingIR);
1968
+ const scores = passScoreTargets(workingIR, {
1969
+ estimatedInputTokens: inputTokens,
1970
+ profilesById: resolver,
1971
+ policy: opts.policy
1972
+ });
1973
+ accumulatedMutations.push(...scores.mutations);
1974
+ const target = pickTarget(workingIR, scores.value);
1975
+ if (!target) {
1976
+ throw new Error(
1977
+ `compile(): no allowed model fits the request. Scores: ${JSON.stringify(scores.value, null, 2)}`
1978
+ );
1979
+ }
1980
+ const profile = resolver(target.modelId);
1981
+ const fallbackChain = scores.value.filter((s) => s.modelId !== target.modelId && s.fits).sort((a, b) => b.rank - a.rank).map((s) => s.modelId);
1982
+ const cliffs = passApplyCliffs(workingIR, profile, inputTokens);
1983
+ workingIR = cliffs.value.ir;
1984
+ accumulatedMutations.push(...cliffs.mutations);
1985
+ const translated = applySectionRewrites({
1986
+ ir: workingIR,
1987
+ profile,
1988
+ archetype: ir.intent.archetype
1989
+ });
1990
+ workingIR = translated.rewrittenIR;
1991
+ const sectionRewritesApplied = translated.rewrites;
1992
+ let wireOverrides;
1993
+ for (const rw of sectionRewritesApplied) {
1994
+ if (!rw.wireOverrides) continue;
1995
+ if (!wireOverrides) wireOverrides = {};
1996
+ if (rw.wireOverrides.parallelToolCalls !== void 0) {
1997
+ wireOverrides.parallelToolCalls = rw.wireOverrides.parallelToolCalls;
1998
+ }
1999
+ }
2000
+ for (const rw of sectionRewritesApplied) {
2001
+ accumulatedMutations.push({
2002
+ id: `translator:${rw.rule}:${rw.sectionId}`,
2003
+ source: "translator",
2004
+ passName: "translator",
2005
+ description: `Rewrote section "${rw.sectionId}" (kind=${rw.kind}) via rule "${rw.rule}".`
2006
+ });
2007
+ }
2008
+ const lowered = lower(workingIR, profile, {
2009
+ forceThinkingZero: cliffs.value.loweringHints.forceThinkingZero,
2010
+ forceTerseOutput: cliffs.value.loweringHints.forceTerseOutput,
2011
+ wireOverrides
2012
+ });
2013
+ validateFinalFit(workingIR, profile, inputTokens);
2014
+ const handle = makeHandle();
2015
+ const finalShape = computeShape(workingIR, inputTokens);
2016
+ const _learningKey = learningKey(ir.intent.archetype, profile.id, finalShape);
2017
+ const historyCacheMarkIndex = computeHistoryCacheMarkIndex(workingIR);
2018
+ const systemMessages = buildSystemMessages(workingIR, profile.provider);
2019
+ const systemCacheMarkIndex = lastCacheableSystemIndex(systemMessages);
2020
+ const diagnostics = {
2021
+ sectionsKept: workingIR.sections.length,
2022
+ sectionsDropped: ir.sections.length - workingIR.sections.length,
2023
+ toolsKept: workingIR.tools?.length ?? 0,
2024
+ toolsDropped: (ir.tools?.length ?? 0) - (workingIR.tools?.length ?? 0),
2025
+ historyKept: workingIR.history?.length ?? 0,
2026
+ historyDropped: (ir.history?.length ?? 0) - (workingIR.history?.length ?? 0),
2027
+ cacheableTokens: lowered.diagnostics.cacheableTokens,
2028
+ estimatedCacheSavingsUsd: lowered.diagnostics.estimatedCacheSavingsUsd,
2029
+ historyCacheableTokens: lowered.diagnostics.historyCacheableTokens,
2030
+ historyTokensTotal: compressed.historyTokensTotal,
2031
+ // alpha.20 E3: mirror the consumer's declared mode for Glass-Box +
2032
+ // brain observability. Undefined when not declared (pre-alpha.20).
2033
+ toolOrchestration: ir.constraints?.toolOrchestration,
2034
+ // alpha.33 — see top-of-block comment.
2035
+ historyCacheMarkIndex,
2036
+ systemCacheMarkIndex
2037
+ };
2038
+ if (ir.intent.archetype === "hunt" && ir.constraints?.toolOrchestration === "sequential") {
2039
+ accumulatedMutations.push({
2040
+ id: "sequential-mode-chain-selected",
2041
+ source: "tool_orchestration",
2042
+ passName: "compile",
2043
+ description: "ir.constraints.toolOrchestration='sequential' selected the DeepSeek-tier-0 hunt chain overlay (L-040 parallel-tool cliff doesn't apply at single-step granularity)."
2044
+ });
2045
+ }
2046
+ const phase2ProfileResolver = opts.profileResolver ? (id) => {
2047
+ try {
2048
+ return opts.profileResolver(id);
2049
+ } catch {
2050
+ return void 0;
2051
+ }
2052
+ } : tryGetProfile;
2053
+ const rawAdvisories = runAdvisor(
2054
+ ir,
2055
+ {
2056
+ target: profile.id,
2057
+ provider: profile.provider,
2058
+ tokensIn: inputTokens,
2059
+ diagnostics
2060
+ },
2061
+ profile,
2062
+ opts.policy,
2063
+ {
2064
+ fallbackChain,
2065
+ profileResolver: phase2ProfileResolver,
2066
+ // alpha.29 — feed translator rewrites to the advisor so the
2067
+ // `archetype-perf-floor-breach` rule can suppress when the translator
2068
+ // already cleared the cliff for the same archetype. Without this,
2069
+ // both the rewrite AND the advisory fire — noisy, and the advisory
2070
+ // would mislead consumers into thinking the cliff is unaddressed.
2071
+ sectionRewritesApplied
2072
+ }
2073
+ );
2074
+ const advisories = rawAdvisories.map((a) => ({
2075
+ ...a,
2076
+ kgautoRequestId: handle,
2077
+ ownership: deriveOwnership(a.code, a.ownership)
2078
+ }));
2079
+ return {
2080
+ handle,
2081
+ target: profile.id,
2082
+ provider: profile.provider,
2083
+ request: lowered.request,
2084
+ tokensIn: inputTokens,
2085
+ estimatedCostUsd: target.estimatedCostUsd,
2086
+ mutationsApplied: accumulatedMutations,
2087
+ fallbackChain,
2088
+ advisories,
2089
+ diagnostics,
2090
+ sectionRewritesApplied,
2091
+ wireOverrides,
2092
+ systemMessages
2093
+ };
2094
+ }
2095
+ function computeHistoryCacheMarkIndex(ir) {
2096
+ const policy = ir.historyCachePolicy;
2097
+ if (!policy || policy.strategy === "none") return void 0;
2098
+ const historyLen = ir.history?.length ?? 0;
2099
+ if (historyLen === 0) return void 0;
2100
+ if (policy.strategy === "all-but-latest") {
2101
+ return historyLen - 1;
2102
+ }
2103
+ if (policy.strategy === "fixed-suffix") {
2104
+ const idx = historyLen - 1 - policy.suffix;
2105
+ if (idx < 0) return void 0;
2106
+ return idx;
2107
+ }
2108
+ return void 0;
2109
+ }
2110
+ function buildSystemMessages(ir, provider) {
2111
+ const sections = ir.sections;
2112
+ if (!sections || sections.length === 0) return [];
2113
+ return sections.map((s) => {
2114
+ const base = { role: "system", content: s.text };
2115
+ if (provider === "anthropic" && s.cacheable) {
2116
+ base.providerOptions = { anthropic: { cacheControl: { type: "ephemeral" } } };
2117
+ }
2118
+ return base;
2119
+ });
2120
+ }
2121
+ function lastCacheableSystemIndex(systemMessages) {
2122
+ for (let i = systemMessages.length - 1; i >= 0; i--) {
2123
+ const entry = systemMessages[i];
2124
+ if (entry && entry.providerOptions?.anthropic?.cacheControl) {
2125
+ return i;
2126
+ }
2127
+ }
2128
+ return void 0;
2129
+ }
2130
+ function validateIR(ir) {
2131
+ if (!ir.appId) throw new Error("compile(): ir.appId is required");
2132
+ if (!ir.intent || !ir.intent.archetype) {
2133
+ throw new Error("compile(): ir.intent.archetype is required (use a dialect-v1 archetype)");
2134
+ }
2135
+ if (!Array.isArray(ir.models) || ir.models.length === 0) {
2136
+ throw new Error("compile(): ir.models must be a non-empty array");
2137
+ }
2138
+ if (!Array.isArray(ir.sections)) {
2139
+ throw new Error("compile(): ir.sections must be an array");
2140
+ }
2141
+ }
2142
+ function resolveModelEntries(ir) {
2143
+ const out = [];
2144
+ const seen = /* @__PURE__ */ new Set();
2145
+ for (const entry of ir.models) {
2146
+ let id;
2147
+ if (typeof entry === "string") {
2148
+ id = entry;
2149
+ } else if (entry && typeof entry === "object" && "family" in entry) {
2150
+ id = resolveFamilyEntry(entry.family, {
2151
+ archetype: ir.intent.archetype,
2152
+ appId: ir.appId
2153
+ });
2154
+ } else {
2155
+ throw new Error(
2156
+ `compile(): ir.models entry must be a string or { family: string }; got ${JSON.stringify(entry)}`
2157
+ );
2158
+ }
2159
+ if (seen.has(id)) continue;
2160
+ seen.add(id);
2161
+ out.push(id);
2162
+ }
2163
+ return { ...ir, models: out };
2164
+ }
2165
+ function pickTarget(ir, scores) {
2166
+ if (ir.constraints?.forceModel) {
2167
+ const forced = scores.find((s) => s.modelId === ir.constraints.forceModel);
2168
+ if (forced && forced.fits) return forced;
2169
+ if (forced) {
2170
+ throw new Error(
2171
+ `compile(): forceModel="${ir.constraints.forceModel}" does not fit: ${forced.rejectReasons.join("; ")}`
2172
+ );
2173
+ }
2174
+ }
2175
+ const fitting = scores.filter((s) => s.fits).sort((a, b) => b.rank - a.rank);
2176
+ return fitting[0];
2177
+ }
2178
+ function validateFinalFit(ir, profile, tokens) {
2179
+ if (tokens > profile.maxContextTokens) {
2180
+ throw new Error(
2181
+ `compile(): final IR is ${tokens} tokens, exceeds ${profile.id} context (${profile.maxContextTokens})`
2182
+ );
2183
+ }
2184
+ if ((ir.tools?.length ?? 0) > profile.maxTools) {
2185
+ throw new Error(
2186
+ `compile(): final IR has ${ir.tools?.length} tools, exceeds ${profile.id} maxTools (${profile.maxTools})`
2187
+ );
2188
+ }
2189
+ }
2190
+
2191
+ // src/pricing-brain.ts
2192
+ function isPricingRow(x) {
2193
+ if (!x || typeof x !== "object") return false;
2194
+ const r = x;
2195
+ return typeof r.model_id === "string" && typeof r.cost_input_per_1m === "number" && typeof r.cost_output_per_1m === "number" && typeof r.valid_from === "string";
2196
+ }
2197
+ function mapRowsToPricing(rows) {
2198
+ const out = [];
2199
+ for (const row of rows) {
2200
+ if (!isPricingRow(row)) continue;
2201
+ out.push({
2202
+ modelId: row.model_id,
2203
+ costInputPer1m: row.cost_input_per_1m,
2204
+ costOutputPer1m: row.cost_output_per_1m,
2205
+ cacheInputPer1m: row.cache_input_per_1m ?? void 0,
2206
+ cacheCreationPer1m: row.cache_creation_per_1m ?? void 0,
2207
+ validFrom: Date.parse(row.valid_from),
2208
+ validUntil: row.valid_until == null ? void 0 : Date.parse(row.valid_until),
2209
+ source: row.source ?? void 0
2210
+ });
2211
+ }
2212
+ return out;
2213
+ }
2214
+ function bundledPricing() {
2215
+ const out = [];
2216
+ for (const profile of allProfiles()) {
2217
+ out.push({
2218
+ modelId: profile.id,
2219
+ costInputPer1m: profile.costInputPer1m,
2220
+ costOutputPer1m: profile.costOutputPer1m,
2221
+ cacheInputPer1m: profile.lowering.cache.discount !== void 0 && profile.lowering.cache.discount > 0 ? profile.costInputPer1m * profile.lowering.cache.discount : void 0,
2222
+ validFrom: 0,
2223
+ validUntil: void 0,
2224
+ source: "profile_seed"
2225
+ });
2226
+ }
2227
+ return out;
2228
+ }
2229
+ var loadPricingFromBrain = createBrainQueryCache({
2230
+ table: "kgauto_pricing",
2231
+ mapRows: mapRowsToPricing,
2232
+ bundledFallback: bundledPricing
2233
+ });
2234
+ function resolvePricingAt(modelId, at = /* @__PURE__ */ new Date()) {
2235
+ const ts = at.getTime();
2236
+ const all = loadPricingFromBrain();
2237
+ let best;
2238
+ for (const row of all) {
2239
+ if (row.modelId !== modelId) continue;
2240
+ if (row.validFrom > ts) continue;
2241
+ if (row.validUntil !== void 0 && row.validUntil <= ts) continue;
2242
+ if (!best || row.validFrom > best.validFrom) best = row;
2243
+ }
2244
+ return best;
2245
+ }
2246
+
2247
+ // src/brain.ts
2248
+ var activeConfig;
2249
+ function configureBrain(config) {
2250
+ const endpoint = config.endpoint.replace(/\/outcomes\/?$/, "");
2251
+ activeConfig = { ...config, endpoint };
2252
+ const bq = config.brainQuery ?? {};
2253
+ const enabledTables = /* @__PURE__ */ new Set();
2254
+ if (bq.chains !== false) enabledTables.add("kgauto_chains");
2255
+ if (bq.perf !== false) enabledTables.add("kgauto_archetype_perf");
2256
+ if (bq.pricing !== false) enabledTables.add("kgauto_pricing");
2257
+ if (bq.models !== false) {
2258
+ enabledTables.add("kgauto_models");
2259
+ enabledTables.add("kgauto_aliases");
2260
+ }
2261
+ if (enabledTables.size === 0) {
2262
+ configureBrainQuery(void 0);
2263
+ } else {
2264
+ configureBrainQuery({
2265
+ endpoint,
2266
+ configEndpoint: bq.configEndpoint,
2267
+ ttlMs: bq.cacheTtlMs ?? 3e5,
2268
+ fetchImpl: config.fetchImpl ?? fetch,
2269
+ enabledTables,
2270
+ onError: config.onError
2271
+ });
2272
+ }
2273
+ if (bq.findingsExclusions !== false) {
2274
+ configureExclusionFindingsBrain({
2275
+ endpoint: bq.findingsEndpoint ?? DEFAULT_FINDINGS_ENDPOINT,
2276
+ ttlMs: bq.cacheTtlMs ?? 3e5,
2277
+ fetchImpl: config.fetchImpl ?? fetch,
2278
+ onError: config.onError
2279
+ });
2280
+ } else {
2281
+ configureExclusionFindingsBrain(void 0);
2282
+ }
2283
+ }
2284
+ function clearBrain() {
2285
+ activeConfig = void 0;
2286
+ configureBrainQuery(void 0);
2287
+ configureExclusionFindingsBrain(void 0);
2288
+ }
2289
+ var compileRegistry = /* @__PURE__ */ new Map();
2290
+ var REGISTRY_MAX_ENTRIES = 1e4;
2291
+ function registerCompile(appId, archetype, ir, result) {
2292
+ if (compileRegistry.size >= REGISTRY_MAX_ENTRIES) {
2293
+ const cutoff = Math.floor(REGISTRY_MAX_ENTRIES * 0.25);
2294
+ let evicted = 0;
2295
+ for (const k of compileRegistry.keys()) {
2296
+ compileRegistry.delete(k);
2297
+ if (++evicted >= cutoff) break;
2298
+ }
2299
+ }
2300
+ const tokens = result.tokensIn;
2301
+ const shape = computeShape(
2302
+ {
2303
+ appId,
2304
+ intent: { name: archetype, archetype },
2305
+ sections: ir.sections,
2306
+ tools: ir.tools,
2307
+ history: ir.history,
2308
+ models: ir.models,
2309
+ constraints: ir.constraints
2310
+ },
2311
+ tokens
2312
+ );
2313
+ const shapeKey = `${shape.contextBucket}-${shape.toolCountBucket}-${shape.historyDepth}-${shape.outputMode}`;
2314
+ const toolsCount = result.diagnostics.toolsKept;
2315
+ const historyDepth = Array.isArray(ir.history) ? ir.history.length : 0;
2316
+ const systemPromptChars = estimateSystemPromptChars(ir.sections);
2317
+ compileRegistry.set(result.handle, {
2318
+ appId,
2319
+ archetype,
2320
+ model: result.target,
2321
+ provider: result.provider,
2322
+ shapeKey,
2323
+ learningKey: learningKey(archetype, result.target, shape),
2324
+ estimatedTokensIn: tokens,
2325
+ mutationsApplied: result.mutationsApplied.map((m) => m.id),
2326
+ // alpha.30: cache the in-memory advisories so record() can auto-persist
2327
+ // to `compile_outcome_advisories` without consumer-side threading.
2328
+ advisoriesFromCompile: result.advisories ?? [],
2329
+ startedAt: Date.now(),
2330
+ historyCacheableTokens: result.diagnostics.historyCacheableTokens,
2331
+ historyTokensTotal: result.diagnostics.historyTokensTotal,
2332
+ // alpha.20 E3: capture consumer's declared mode for the brain payload.
2333
+ toolOrchestration: result.diagnostics.toolOrchestration,
2334
+ // alpha.28: shape fields for Glass-Box renderer.
2335
+ toolsCount,
2336
+ historyDepth,
2337
+ systemPromptChars,
2338
+ // alpha.29: translator activity — persisted on the brain row so
2339
+ // cross-app aggregates can answer "Sonnet narration rule fired N times,
2340
+ // outcome quality lifted to M."
2341
+ sectionRewritesApplied: result.sectionRewritesApplied
2342
+ });
2343
+ }
2344
+ function estimateSystemPromptChars(sections) {
2345
+ if (!Array.isArray(sections) || sections.length === 0) return void 0;
2346
+ let total = 0;
2347
+ for (const s of sections) {
2348
+ if (s && typeof s === "object") {
2349
+ const content = s.content;
2350
+ if (typeof content === "string") total += content.length;
2351
+ }
2352
+ }
2353
+ return total > 0 ? total : void 0;
2354
+ }
2355
+ async function record(input) {
2356
+ const reg = compileRegistry.get(input.handle);
2357
+ if (reg) compileRegistry.delete(input.handle);
2358
+ if (!activeConfig) {
2359
+ return;
2360
+ }
2361
+ const payload = buildPayload(input, reg);
2362
+ const config = activeConfig;
2363
+ const fetchFn = config.fetchImpl ?? fetch;
2364
+ const send = async () => {
2365
+ let outcomeId;
2366
+ try {
2367
+ const res = await fetchFn(`${config.endpoint}/outcomes`, {
2368
+ method: "POST",
2369
+ headers: {
2370
+ "Content-Type": "application/json",
2371
+ // alpha.20: request the inserted row back so we can JOIN advisories
2372
+ // to it via outcome_id. PostgREST returns the row when
2373
+ // `Prefer: return=representation` is set; proxies that pass the
2374
+ // header through (the recommended `const row = { ...body }` shape
2375
+ // from OutcomePayload's forward-compat rule) will surface
2376
+ // the row id. Proxies that don't (legacy / hand-rolled shapes)
2377
+ // simply produce no parseable id → secondary advisory POST is
2378
+ // skipped silently. Best-effort — primary outcome row is the
2379
+ // load-bearing write.
2380
+ Prefer: "return=representation",
2381
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
2382
+ },
2383
+ body: JSON.stringify(payload)
2384
+ });
2385
+ if (!res.ok) {
2386
+ const text = await res.text().catch(() => "<no body>");
2387
+ throw new Error(`brain ${res.status}: ${text}`);
2388
+ }
2389
+ outcomeId = await tryExtractOutcomeId(res);
2390
+ } catch (err) {
2391
+ (config.onError ?? defaultOnError4)(err);
2392
+ return;
2393
+ }
2394
+ const advisories = input.advisories ?? reg?.advisoriesFromCompile;
2395
+ if (!advisories || advisories.length === 0) return;
2396
+ if (outcomeId === void 0) return;
2397
+ try {
2398
+ const advisoryPayload = advisories.map((a) => buildAdvisoryRow(outcomeId, a));
2399
+ const res = await fetchFn(`${config.endpoint}/compile_outcome_advisories`, {
2400
+ method: "POST",
2401
+ headers: {
2402
+ "Content-Type": "application/json",
2403
+ Prefer: "return=minimal",
2404
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
2405
+ },
2406
+ body: JSON.stringify(advisoryPayload)
2407
+ });
2408
+ if (!res.ok) {
2409
+ const text = await res.text().catch(() => "<no body>");
2410
+ throw new Error(`brain advisories ${res.status}: ${text}`);
2411
+ }
2412
+ } catch (err) {
2413
+ (config.onError ?? defaultOnError4)(err);
2414
+ }
2415
+ };
2416
+ if (config.sync) {
2417
+ await send();
2418
+ } else {
2419
+ void send();
2420
+ }
2421
+ }
2422
+ function defaultOnError4(err) {
2423
+ console.warn("[kgauto] brain record failed:", err);
2424
+ }
2425
+ function buildPayload(input, reg) {
2426
+ const compileTarget = reg?.model;
2427
+ const actual = input.actualModel ?? compileTarget;
2428
+ const requested = input.actualModel && compileTarget && input.actualModel !== compileTarget ? compileTarget : void 0;
2429
+ const mutationsApplied = input.mutationsApplied ?? reg?.mutationsApplied ?? [];
2430
+ const costModel = actual;
2431
+ const costUsdActual = costModel ? computeCostUsd(costModel, input.tokensIn, input.tokensOut) : void 0;
2432
+ const fellOverFrom = input.fellOverFrom ?? requested;
2433
+ const fallbackReason = fellOverFrom ? input.fallbackReason : void 0;
798
2434
  return {
799
2435
  handle: input.handle,
800
2436
  app_id: reg?.appId,
@@ -822,34 +2458,418 @@ function buildPayload(input, reg) {
822
2458
  cache_read_input_tokens: input.cacheReadInputTokens,
823
2459
  cache_creation_input_tokens: input.cacheCreationInputTokens,
824
2460
  cost_usd_actual: costUsdActual,
825
- ttft_ms: input.ttftMs
2461
+ ttft_ms: input.ttftMs,
2462
+ history_cacheable_tokens: reg?.historyCacheableTokens,
2463
+ history_tokens_at_compile: reg?.historyTokensTotal,
2464
+ // alpha.20 E3: mirror consumer's declared tool-orchestration mode so
2465
+ // the brain can measure per-mode model perf separately (DeepSeek in
2466
+ // sequential vs parallel mode is two different stories — L-040).
2467
+ // Null when consumer hadn't adopted the constraint yet.
2468
+ tool_orchestration: reg?.toolOrchestration ?? null,
2469
+ // alpha.28 — Glass-Box renderer substrate (migration 018). All optional;
2470
+ // omitted-undefined PostgREST inserts store NULL → renderer renders "—".
2471
+ finish_reason: input.finishReason,
2472
+ total_ms: input.totalMs ?? input.latencyMs,
2473
+ tools_count: input.toolsCount ?? reg?.toolsCount,
2474
+ history_depth: input.historyDepth ?? reg?.historyDepth,
2475
+ system_prompt_chars: input.systemPromptChars ?? reg?.systemPromptChars,
2476
+ fell_over_from: fellOverFrom,
2477
+ fallback_reason: fallbackReason,
2478
+ // alpha.29 — translator activity (migration 019). Send NULL when no
2479
+ // rewrites fired so the brain's "did the translator do anything?"
2480
+ // queries can use `IS NOT NULL` cleanly.
2481
+ section_rewrites_applied: reg?.sectionRewritesApplied && reg.sectionRewritesApplied.length > 0 ? reg.sectionRewritesApplied : null
2482
+ };
2483
+ }
2484
+ function computeCostUsd(modelId, tokensIn, tokensOut) {
2485
+ if (tokensIn === 0 && tokensOut === 0) return void 0;
2486
+ const brainRow = resolvePricingAt(modelId);
2487
+ if (brainRow && (brainRow.costInputPer1m > 0 || brainRow.costOutputPer1m > 0)) {
2488
+ const inUsd2 = tokensIn / 1e6 * brainRow.costInputPer1m;
2489
+ const outUsd2 = tokensOut / 1e6 * brainRow.costOutputPer1m;
2490
+ return Math.round((inUsd2 + outUsd2) * 1e6) / 1e6;
2491
+ }
2492
+ const profile = tryGetProfile(modelId);
2493
+ if (!profile) return void 0;
2494
+ const inUsd = tokensIn / 1e6 * profile.costInputPer1m;
2495
+ const outUsd = tokensOut / 1e6 * profile.costOutputPer1m;
2496
+ return Math.round((inUsd + outUsd) * 1e6) / 1e6;
2497
+ }
2498
+ async function tryExtractOutcomeId(res) {
2499
+ try {
2500
+ const ct = res.headers?.get?.("content-type") ?? "";
2501
+ if (ct && !ct.includes("application/json")) return void 0;
2502
+ if (typeof res.json !== "function") return void 0;
2503
+ const body = await res.json();
2504
+ if (Array.isArray(body) && body.length > 0) {
2505
+ const first = body[0];
2506
+ const id = first?.id;
2507
+ if (typeof id === "number") return id;
2508
+ } else if (body && typeof body === "object") {
2509
+ const id = body.id;
2510
+ if (typeof id === "number") return id;
2511
+ }
2512
+ return void 0;
2513
+ } catch {
2514
+ return void 0;
2515
+ }
2516
+ }
2517
+ function buildAdvisoryRow(outcomeId, a) {
2518
+ return {
2519
+ outcome_id: outcomeId,
2520
+ code: a.code,
2521
+ level: a.level,
2522
+ message: a.message,
2523
+ ...a.recommendationType ? { recommendation_type: a.recommendationType } : {},
2524
+ ...a.suggestion ? { suggestion: a.suggestion } : {},
2525
+ ...a.docsUrl ? { docs_url: a.docsUrl } : {}
2526
+ };
2527
+ }
2528
+ async function recordOutcome(input) {
2529
+ if (!activeConfig) {
2530
+ return { ok: false, reason: "brain_not_configured" };
2531
+ }
2532
+ const config = activeConfig;
2533
+ const fetchFn = config.fetchImpl ?? fetch;
2534
+ const payload = {
2535
+ outcome_id: input.outcomeId,
2536
+ outcome: input.outcome,
2537
+ rating: input.rating ?? null,
2538
+ reason: input.reason ?? null,
2539
+ observed_confidence: input.observedConfidence ?? null
2540
+ };
2541
+ const send = async () => {
2542
+ try {
2543
+ const res = await fetchFn(`${config.endpoint}/compile_outcome_quality`, {
2544
+ method: "POST",
2545
+ headers: {
2546
+ "Content-Type": "application/json",
2547
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
2548
+ },
2549
+ body: JSON.stringify(payload)
2550
+ });
2551
+ if (!res.ok) {
2552
+ const text = await res.text().catch(() => "<no body>");
2553
+ const err = new Error(`brain ${res.status}: ${text}`);
2554
+ (config.onError ?? defaultOnError4)(err);
2555
+ return { ok: false, reason: "persistence_failed" };
2556
+ }
2557
+ return { ok: true };
2558
+ } catch (err) {
2559
+ (config.onError ?? defaultOnError4)(err);
2560
+ return { ok: false, reason: "persistence_failed" };
2561
+ }
2562
+ };
2563
+ if (config.sync) {
2564
+ return send();
2565
+ }
2566
+ void send();
2567
+ return { ok: true };
2568
+ }
2569
+
2570
+ // src/ir.ts
2571
+ var CallError = class extends Error {
2572
+ attempts;
2573
+ lastErrorCode;
2574
+ lastStatus;
2575
+ constructor(message, attempts, lastStatus, lastErrorCode) {
2576
+ super(message);
2577
+ this.name = "CallError";
2578
+ this.attempts = attempts;
2579
+ this.lastStatus = lastStatus;
2580
+ this.lastErrorCode = lastErrorCode;
2581
+ }
2582
+ };
2583
+
2584
+ // src/streaming.ts
2585
+ var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
2586
+ async function streamAnthropic(request, apiKey, opts) {
2587
+ const { provider: _provider, ...body } = request;
2588
+ const fetchFn = opts.fetchImpl ?? fetch;
2589
+ let res;
2590
+ try {
2591
+ res = await fetchFn(ANTHROPIC_URL, {
2592
+ method: "POST",
2593
+ headers: {
2594
+ "x-api-key": apiKey,
2595
+ "anthropic-version": "2023-06-01",
2596
+ "content-type": "application/json"
2597
+ },
2598
+ body: JSON.stringify({ ...body, stream: true })
2599
+ });
2600
+ } catch (err) {
2601
+ return retryableError(0, "network_error", String(err), null);
2602
+ }
2603
+ if (!res.ok) {
2604
+ const errBody = await res.json().catch(() => ({}));
2605
+ return classifyHttpError(res.status, errBody);
2606
+ }
2607
+ let text = "";
2608
+ let inputTokens = 0;
2609
+ let outputTokens = 0;
2610
+ let cacheReadTokens;
2611
+ let cacheCreatedTokens;
2612
+ let stopReason;
2613
+ const toolBlocks = /* @__PURE__ */ new Map();
2614
+ try {
2615
+ await parseSSEStream(res, (event) => {
2616
+ if (!event.data) return;
2617
+ let payload;
2618
+ try {
2619
+ payload = JSON.parse(event.data);
2620
+ } catch {
2621
+ return;
2622
+ }
2623
+ const type = payload.type;
2624
+ if (type === "message_start") {
2625
+ const msg = payload.message;
2626
+ const usage = msg?.usage;
2627
+ if (usage) {
2628
+ inputTokens = usage.input_tokens ?? 0;
2629
+ outputTokens = usage.output_tokens ?? 0;
2630
+ if (typeof usage.cache_read_input_tokens === "number")
2631
+ cacheReadTokens = usage.cache_read_input_tokens;
2632
+ if (typeof usage.cache_creation_input_tokens === "number")
2633
+ cacheCreatedTokens = usage.cache_creation_input_tokens;
2634
+ }
2635
+ return;
2636
+ }
2637
+ if (type === "content_block_start") {
2638
+ const p = payload;
2639
+ const idx = p.index ?? 0;
2640
+ const block = p.content_block;
2641
+ if (block?.type === "tool_use" && block.id && block.name) {
2642
+ toolBlocks.set(idx, { id: block.id, name: block.name, argsJson: "" });
2643
+ }
2644
+ return;
2645
+ }
2646
+ if (type === "content_block_delta") {
2647
+ const p = payload;
2648
+ const delta = p.delta;
2649
+ if (!delta) return;
2650
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
2651
+ text += delta.text;
2652
+ opts.onChunk(delta.text);
2653
+ } else if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
2654
+ const idx = p.index ?? 0;
2655
+ const tool = toolBlocks.get(idx);
2656
+ if (tool) tool.argsJson += delta.partial_json;
2657
+ }
2658
+ return;
2659
+ }
2660
+ if (type === "message_delta") {
2661
+ const p = payload;
2662
+ if (p.delta?.stop_reason) stopReason = p.delta.stop_reason;
2663
+ if (typeof p.usage?.output_tokens === "number") outputTokens = p.usage.output_tokens;
2664
+ return;
2665
+ }
2666
+ });
2667
+ } catch (err) {
2668
+ return retryableError(0, "stream_interrupted", String(err), null);
2669
+ }
2670
+ const toolCalls = Array.from(toolBlocks.values()).map((b) => ({
2671
+ id: b.id,
2672
+ name: b.name,
2673
+ args: tryParseJson(b.argsJson) ?? {}
2674
+ }));
2675
+ const tokens = {
2676
+ input: inputTokens,
2677
+ output: outputTokens,
2678
+ total: inputTokens + outputTokens,
2679
+ cached: cacheReadTokens,
2680
+ cacheCreated: cacheCreatedTokens
2681
+ };
2682
+ const response = {
2683
+ text,
2684
+ structuredOutput: null,
2685
+ toolCalls,
2686
+ tokens,
2687
+ finishReason: stopReason,
2688
+ raw: { streamed: true, provider: "anthropic" }
2689
+ };
2690
+ return { ok: true, status: res.status, response };
2691
+ }
2692
+ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
2693
+ const { provider: _provider, ...body } = request;
2694
+ const fetchFn = opts.fetchImpl ?? fetch;
2695
+ const reqBody = {
2696
+ ...body,
2697
+ stream: true,
2698
+ stream_options: {
2699
+ ...body.stream_options ?? {},
2700
+ include_usage: true
2701
+ }
2702
+ };
2703
+ let res;
2704
+ try {
2705
+ res = await fetchFn(url, {
2706
+ method: "POST",
2707
+ headers: {
2708
+ authorization: `Bearer ${apiKey}`,
2709
+ "content-type": "application/json"
2710
+ },
2711
+ body: JSON.stringify(reqBody)
2712
+ });
2713
+ } catch (err) {
2714
+ return retryableError(0, "network_error", String(err), null);
2715
+ }
2716
+ if (!res.ok) {
2717
+ const errBody = await res.json().catch(() => ({}));
2718
+ return classifyHttpError(res.status, errBody);
2719
+ }
2720
+ let text = "";
2721
+ let finishReason;
2722
+ let inputTokens = 0;
2723
+ let outputTokens = 0;
2724
+ let totalTokens;
2725
+ let cachedTokens;
2726
+ const toolBuffers = /* @__PURE__ */ new Map();
2727
+ try {
2728
+ await parseSSEStream(res, (event) => {
2729
+ if (!event.data) return;
2730
+ if (event.data === "[DONE]") return;
2731
+ let payload;
2732
+ try {
2733
+ payload = JSON.parse(event.data);
2734
+ } catch {
2735
+ return;
2736
+ }
2737
+ const choices = payload.choices;
2738
+ if (choices && choices.length > 0) {
2739
+ const choice = choices[0];
2740
+ const deltaContent = choice.delta?.content;
2741
+ if (typeof deltaContent === "string" && deltaContent.length > 0) {
2742
+ text += deltaContent;
2743
+ opts.onChunk(deltaContent);
2744
+ }
2745
+ const toolDeltas = choice.delta?.tool_calls;
2746
+ if (toolDeltas) {
2747
+ for (const tcDelta of toolDeltas) {
2748
+ const idx = tcDelta.index ?? 0;
2749
+ let buf = toolBuffers.get(idx);
2750
+ if (!buf) {
2751
+ buf = { id: tcDelta.id ?? `tc-${idx}`, name: "", argsJson: "" };
2752
+ toolBuffers.set(idx, buf);
2753
+ }
2754
+ if (tcDelta.id) buf.id = tcDelta.id;
2755
+ if (tcDelta.function?.name) buf.name += tcDelta.function.name;
2756
+ if (tcDelta.function?.arguments) buf.argsJson += tcDelta.function.arguments;
2757
+ }
2758
+ }
2759
+ if (choice.finish_reason) finishReason = choice.finish_reason;
2760
+ }
2761
+ const usage = payload.usage;
2762
+ if (usage) {
2763
+ if (typeof usage.prompt_tokens === "number") inputTokens = usage.prompt_tokens;
2764
+ if (typeof usage.completion_tokens === "number") outputTokens = usage.completion_tokens;
2765
+ if (typeof usage.total_tokens === "number") totalTokens = usage.total_tokens;
2766
+ const details = usage.prompt_tokens_details;
2767
+ if (typeof details?.cached_tokens === "number") cachedTokens = details.cached_tokens;
2768
+ }
2769
+ });
2770
+ } catch (err) {
2771
+ return retryableError(0, "stream_interrupted", String(err), null);
2772
+ }
2773
+ const toolCalls = Array.from(toolBuffers.values()).filter((b) => b.name.length > 0).map((b) => ({
2774
+ id: b.id,
2775
+ name: b.name,
2776
+ args: tryParseJson(b.argsJson) ?? {}
2777
+ }));
2778
+ const tokens = {
2779
+ input: inputTokens,
2780
+ output: outputTokens,
2781
+ total: totalTokens ?? inputTokens + outputTokens,
2782
+ cached: cachedTokens
826
2783
  };
2784
+ const response = {
2785
+ text,
2786
+ structuredOutput: null,
2787
+ toolCalls,
2788
+ tokens,
2789
+ finishReason,
2790
+ raw: { streamed: true, provider: providerLabel }
2791
+ };
2792
+ return { ok: true, status: res.status, response };
2793
+ }
2794
+ async function parseSSEStream(response, handler) {
2795
+ const body = response.body;
2796
+ if (!body) throw new Error("Response has no body for SSE parse");
2797
+ const reader = body.getReader();
2798
+ const decoder = new TextDecoder("utf-8");
2799
+ let buffer = "";
2800
+ for (; ; ) {
2801
+ const { value, done } = await reader.read();
2802
+ if (done) break;
2803
+ buffer += decoder.decode(value, { stream: true });
2804
+ let sep;
2805
+ while (sep = buffer.indexOf("\n\n"), sep !== -1) {
2806
+ const block = buffer.slice(0, sep);
2807
+ buffer = buffer.slice(sep + 2);
2808
+ const event = parseSSEBlock(block);
2809
+ if (event) handler(event);
2810
+ }
2811
+ }
2812
+ if (buffer.length > 0) {
2813
+ const event = parseSSEBlock(buffer);
2814
+ if (event) handler(event);
2815
+ }
827
2816
  }
828
- function computeCostUsd(modelId, tokensIn, tokensOut) {
829
- if (tokensIn === 0 && tokensOut === 0) return void 0;
830
- const profile = tryGetProfile(modelId);
831
- if (!profile) return void 0;
832
- const inUsd = tokensIn / 1e6 * profile.costInputPer1m;
833
- const outUsd = tokensOut / 1e6 * profile.costOutputPer1m;
834
- return Math.round((inUsd + outUsd) * 1e6) / 1e6;
2817
+ function parseSSEBlock(block) {
2818
+ const lines = block.split(/\r?\n/);
2819
+ let eventName;
2820
+ const dataLines = [];
2821
+ for (const line of lines) {
2822
+ if (line.startsWith(":")) continue;
2823
+ if (line.startsWith("event:")) {
2824
+ eventName = line.slice(6).trim();
2825
+ } else if (line.startsWith("data:")) {
2826
+ dataLines.push(line.slice(5).trim());
2827
+ }
2828
+ }
2829
+ if (dataLines.length === 0) return void 0;
2830
+ return { event: eventName, data: dataLines.join("\n") };
835
2831
  }
836
-
837
- // src/ir.ts
838
- var CallError = class extends Error {
839
- attempts;
840
- lastErrorCode;
841
- lastStatus;
842
- constructor(message, attempts, lastStatus, lastErrorCode) {
843
- super(message);
844
- this.name = "CallError";
845
- this.attempts = attempts;
846
- this.lastStatus = lastStatus;
847
- this.lastErrorCode = lastErrorCode;
2832
+ function tryParseJson(s) {
2833
+ if (typeof s !== "string" || s.length === 0) return void 0;
2834
+ try {
2835
+ const parsed = JSON.parse(s);
2836
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
2837
+ } catch {
2838
+ return void 0;
848
2839
  }
849
- };
2840
+ }
2841
+ function classifyHttpError(status, body) {
2842
+ const message = extractErrorMessage(body) ?? `HTTP ${status}`;
2843
+ if (status === 429)
2844
+ return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
2845
+ if (status === 408)
2846
+ return { ok: false, status, errorType: "retryable", errorCode: "timeout", message, raw: body };
2847
+ if (status >= 500)
2848
+ return { ok: false, status, errorType: "retryable", errorCode: "server_error", message, raw: body };
2849
+ if (status === 404)
2850
+ return { ok: false, status, errorType: "retryable", errorCode: "model_not_found", message, raw: body };
2851
+ if (status === 401 || status === 403)
2852
+ return { ok: false, status, errorType: "terminal", errorCode: "auth", message, raw: body };
2853
+ if (status === 400)
2854
+ return { ok: false, status, errorType: "terminal", errorCode: "invalid_request", message, raw: body };
2855
+ return { ok: false, status, errorType: "terminal", errorCode: "unknown", message, raw: body };
2856
+ }
2857
+ function extractErrorMessage(body) {
2858
+ if (!body || typeof body !== "object") return void 0;
2859
+ const b = body;
2860
+ if (b.error && typeof b.error === "object") {
2861
+ const e = b.error;
2862
+ if (typeof e.message === "string") return e.message;
2863
+ }
2864
+ if (typeof b.message === "string") return b.message;
2865
+ return void 0;
2866
+ }
2867
+ function retryableError(status, code, message, raw) {
2868
+ return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
2869
+ }
850
2870
 
851
2871
  // src/execute.ts
852
- var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
2872
+ var ANTHROPIC_URL2 = "https://api.anthropic.com/v1/messages";
853
2873
  var OPENAI_URL = "https://api.openai.com/v1/chat/completions";
854
2874
  var DEEPSEEK_URL = "https://api.deepseek.com/chat/completions";
855
2875
  async function execute(request, opts = {}) {
@@ -870,16 +2890,22 @@ async function execute(request, opts = {}) {
870
2890
  }
871
2891
  }
872
2892
  async function executeAnthropic(request, opts) {
873
- const apiKey = opts.apiKeys?.anthropic ?? process.env.ANTHROPIC_API_KEY;
2893
+ const apiKey = resolveProviderKey("anthropic", { apiKeys: opts.apiKeys });
874
2894
  if (!apiKey) {
875
2895
  return terminalError(401, "auth", "ANTHROPIC_API_KEY missing");
876
2896
  }
2897
+ if (opts.onChunk) {
2898
+ return streamAnthropic(request, apiKey, {
2899
+ onChunk: opts.onChunk,
2900
+ fetchImpl: opts.fetchImpl
2901
+ });
2902
+ }
877
2903
  const { provider: _provider, ...body } = request;
878
2904
  const fetchFn = opts.fetchImpl ?? fetch;
879
2905
  let res;
880
2906
  let json;
881
2907
  try {
882
- res = await fetchFn(ANTHROPIC_URL, {
2908
+ res = await fetchFn(ANTHROPIC_URL2, {
883
2909
  method: "POST",
884
2910
  headers: {
885
2911
  "x-api-key": apiKey,
@@ -890,9 +2916,9 @@ async function executeAnthropic(request, opts) {
890
2916
  });
891
2917
  json = await res.json().catch(() => ({}));
892
2918
  } catch (err) {
893
- return retryableError(0, "network_error", String(err), null);
2919
+ return retryableError2(0, "network_error", String(err), null);
894
2920
  }
895
- if (!res.ok) return classifyHttpError(res.status, json);
2921
+ if (!res.ok) return classifyHttpError2(res.status, json);
896
2922
  return { ok: true, status: res.status, response: normalizeAnthropic(json) };
897
2923
  }
898
2924
  function normalizeAnthropic(raw) {
@@ -909,7 +2935,7 @@ function normalizeAnthropic(raw) {
909
2935
  return { text, structuredOutput: null, toolCalls, tokens, finishReason: r.stop_reason, raw };
910
2936
  }
911
2937
  async function executeGoogle(request, opts) {
912
- const apiKey = opts.apiKeys?.google ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
2938
+ const apiKey = resolveProviderKey("google", { apiKeys: opts.apiKeys });
913
2939
  if (!apiKey) {
914
2940
  return terminalError(401, "auth", "GOOGLE_API_KEY/GEMINI_API_KEY missing");
915
2941
  }
@@ -926,9 +2952,9 @@ async function executeGoogle(request, opts) {
926
2952
  });
927
2953
  json = await res.json().catch(() => ({}));
928
2954
  } catch (err) {
929
- return retryableError(0, "network_error", String(err), null);
2955
+ return retryableError2(0, "network_error", String(err), null);
930
2956
  }
931
- if (!res.ok) return classifyHttpError(res.status, json);
2957
+ if (!res.ok) return classifyHttpError2(res.status, json);
932
2958
  return { ok: true, status: res.status, response: normalizeGoogle(json) };
933
2959
  }
934
2960
  function normalizeGoogle(raw) {
@@ -951,10 +2977,16 @@ function normalizeGoogle(raw) {
951
2977
  return { text, structuredOutput: null, toolCalls, tokens, finishReason: candidate?.finishReason, raw };
952
2978
  }
953
2979
  async function executeOpenAI(request, opts) {
954
- const apiKey = opts.apiKeys?.openai ?? process.env.OPENAI_API_KEY;
2980
+ const apiKey = resolveProviderKey("openai", { apiKeys: opts.apiKeys });
955
2981
  if (!apiKey) {
956
2982
  return terminalError(401, "auth", "OPENAI_API_KEY missing");
957
2983
  }
2984
+ if (opts.onChunk) {
2985
+ return streamOpenAILike(OPENAI_URL, request, apiKey, "openai", {
2986
+ onChunk: opts.onChunk,
2987
+ fetchImpl: opts.fetchImpl
2988
+ });
2989
+ }
958
2990
  const { provider: _provider, ...body } = request;
959
2991
  const fetchFn = opts.fetchImpl ?? fetch;
960
2992
  let res;
@@ -967,16 +2999,22 @@ async function executeOpenAI(request, opts) {
967
2999
  });
968
3000
  json = await res.json().catch(() => ({}));
969
3001
  } catch (err) {
970
- return retryableError(0, "network_error", String(err), null);
3002
+ return retryableError2(0, "network_error", String(err), null);
971
3003
  }
972
- if (!res.ok) return classifyHttpError(res.status, json);
3004
+ if (!res.ok) return classifyHttpError2(res.status, json);
973
3005
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
974
3006
  }
975
3007
  async function executeDeepSeek(request, opts) {
976
- const apiKey = opts.apiKeys?.deepseek ?? process.env.DEEPSEEK_API_KEY;
3008
+ const apiKey = resolveProviderKey("deepseek", { apiKeys: opts.apiKeys });
977
3009
  if (!apiKey) {
978
3010
  return terminalError(401, "auth", "DEEPSEEK_API_KEY missing");
979
3011
  }
3012
+ if (opts.onChunk) {
3013
+ return streamOpenAILike(DEEPSEEK_URL, request, apiKey, "deepseek", {
3014
+ onChunk: opts.onChunk,
3015
+ fetchImpl: opts.fetchImpl
3016
+ });
3017
+ }
980
3018
  const { provider: _provider, ...body } = request;
981
3019
  const fetchFn = opts.fetchImpl ?? fetch;
982
3020
  let res;
@@ -989,9 +3027,9 @@ async function executeDeepSeek(request, opts) {
989
3027
  });
990
3028
  json = await res.json().catch(() => ({}));
991
3029
  } catch (err) {
992
- return retryableError(0, "network_error", String(err), null);
3030
+ return retryableError2(0, "network_error", String(err), null);
993
3031
  }
994
- if (!res.ok) return classifyHttpError(res.status, json);
3032
+ if (!res.ok) return classifyHttpError2(res.status, json);
995
3033
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
996
3034
  }
997
3035
  function normalizeOpenAILike(raw) {
@@ -1001,7 +3039,7 @@ function normalizeOpenAILike(raw) {
1001
3039
  const toolCalls = (choice?.message?.tool_calls ?? []).filter((tc) => tc.function?.name).map((tc, i) => ({
1002
3040
  id: tc.id ?? `tc-${i}`,
1003
3041
  name: tc.function.name,
1004
- args: tryParseJson(tc.function?.arguments) ?? {}
3042
+ args: tryParseJson2(tc.function?.arguments) ?? {}
1005
3043
  }));
1006
3044
  const u = r.usage ?? {};
1007
3045
  const tokens = {
@@ -1018,8 +3056,8 @@ function applyOverrides(request, overrides) {
1018
3056
  if (!layer) return request;
1019
3057
  return { ...request, ...layer };
1020
3058
  }
1021
- function classifyHttpError(status, body) {
1022
- const message = extractErrorMessage(body) ?? `HTTP ${status}`;
3059
+ function classifyHttpError2(status, body) {
3060
+ const message = extractErrorMessage2(body) ?? `HTTP ${status}`;
1023
3061
  if (status === 429) {
1024
3062
  return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
1025
3063
  }
@@ -1040,7 +3078,7 @@ function classifyHttpError(status, body) {
1040
3078
  }
1041
3079
  return { ok: false, status, errorType: "terminal", errorCode: "unknown", message, raw: body };
1042
3080
  }
1043
- function extractErrorMessage(body) {
3081
+ function extractErrorMessage2(body) {
1044
3082
  if (!body || typeof body !== "object") return void 0;
1045
3083
  const b = body;
1046
3084
  if (b.error && typeof b.error === "object") {
@@ -1053,10 +3091,10 @@ function extractErrorMessage(body) {
1053
3091
  function terminalError(status, code, message) {
1054
3092
  return { ok: false, status, errorType: "terminal", errorCode: code, message, raw: null };
1055
3093
  }
1056
- function retryableError(status, code, message, raw) {
3094
+ function retryableError2(status, code, message, raw) {
1057
3095
  return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
1058
3096
  }
1059
- function tryParseJson(s) {
3097
+ function tryParseJson2(s) {
1060
3098
  if (typeof s !== "string" || s.length === 0) return void 0;
1061
3099
  try {
1062
3100
  const parsed = JSON.parse(s);
@@ -1068,15 +3106,148 @@ function tryParseJson(s) {
1068
3106
 
1069
3107
  // src/call.ts
1070
3108
  async function call(ir, opts = {}) {
3109
+ const traceId = generateTraceId();
3110
+ safeEmit(
3111
+ () => emitCompileStart(traceId, ir.appId, {
3112
+ appId: ir.appId,
3113
+ archetype: ir.intent.archetype,
3114
+ // alpha.41: ir.models is ChainModelEntry[] (string | { family }).
3115
+ // Glass-Box renderer carries string ids only; surface family entries
3116
+ // as their family tag prefixed so the panel still shows them in the
3117
+ // pre-resolution log.
3118
+ models: ir.models.map(
3119
+ (m) => typeof m === "string" ? m : `family:${m.family}`
3120
+ )
3121
+ })
3122
+ );
1071
3123
  const initial = compileAndRegister(ir, opts);
3124
+ safeEmit(
3125
+ () => emitCompileDone(traceId, ir.appId, {
3126
+ target: initial.target,
3127
+ provider: initial.provider,
3128
+ fallbackChain: initial.fallbackChain,
3129
+ tokensIn: initial.tokensIn,
3130
+ estimatedCostUsd: initial.estimatedCostUsd,
3131
+ mutationsApplied: initial.mutationsApplied,
3132
+ advisories: initial.advisories
3133
+ })
3134
+ );
3135
+ for (const adv of initial.advisories) {
3136
+ safeEmit(
3137
+ () => emitAdvisoryFired(traceId, ir.appId, { code: adv.code, message: adv.message })
3138
+ );
3139
+ }
1072
3140
  const start = Date.now();
1073
3141
  const attempts = [];
1074
- const targetsToTry = [initial.target, ...initial.fallbackChain];
3142
+ const rawTargets = [initial.target, ...initial.fallbackChain];
3143
+ let unreachableFiltered;
3144
+ let targetsToTry;
3145
+ if (opts.noAutoFilter) {
3146
+ targetsToTry = rawTargets;
3147
+ } else {
3148
+ const dropped = [];
3149
+ targetsToTry = [];
3150
+ for (const t of rawTargets) {
3151
+ if (isModelReachable(t, { apiKeys: opts.apiKeys })) {
3152
+ targetsToTry.push(t);
3153
+ } else {
3154
+ dropped.push(t);
3155
+ }
3156
+ }
3157
+ unreachableFiltered = dropped;
3158
+ if (targetsToTry.length === 0) {
3159
+ const latencyMs2 = Date.now() - start;
3160
+ await record({
3161
+ handle: initial.handle,
3162
+ tokensIn: 0,
3163
+ tokensOut: 0,
3164
+ latencyMs: latencyMs2,
3165
+ success: false,
3166
+ errorType: "no_reachable_models",
3167
+ promptPreview: extractPromptPreview(ir)
3168
+ });
3169
+ const noReachableAttempts = dropped.map((m) => ({
3170
+ model: m,
3171
+ status: "terminal",
3172
+ errorCode: "unreachable_provider",
3173
+ message: `No API key for ${m}'s provider \u2014 set one of PROVIDER_ENV_KEYS or pass apiKeys`
3174
+ }));
3175
+ throw new CallError(
3176
+ `call(): no reachable models in chain. Filtered: [${dropped.join(", ")}]. Add a key for one provider, or pass apiKeys.`,
3177
+ noReachableAttempts,
3178
+ void 0,
3179
+ "no_reachable_models"
3180
+ );
3181
+ }
3182
+ const archetypeName = ir.intent?.archetype;
3183
+ if (archetypeName) {
3184
+ const ensured = ensureCrossProviderTail({
3185
+ chain: targetsToTry,
3186
+ archetype: archetypeName,
3187
+ apiKeys: opts.apiKeys
3188
+ });
3189
+ if (ensured.appended) {
3190
+ targetsToTry = ensured.chain;
3191
+ }
3192
+ }
3193
+ }
3194
+ let policyBlockedFiltered;
3195
+ if (opts.policy?.blockedModels && opts.policy.blockedModels.length > 0) {
3196
+ const blocked = new Set(opts.policy.blockedModels);
3197
+ const filtered = [];
3198
+ const dropped = [];
3199
+ for (const t of targetsToTry) {
3200
+ if (blocked.has(t)) {
3201
+ dropped.push(t);
3202
+ } else {
3203
+ filtered.push(t);
3204
+ }
3205
+ }
3206
+ if (dropped.length > 0) {
3207
+ policyBlockedFiltered = dropped;
3208
+ targetsToTry = filtered;
3209
+ }
3210
+ if (targetsToTry.length === 0) {
3211
+ const latencyMs2 = Date.now() - start;
3212
+ await record({
3213
+ handle: initial.handle,
3214
+ tokensIn: 0,
3215
+ tokensOut: 0,
3216
+ latencyMs: latencyMs2,
3217
+ success: false,
3218
+ errorType: "all_blocked_by_policy",
3219
+ promptPreview: extractPromptPreview(ir)
3220
+ });
3221
+ const blockedAttempts = dropped.map((m) => ({
3222
+ model: m,
3223
+ status: "terminal",
3224
+ errorCode: "blocked_by_policy",
3225
+ message: `Skipped \u2014 model ${m} is in CompilePolicy.blockedModels`
3226
+ }));
3227
+ throw new CallError(
3228
+ `call(): all chain targets blocked by CompilePolicy.blockedModels: [${dropped.join(", ")}]`,
3229
+ blockedAttempts,
3230
+ void 0,
3231
+ "all_blocked_by_policy"
3232
+ );
3233
+ }
3234
+ }
1075
3235
  let activeCompile = initial;
1076
3236
  let lastErr;
3237
+ const failedProviders = /* @__PURE__ */ new Set();
1077
3238
  for (let i = 0; i < targetsToTry.length; i++) {
1078
3239
  const targetModel = targetsToTry[i];
1079
- if (i > 0) {
3240
+ const targetProfile = tryGetProfile(targetModel);
3241
+ if (targetProfile && failedProviders.has(targetProfile.provider) && !opts.noFallback) {
3242
+ attempts.push({
3243
+ model: targetModel,
3244
+ status: "terminal",
3245
+ errorCode: "auth_inferred",
3246
+ message: `Skipped \u2014 provider ${targetProfile.provider} returned 401/403 earlier in this call; same key inferred to fail`
3247
+ });
3248
+ continue;
3249
+ }
3250
+ if (targetModel !== initial.target) {
1080
3251
  try {
1081
3252
  activeCompile = compileAndRegister(
1082
3253
  {
@@ -1096,49 +3267,98 @@ async function call(ir, opts = {}) {
1096
3267
  continue;
1097
3268
  }
1098
3269
  }
3270
+ safeEmit(
3271
+ () => emitExecuteAttempt(traceId, ir.appId, { model: targetModel, attemptIndex: i })
3272
+ );
3273
+ const targetSupportsStreaming = targetProfile?.streaming === true;
3274
+ const streamingOnChunk = opts.onChunk && !opts.noStream && targetSupportsStreaming ? opts.onChunk : void 0;
1099
3275
  const exec = await execute(activeCompile.request, {
1100
3276
  apiKeys: opts.apiKeys,
1101
3277
  fetchImpl: opts.fetchImpl,
1102
- providerOverrides: opts.providerOverrides
3278
+ providerOverrides: opts.providerOverrides,
3279
+ onChunk: streamingOnChunk
1103
3280
  });
1104
- if (exec.ok) {
3281
+ const validated = exec.ok ? validateStructuredContract(exec, ir) : exec;
3282
+ if (validated.ok) {
1105
3283
  attempts.push({ model: targetModel, status: "success" });
1106
3284
  const latencyMs2 = Date.now() - start;
1107
- const responseWithStructured = withStructuredOutput(exec.response, ir);
3285
+ safeEmit(
3286
+ () => emitExecuteSuccess(traceId, ir.appId, {
3287
+ model: targetModel,
3288
+ tokensIn: validated.response.tokens.input,
3289
+ tokensOut: validated.response.tokens.output,
3290
+ latencyMs: latencyMs2
3291
+ })
3292
+ );
3293
+ const fellOver = targetModel !== initial.target;
3294
+ const fallbackReason = fellOver ? normalizeFallbackReason(attempts) : void 0;
1108
3295
  await record({
1109
3296
  handle: initial.handle,
1110
- tokensIn: responseWithStructured.tokens.input,
1111
- tokensOut: responseWithStructured.tokens.output,
3297
+ tokensIn: validated.response.tokens.input,
3298
+ tokensOut: validated.response.tokens.output,
1112
3299
  latencyMs: latencyMs2,
1113
3300
  success: true,
1114
- emptyResponse: responseWithStructured.tokens.output === 0,
1115
- toolsCalled: responseWithStructured.toolCalls.map((tc) => tc.name),
3301
+ emptyResponse: validated.response.tokens.output === 0,
3302
+ toolsCalled: validated.response.toolCalls.map((tc) => tc.name),
1116
3303
  actualModel: targetModel !== initial.target ? targetModel : void 0,
1117
3304
  mutationsApplied: targetModel !== initial.target ? activeCompile.mutationsApplied.map((m) => m.id) : void 0,
1118
3305
  promptPreview: extractPromptPreview(ir),
1119
- responsePreview: responseWithStructured.text.slice(0, 200),
1120
- cacheReadInputTokens: responseWithStructured.tokens.cached,
1121
- cacheCreationInputTokens: responseWithStructured.tokens.cacheCreated
3306
+ responsePreview: validated.response.text.slice(0, 200),
3307
+ cacheReadInputTokens: validated.response.tokens.cached,
3308
+ cacheCreationInputTokens: validated.response.tokens.cacheCreated,
3309
+ // alpha.28 — Glass-Box renderer substrate (migration 018). call()
3310
+ // owns the lifecycle so it has direct visibility into finishReason
3311
+ // (from the normalized provider response), totalMs (mirrors latencyMs
3312
+ // for non-streaming; future streaming variant may diverge), and the
3313
+ // fell-over-from / fallback-reason pair (already computed above for
3314
+ // the CallResult return shape).
3315
+ finishReason: validated.response.finishReason,
3316
+ totalMs: latencyMs2,
3317
+ fellOverFrom: fellOver ? initial.target : void 0,
3318
+ fallbackReason
1122
3319
  });
3320
+ if (fellOver) {
3321
+ const firstFailed = attempts.find((a) => a.status !== "success");
3322
+ if (firstFailed) {
3323
+ safeEmit(
3324
+ () => emitFallbackWalked(traceId, ir.appId, {
3325
+ from: initial.target,
3326
+ to: targetModel,
3327
+ reason: fallbackReason ?? "unknown",
3328
+ attempt: firstFailed
3329
+ })
3330
+ );
3331
+ }
3332
+ }
1123
3333
  return {
1124
3334
  handle: initial.handle,
1125
3335
  actualModel: targetModel,
1126
3336
  requestedModel: initial.target,
1127
3337
  provider: activeCompile.provider,
1128
- response: responseWithStructured,
3338
+ response: validated.response,
1129
3339
  latencyMs: latencyMs2,
1130
3340
  mutationsApplied: activeCompile.mutationsApplied,
1131
- attempts
3341
+ attempts,
3342
+ servedBy: targetModel,
3343
+ fellOverFrom: fellOver ? initial.target : void 0,
3344
+ fallbackReason,
3345
+ unreachableFiltered,
3346
+ policyBlockedFiltered,
3347
+ traceId
1132
3348
  };
1133
3349
  }
1134
3350
  attempts.push({
1135
3351
  model: targetModel,
1136
- status: exec.errorType,
1137
- errorCode: exec.errorCode,
1138
- message: exec.message
3352
+ status: validated.errorType,
3353
+ errorCode: validated.errorCode,
3354
+ message: validated.message
1139
3355
  });
1140
- lastErr = exec;
1141
- if (exec.errorType === "terminal" || opts.noFallback) {
3356
+ lastErr = validated;
3357
+ if (validated.errorType === "terminal" || opts.noFallback) {
3358
+ if (validated.errorCode === "auth" && !opts.noFallback && activeCompile.provider) {
3359
+ failedProviders.add(activeCompile.provider);
3360
+ continue;
3361
+ }
1142
3362
  break;
1143
3363
  }
1144
3364
  }
@@ -1152,8 +3372,10 @@ async function call(ir, opts = {}) {
1152
3372
  errorType: lastErr?.errorCode,
1153
3373
  promptPreview: extractPromptPreview(ir)
1154
3374
  });
3375
+ const filteredNote = unreachableFiltered && unreachableFiltered.length > 0 ? ` (also auto-filtered: [${unreachableFiltered.join(", ")}] \u2014 no API key)` : "";
3376
+ const blockedNote = policyBlockedFiltered && policyBlockedFiltered.length > 0 ? ` (also policy-blocked: [${policyBlockedFiltered.join(", ")}])` : "";
1155
3377
  throw new CallError(
1156
- `call(): all attempts failed${lastErr ? ` \u2014 ${lastErr.errorCode}: ${lastErr.message}` : ""}`,
3378
+ `call(): all attempts failed${lastErr ? ` \u2014 ${lastErr.errorCode}: ${lastErr.message}` : ""}${filteredNote}${blockedNote}`,
1157
3379
  attempts,
1158
3380
  lastErr?.status,
1159
3381
  lastErr?.errorCode
@@ -1175,20 +3397,95 @@ function extractPromptPreview(ir) {
1175
3397
  if (lastHist) return lastHist.slice(0, 200);
1176
3398
  return void 0;
1177
3399
  }
1178
- function withStructuredOutput(response, ir) {
1179
- if (!ir.constraints?.structuredOutput) return response;
1180
- if (!response.text) return response;
3400
+ function validateStructuredContract(exec, ir) {
3401
+ if (!ir.constraints?.structuredOutput) {
3402
+ return { ok: true, response: exec.response };
3403
+ }
3404
+ const finish = (exec.response.finishReason ?? "").toLowerCase();
3405
+ if (finish === "max_tokens" || finish === "length") {
3406
+ return {
3407
+ ok: false,
3408
+ status: exec.status,
3409
+ errorType: "retryable",
3410
+ errorCode: "max_tokens_on_structured_output",
3411
+ message: `Provider returned finishReason="${exec.response.finishReason}" on a structured-output call \u2014 output truncated mid-token, JSON cannot be valid`,
3412
+ raw: exec.response.raw
3413
+ };
3414
+ }
3415
+ if (!exec.response.text) {
3416
+ return { ok: true, response: exec.response };
3417
+ }
1181
3418
  try {
1182
- const parsed = JSON.parse(response.text);
1183
- return { ...response, structuredOutput: parsed };
3419
+ const parsed = JSON.parse(exec.response.text);
3420
+ return { ok: true, response: { ...exec.response, structuredOutput: parsed } };
1184
3421
  } catch (err) {
1185
3422
  return {
1186
- ...response,
1187
- structuredOutput: null,
1188
- parseError: err instanceof Error ? err.message : String(err)
3423
+ ok: false,
3424
+ status: exec.status,
3425
+ errorType: "retryable",
3426
+ errorCode: "structured_output_parse_failed",
3427
+ message: err instanceof Error ? err.message : String(err),
3428
+ raw: exec.response.raw
1189
3429
  };
1190
3430
  }
1191
3431
  }
3432
+ function normalizeFallbackReason(attempts) {
3433
+ const first = attempts.find((a) => a.status !== "success");
3434
+ if (!first) return void 0;
3435
+ const code = first.errorCode ?? "";
3436
+ if (code === "rate_limit_429" || code === "rate_limit") return "rate_limit";
3437
+ if (code === "max_tokens_on_structured_output" || code === "structured_output_parse_failed") {
3438
+ return "cliff";
3439
+ }
3440
+ if (code === "cost_cap_exceeded") return "cost_cap";
3441
+ if (code === "auth" || code === "auth_inferred") return "provider_auth_failed";
3442
+ return "provider_error";
3443
+ }
3444
+ function generateTraceId() {
3445
+ try {
3446
+ const g = globalThis;
3447
+ if (g.crypto && typeof g.crypto.randomUUID === "function") {
3448
+ return g.crypto.randomUUID();
3449
+ }
3450
+ } catch {
3451
+ }
3452
+ const hex = (n) => Math.floor(Math.random() * Math.pow(16, n)).toString(16).padStart(n, "0");
3453
+ return `${hex(8)}-${hex(4)}-${hex(4)}-${hex(4)}-${hex(12)}`;
3454
+ }
3455
+ function safeEmit(fn) {
3456
+ try {
3457
+ fn();
3458
+ } catch {
3459
+ }
3460
+ }
3461
+
3462
+ // src/streamtext-helpers.ts
3463
+ function attachCacheControlToStreamTextInput(result, convertedMessages) {
3464
+ const messages = convertedMessages.map((m) => ({ ...m }));
3465
+ const markIdx = result.diagnostics.historyCacheMarkIndex;
3466
+ if (result.provider === "anthropic" && typeof markIdx === "number" && markIdx >= 0 && markIdx < messages.length) {
3467
+ const target = messages[markIdx];
3468
+ if (target) {
3469
+ messages[markIdx] = {
3470
+ role: target.role,
3471
+ content: target.content,
3472
+ providerOptions: {
3473
+ ...target.providerOptions ?? {},
3474
+ anthropic: {
3475
+ ...target.providerOptions?.anthropic ?? {},
3476
+ cacheControl: { type: "ephemeral" }
3477
+ }
3478
+ }
3479
+ };
3480
+ }
3481
+ }
3482
+ const systemMessages = result.systemMessages;
3483
+ const hasStructuredSystemCacheControl = result.provider === "anthropic" && systemMessages.some(
3484
+ (m) => m.providerOptions?.anthropic?.cacheControl !== void 0
3485
+ );
3486
+ const system = hasStructuredSystemCacheControl ? systemMessages : systemMessages.map((m) => m.content).join("\n\n");
3487
+ return { system, messages };
3488
+ }
1192
3489
 
1193
3490
  // src/oracle.ts
1194
3491
  var DEFAULT_DIMENSIONS = ["correctness", "completeness", "conciseness", "format"];
@@ -1278,6 +3575,338 @@ function clamp(n) {
1278
3575
  return Math.max(0, Math.min(1, n));
1279
3576
  }
1280
3577
 
3578
+ // src/advisories-api.ts
3579
+ var SEVERITY_SET = /* @__PURE__ */ new Set(["info", "warn", "critical"]);
3580
+ var STATUS_SET = /* @__PURE__ */ new Set(["open", "snoozed", "resolved"]);
3581
+ var RESOLUTION_SOURCE_SET = /* @__PURE__ */ new Set([
3582
+ "auto",
3583
+ "consumer-marked",
3584
+ "declined"
3585
+ ]);
3586
+ function asString2(v) {
3587
+ return typeof v === "string" && v.length > 0 ? v : void 0;
3588
+ }
3589
+ function asSeverity(v) {
3590
+ if (typeof v === "string" && SEVERITY_SET.has(v)) {
3591
+ return v;
3592
+ }
3593
+ return "info";
3594
+ }
3595
+ function asStatus(v) {
3596
+ if (typeof v === "string" && STATUS_SET.has(v)) {
3597
+ return v;
3598
+ }
3599
+ return "open";
3600
+ }
3601
+ function asResolutionSource(v) {
3602
+ if (typeof v === "string" && RESOLUTION_SOURCE_SET.has(v)) {
3603
+ return v;
3604
+ }
3605
+ return void 0;
3606
+ }
3607
+ function rowToAdvisory(row) {
3608
+ const archetype = asString2(row.applies_to_archetype);
3609
+ const model = asString2(row.applies_to_model);
3610
+ const docsLink = asString2(row.docs_url);
3611
+ const suggestion = asString2(row.suggestion);
3612
+ let suggestedFix = null;
3613
+ if (docsLink || suggestion) {
3614
+ suggestedFix = { type: "manual" };
3615
+ if (docsLink) suggestedFix.docsLink = docsLink;
3616
+ if (suggestion) suggestedFix.before = suggestion;
3617
+ }
3618
+ const out = {
3619
+ id: typeof row.id === "string" ? row.id : "",
3620
+ rule: typeof row.rule === "string" ? row.rule : "",
3621
+ severity: asSeverity(row.severity),
3622
+ openedAt: typeof row.opened_at === "string" ? row.opened_at : "",
3623
+ lastObservedAt: typeof row.last_observed_at === "string" ? row.last_observed_at : "",
3624
+ observationCount: typeof row.observation_count === "number" ? row.observation_count : 0,
3625
+ appliesTo: {
3626
+ ...archetype ? { archetype } : {},
3627
+ ...model ? { model } : {}
3628
+ },
3629
+ message: typeof row.message === "string" ? row.message : "",
3630
+ suggestedFix,
3631
+ autoApplicable: false,
3632
+ // reserved — alpha.30+
3633
+ status: asStatus(row.status)
3634
+ };
3635
+ const resolvedAt = asString2(row.resolved_at);
3636
+ if (resolvedAt) out.resolvedAt = resolvedAt;
3637
+ const resolutionSource = asResolutionSource(row.resolution_source);
3638
+ if (resolutionSource) out.resolutionSource = resolutionSource;
3639
+ const resolutionNote = asString2(row.resolution_note);
3640
+ if (resolutionNote) out.resolutionNote = resolutionNote;
3641
+ return out;
3642
+ }
3643
+ function resolveFetch(injected) {
3644
+ return injected ?? ((...args) => globalThis.fetch(...args));
3645
+ }
3646
+ function normalizeEndpoint2(endpoint) {
3647
+ return endpoint.replace(/\/+$/, "");
3648
+ }
3649
+ async function getActionableAdvisories(opts) {
3650
+ const {
3651
+ appId,
3652
+ severity,
3653
+ status,
3654
+ brainEndpoint,
3655
+ brainJwt,
3656
+ brainAnonKey,
3657
+ fetch: injectedFetch
3658
+ } = opts;
3659
+ if (!appId) {
3660
+ throw new Error("getActionableAdvisories: appId is required");
3661
+ }
3662
+ const doFetch = resolveFetch(injectedFetch);
3663
+ const base = normalizeEndpoint2(brainEndpoint);
3664
+ const qs = new URLSearchParams();
3665
+ qs.set("app_id", `eq.${appId}`);
3666
+ if (severity) qs.set("severity", `eq.${severity}`);
3667
+ const effectiveStatus = status ?? "open";
3668
+ if (effectiveStatus !== "all") {
3669
+ qs.set("status", `eq.${effectiveStatus}`);
3670
+ }
3671
+ qs.set("order", "last_observed_at.desc");
3672
+ const url = `${base}/rest/v1/actionable_advisories_v?${qs.toString()}`;
3673
+ let res;
3674
+ try {
3675
+ res = await doFetch(url, {
3676
+ method: "GET",
3677
+ headers: {
3678
+ Authorization: `Bearer ${brainJwt}`,
3679
+ apikey: brainAnonKey,
3680
+ Accept: "application/json"
3681
+ }
3682
+ });
3683
+ } catch (err) {
3684
+ const msg = err instanceof Error ? err.message : String(err);
3685
+ throw new Error(`getActionableAdvisories: network error: ${msg}`);
3686
+ }
3687
+ if (res.status === 401 || res.status === 403) {
3688
+ throw new Error("getActionableAdvisories: brain auth misconfig");
3689
+ }
3690
+ if (res.status >= 500) {
3691
+ throw new Error(`getActionableAdvisories: brain unavailable (${res.status})`);
3692
+ }
3693
+ if (!res.ok) {
3694
+ throw new Error(`getActionableAdvisories: bad request (${res.status})`);
3695
+ }
3696
+ let rows;
3697
+ try {
3698
+ rows = await res.json();
3699
+ } catch {
3700
+ throw new Error("getActionableAdvisories: malformed brain response");
3701
+ }
3702
+ if (!Array.isArray(rows)) {
3703
+ throw new Error("getActionableAdvisories: expected array from brain");
3704
+ }
3705
+ const out = [];
3706
+ for (const raw of rows) {
3707
+ if (raw && typeof raw === "object") {
3708
+ out.push(rowToAdvisory(raw));
3709
+ }
3710
+ }
3711
+ return out;
3712
+ }
3713
+ async function markAdvisoryResolved(opts) {
3714
+ const {
3715
+ id,
3716
+ resolutionNote,
3717
+ brainEndpoint,
3718
+ brainJwt,
3719
+ brainAnonKey,
3720
+ fetch: injectedFetch
3721
+ } = opts;
3722
+ if (!id) {
3723
+ return { ok: false, reason: "id_required" };
3724
+ }
3725
+ const doFetch = resolveFetch(injectedFetch);
3726
+ const base = normalizeEndpoint2(brainEndpoint);
3727
+ const lookupUrl = `${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=app_id,rule`;
3728
+ let lookupRes;
3729
+ try {
3730
+ lookupRes = await doFetch(lookupUrl, {
3731
+ method: "GET",
3732
+ headers: {
3733
+ Authorization: `Bearer ${brainJwt}`,
3734
+ apikey: brainAnonKey,
3735
+ Accept: "application/json"
3736
+ }
3737
+ });
3738
+ } catch (err) {
3739
+ const msg = err instanceof Error ? err.message : String(err);
3740
+ return { ok: false, reason: `network_error:${msg}` };
3741
+ }
3742
+ if (lookupRes.status === 401 || lookupRes.status === 403) {
3743
+ return { ok: false, reason: "brain_auth_misconfig" };
3744
+ }
3745
+ if (lookupRes.status >= 500) {
3746
+ return { ok: false, reason: "brain_unavailable" };
3747
+ }
3748
+ if (!lookupRes.ok) {
3749
+ return { ok: false, reason: `brain_lookup_failed:${lookupRes.status}` };
3750
+ }
3751
+ let lookupRows;
3752
+ try {
3753
+ lookupRows = await lookupRes.json();
3754
+ } catch {
3755
+ return { ok: false, reason: "brain_lookup_malformed" };
3756
+ }
3757
+ if (!Array.isArray(lookupRows) || lookupRows.length === 0) {
3758
+ return { ok: false, reason: "advisory_not_found" };
3759
+ }
3760
+ const tuple = lookupRows[0];
3761
+ const appId = typeof tuple.app_id === "string" ? tuple.app_id : "";
3762
+ const code = typeof tuple.rule === "string" ? tuple.rule : "";
3763
+ if (!appId || !code) {
3764
+ return { ok: false, reason: "advisory_tuple_invalid" };
3765
+ }
3766
+ const outcomesUrl = `${base}/rest/v1/compile_outcomes?app_id=eq.${encodeURIComponent(appId)}&select=id`;
3767
+ let outcomesRes;
3768
+ try {
3769
+ outcomesRes = await doFetch(outcomesUrl, {
3770
+ method: "GET",
3771
+ headers: {
3772
+ Authorization: `Bearer ${brainJwt}`,
3773
+ apikey: brainAnonKey,
3774
+ Accept: "application/json"
3775
+ }
3776
+ });
3777
+ } catch (err) {
3778
+ const msg = err instanceof Error ? err.message : String(err);
3779
+ return { ok: false, reason: `network_error:${msg}` };
3780
+ }
3781
+ if (outcomesRes.status === 401 || outcomesRes.status === 403) {
3782
+ return { ok: false, reason: "brain_auth_misconfig" };
3783
+ }
3784
+ if (outcomesRes.status >= 500) {
3785
+ return { ok: false, reason: "brain_unavailable" };
3786
+ }
3787
+ if (!outcomesRes.ok) {
3788
+ return { ok: false, reason: `brain_lookup_failed:${outcomesRes.status}` };
3789
+ }
3790
+ let outcomeRows;
3791
+ try {
3792
+ outcomeRows = await outcomesRes.json();
3793
+ } catch {
3794
+ return { ok: false, reason: "brain_lookup_malformed" };
3795
+ }
3796
+ if (!Array.isArray(outcomeRows)) {
3797
+ return { ok: false, reason: "brain_lookup_malformed" };
3798
+ }
3799
+ const outcomeIds = [];
3800
+ for (const row of outcomeRows) {
3801
+ if (row && typeof row === "object") {
3802
+ const idVal = row.id;
3803
+ if (typeof idVal === "number" && Number.isFinite(idVal)) {
3804
+ outcomeIds.push(idVal);
3805
+ }
3806
+ }
3807
+ }
3808
+ if (outcomeIds.length === 0) {
3809
+ return { ok: true };
3810
+ }
3811
+ const inList = outcomeIds.join(",");
3812
+ const patchUrl = `${base}/rest/v1/compile_outcome_advisories?outcome_id=in.(${inList})&code=eq.${encodeURIComponent(code)}&resolved_at=is.null`;
3813
+ const patchBody = {
3814
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
3815
+ resolution_source: "consumer-marked"
3816
+ };
3817
+ if (resolutionNote !== void 0) {
3818
+ patchBody.resolution_note = resolutionNote;
3819
+ }
3820
+ let patchRes;
3821
+ try {
3822
+ patchRes = await doFetch(patchUrl, {
3823
+ method: "PATCH",
3824
+ headers: {
3825
+ Authorization: `Bearer ${brainJwt}`,
3826
+ apikey: brainAnonKey,
3827
+ "Content-Type": "application/json",
3828
+ Accept: "application/json",
3829
+ // PostgREST default is no return; we don't need the row back.
3830
+ Prefer: "return=minimal"
3831
+ },
3832
+ body: JSON.stringify(patchBody)
3833
+ });
3834
+ } catch (err) {
3835
+ const msg = err instanceof Error ? err.message : String(err);
3836
+ return { ok: false, reason: `network_error:${msg}` };
3837
+ }
3838
+ if (patchRes.status === 401 || patchRes.status === 403) {
3839
+ return { ok: false, reason: "brain_auth_misconfig" };
3840
+ }
3841
+ if (patchRes.status >= 500) {
3842
+ return { ok: false, reason: "brain_unavailable" };
3843
+ }
3844
+ if (!patchRes.ok) {
3845
+ return { ok: false, reason: `patch_failed:${patchRes.status}` };
3846
+ }
3847
+ return { ok: true };
3848
+ }
3849
+ async function markExclusionFindingHandled(opts) {
3850
+ const {
3851
+ appId,
3852
+ archetype,
3853
+ excludedModel,
3854
+ resolution,
3855
+ resolutionNote,
3856
+ brainEndpoint,
3857
+ brainJwt,
3858
+ brainAnonKey,
3859
+ fetch: injectedFetch
3860
+ } = opts;
3861
+ if (!appId) return { ok: false, reason: "app_id_required" };
3862
+ if (!archetype) return { ok: false, reason: "archetype_required" };
3863
+ if (!excludedModel) {
3864
+ return { ok: false, reason: "excluded_model_required" };
3865
+ }
3866
+ if (resolution !== "consumer-marked" && resolution !== "declined" && resolution !== "probed-unblock" && resolution !== "probed-stay-excluded") {
3867
+ return { ok: false, reason: "resolution_invalid" };
3868
+ }
3869
+ const doFetch = resolveFetch(injectedFetch);
3870
+ const base = normalizeEndpoint2(brainEndpoint);
3871
+ const url = `${base}/rest/v1/exclusion_findings?app_id=eq.${encodeURIComponent(appId)}&intent_archetype=eq.${encodeURIComponent(archetype)}&excluded_model=eq.${encodeURIComponent(excludedModel)}&resolved_at=is.null`;
3872
+ const patchBody = {
3873
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
3874
+ resolution_source: resolution
3875
+ };
3876
+ if (resolutionNote !== void 0) {
3877
+ patchBody.resolution_note = resolutionNote;
3878
+ }
3879
+ let res;
3880
+ try {
3881
+ res = await doFetch(url, {
3882
+ method: "PATCH",
3883
+ headers: {
3884
+ Authorization: `Bearer ${brainJwt}`,
3885
+ apikey: brainAnonKey,
3886
+ "Content-Type": "application/json",
3887
+ Accept: "application/json",
3888
+ // PATCH may match zero rows when already resolved — return=minimal
3889
+ // keeps the response body empty so the success path is uniform.
3890
+ Prefer: "return=minimal"
3891
+ },
3892
+ body: JSON.stringify(patchBody)
3893
+ });
3894
+ } catch (err) {
3895
+ const msg = err instanceof Error ? err.message : String(err);
3896
+ return { ok: false, reason: `network_error:${msg}` };
3897
+ }
3898
+ if (res.status === 401 || res.status === 403) {
3899
+ return { ok: false, reason: "brain_auth_misconfig" };
3900
+ }
3901
+ if (res.status >= 500) {
3902
+ return { ok: false, reason: "brain_unavailable" };
3903
+ }
3904
+ if (!res.ok) {
3905
+ return { ok: false, reason: `patch_failed:${res.status}` };
3906
+ }
3907
+ return { ok: true };
3908
+ }
3909
+
1281
3910
  // src/index.ts
1282
3911
  function compile2(ir, opts) {
1283
3912
  const result = compile(ir, opts);
@@ -1285,12 +3914,23 @@ function compile2(ir, opts) {
1285
3914
  return result;
1286
3915
  }
1287
3916
  export {
3917
+ ABSOLUTE_FLOOR,
1288
3918
  ALIASES,
1289
3919
  ALL_ARCHETYPES,
3920
+ ARCHETYPE_FLOOR_DEFAULT,
1290
3921
  CallError,
3922
+ DEFAULT_FINDINGS_ENDPOINT,
1291
3923
  DIALECT_VERSION,
3924
+ FamilyResolutionError,
1292
3925
  INTENT_ARCHETYPES,
3926
+ MEASURED_GROUNDING_MIN_N,
3927
+ PRODUCER_OWNED_RULE_CODES,
3928
+ PROVIDER_ENV_KEYS,
3929
+ RULE_SEQUENTIAL_TOOL_CLIFF,
3930
+ TRANSLATOR_FLOOR,
1293
3931
  allProfiles,
3932
+ applySectionRewrites,
3933
+ attachCacheControlToStreamTextInput,
1294
3934
  bucketContext,
1295
3935
  bucketHistory,
1296
3936
  bucketToolCount,
@@ -1300,14 +3940,50 @@ export {
1300
3940
  compile2 as compile,
1301
3941
  configureBrain,
1302
3942
  countTokens,
3943
+ deriveFamilyFromModelId,
3944
+ deriveOwnership,
1303
3945
  execute,
3946
+ getActionableAdvisories,
3947
+ getAllStarterChains,
3948
+ getAllStarterChainsWithGrounding,
3949
+ getArchetypePerfScore,
3950
+ getDefaultFallbackChain,
3951
+ getDefaultFallbackChainWithGrounding,
3952
+ getModelCompatibility,
3953
+ getPerAxisMetrics,
1304
3954
  getProfile,
3955
+ getReachabilityDiagnostic,
3956
+ getRecommendedPrimary,
3957
+ getSequentialStarterChain,
3958
+ getSequentialStarterChainWithGrounding,
3959
+ getStaleExclusionFindings,
3960
+ getStarterChain,
3961
+ getStarterChainWithGrounding,
1305
3962
  hashShape,
1306
3963
  isArchetype,
3964
+ isBrainQueryActiveFor,
3965
+ isExclusionFindingsBrainActive,
3966
+ isModelReachable,
3967
+ isProviderReachable,
1307
3968
  learningKey,
3969
+ loadAliasesFromBrain,
3970
+ loadArchetypePerfFromBrain,
3971
+ loadArchetypePerfNFromBrain,
3972
+ loadChainsFromBrain,
3973
+ loadModelsFromBrain,
3974
+ loadPricingFromBrain,
3975
+ markAdvisoryResolved,
3976
+ markExclusionFindingHandled,
3977
+ markPromoteReadyHandled,
3978
+ profileToRow,
1308
3979
  profilesByProvider,
3980
+ readBrainReadEnv,
1309
3981
  record,
3982
+ recordOutcome,
1310
3983
  resetTokenizer,
3984
+ resolvePricingAt,
3985
+ resolveProviderKey,
3986
+ runAdvisor,
1311
3987
  setTokenizer,
1312
3988
  tryGetProfile
1313
3989
  };