@warmdrift/kgauto-compiler 2.0.0-alpha.6 → 2.0.0-alpha.61

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 (50) hide show
  1. package/README.md +158 -46
  2. package/dist/brain-proxy.d.mts +113 -0
  3. package/dist/brain-proxy.d.ts +113 -0
  4. package/dist/brain-proxy.js +189 -0
  5. package/dist/brain-proxy.mjs +6 -0
  6. package/dist/chunk-3RMLZCUK.mjs +190 -0
  7. package/dist/chunk-IIMPJZNH.mjs +694 -0
  8. package/dist/chunk-IUWFML6Z.mjs +165 -0
  9. package/dist/chunk-NBO4R5PC.mjs +313 -0
  10. package/dist/chunk-P3TOAEG4.mjs +56 -0
  11. package/dist/chunk-QKXTMVCT.mjs +1470 -0
  12. package/dist/chunk-RO22VFIF.mjs +29 -0
  13. package/dist/glassbox/index.d.mts +59 -0
  14. package/dist/glassbox/index.d.ts +59 -0
  15. package/dist/glassbox/index.js +312 -0
  16. package/dist/glassbox/index.mjs +12 -0
  17. package/dist/glassbox-routes/format.d.mts +24 -0
  18. package/dist/glassbox-routes/format.d.ts +24 -0
  19. package/dist/glassbox-routes/format.js +86 -0
  20. package/dist/glassbox-routes/format.mjs +18 -0
  21. package/dist/glassbox-routes/index.d.mts +191 -0
  22. package/dist/glassbox-routes/index.d.ts +191 -0
  23. package/dist/glassbox-routes/index.js +2714 -0
  24. package/dist/glassbox-routes/index.mjs +663 -0
  25. package/dist/glassbox-routes/react/index.d.mts +74 -0
  26. package/dist/glassbox-routes/react/index.d.ts +74 -0
  27. package/dist/glassbox-routes/react/index.js +819 -0
  28. package/dist/glassbox-routes/react/index.mjs +754 -0
  29. package/dist/index.d.mts +2236 -17
  30. package/dist/index.d.ts +2236 -17
  31. package/dist/index.js +7402 -1596
  32. package/dist/index.mjs +3580 -164
  33. package/dist/ir-DAKlQsVb.d.mts +1386 -0
  34. package/dist/ir-DmUuJsWc.d.ts +1386 -0
  35. package/dist/key-health.d.mts +131 -0
  36. package/dist/key-health.d.ts +131 -0
  37. package/dist/key-health.js +215 -0
  38. package/dist/key-health.mjs +6 -0
  39. package/dist/profiles.d.mts +292 -2
  40. package/dist/profiles.d.ts +292 -2
  41. package/dist/profiles.js +1081 -16
  42. package/dist/profiles.mjs +9 -1
  43. package/dist/types-B8X1Pyhx.d.ts +149 -0
  44. package/dist/types-CssWqd0X.d.mts +131 -0
  45. package/dist/types-DR62iPcO.d.ts +131 -0
  46. package/dist/types-MRMBUqzY.d.mts +149 -0
  47. package/package.json +54 -8
  48. package/dist/chunk-MBEI5UOM.mjs +0 -409
  49. package/dist/profiles-CQnLkQ7b.d.ts +0 -611
  50. package/dist/profiles-zm6diETo.d.mts +0 -611
package/dist/index.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ import {
2
+ createBrainForwardRoutes
3
+ } from "./chunk-IUWFML6Z.mjs";
1
4
  import {
2
5
  ALL_ARCHETYPES,
3
6
  DIALECT_VERSION,
@@ -9,13 +12,445 @@ import {
9
12
  isArchetype,
10
13
  learningKey
11
14
  } from "./chunk-5TI6PNSK.mjs";
15
+ import {
16
+ LIBRARY_VERSION,
17
+ createKeyHealthRoute
18
+ } from "./chunk-3RMLZCUK.mjs";
19
+ import {
20
+ ABSOLUTE_FLOOR,
21
+ ARCHETYPE_FLOOR_DEFAULT,
22
+ PROVIDER_ENV_KEYS,
23
+ configureBrainQuery,
24
+ createBrainQueryCache,
25
+ ensureCrossProviderTail,
26
+ getAllStarterChains,
27
+ getAllStarterChainsWithGrounding,
28
+ getDefaultFallbackChain,
29
+ getDefaultFallbackChainWithGrounding,
30
+ getModelCompatibility,
31
+ getPerAxisMetrics,
32
+ getReachabilityDiagnostic,
33
+ getSequentialStarterChain,
34
+ getSequentialStarterChainWithGrounding,
35
+ getStarterChain,
36
+ getStarterChainWithGrounding,
37
+ isBrainQueryActiveFor,
38
+ isModelReachable,
39
+ isProviderReachable,
40
+ loadChainsFromBrain,
41
+ readBrainReadEnv,
42
+ resolveProviderKey
43
+ } from "./chunk-IIMPJZNH.mjs";
12
44
  import {
13
45
  ALIASES,
46
+ LATENCY_TIER_MS,
47
+ _setProfileBrainHook,
14
48
  allProfiles,
49
+ allProfilesRaw,
15
50
  getProfile,
51
+ latencyTierOf,
16
52
  profilesByProvider,
17
53
  tryGetProfile
18
- } from "./chunk-MBEI5UOM.mjs";
54
+ } from "./chunk-QKXTMVCT.mjs";
55
+ import {
56
+ emitAdvisoryFired,
57
+ emitCompileDone,
58
+ emitCompileStart,
59
+ emitExecuteAttempt,
60
+ emitExecuteSuccess,
61
+ emitFallbackWalked
62
+ } from "./chunk-NBO4R5PC.mjs";
63
+
64
+ // src/models-brain.ts
65
+ function isModelRow(x) {
66
+ if (!x || typeof x !== "object") return false;
67
+ const r = x;
68
+ return typeof r.model_id === "string" && typeof r.provider === "string";
69
+ }
70
+ function isAliasRow(x) {
71
+ if (!x || typeof x !== "object") return false;
72
+ const r = x;
73
+ return typeof r.alias_id === "string" && typeof r.canonical_id === "string";
74
+ }
75
+ function rowToProfile(row) {
76
+ try {
77
+ if (row.cliffs !== void 0 && row.cliffs !== null && !Array.isArray(row.cliffs)) {
78
+ return null;
79
+ }
80
+ if (row.recovery !== void 0 && row.recovery !== null && !Array.isArray(row.recovery)) {
81
+ return null;
82
+ }
83
+ if (row.lowering !== void 0 && row.lowering !== null && (typeof row.lowering !== "object" || Array.isArray(row.lowering))) {
84
+ return null;
85
+ }
86
+ if (row.archetype_conventions !== void 0 && row.archetype_conventions !== null && !Array.isArray(row.archetype_conventions)) {
87
+ return null;
88
+ }
89
+ return {
90
+ id: row.model_id,
91
+ provider: row.provider,
92
+ status: row.status ?? "current",
93
+ maxContextTokens: row.max_context_tokens ?? 0,
94
+ maxOutputTokens: row.max_output_tokens ?? 0,
95
+ maxTools: row.max_tools ?? 0,
96
+ parallelToolCalls: row.parallel_tool_calls ?? false,
97
+ structuredOutput: row.structured_output ?? "none",
98
+ systemPromptMode: row.system_prompt_mode ?? "inline",
99
+ streaming: row.streaming ?? true,
100
+ cliffs: row.cliffs ?? [],
101
+ costInputPer1m: row.cost_input_per_1m ?? 0,
102
+ costOutputPer1m: row.cost_output_per_1m ?? 0,
103
+ lowering: row.lowering ?? { system: { mode: "inline" }, cache: { strategy: "unsupported" } },
104
+ recovery: row.recovery ?? [],
105
+ strengths: row.strengths ?? [],
106
+ weaknesses: row.weaknesses ?? [],
107
+ notes: row.notes ?? void 0,
108
+ verifiedAgainstDocs: row.verified_against_docs ?? void 0,
109
+ archetypePerf: row.archetype_perf ?? void 0,
110
+ // alpha.41 — family-resolution fields. `family` may be null pre-
111
+ // migration 024; runtime falls back to deriveFamilyFromModelId at
112
+ // resolution time (see family-resolution.ts, G1).
113
+ family: row.family ?? void 0,
114
+ versionAdded: row.version_added ?? void 0,
115
+ active: row.active ?? void 0,
116
+ // alpha.49 — executable-knowledge fields (migration 028). Invalid
117
+ // latency_tier → undefined (latencyTierOf derives from tags; not
118
+ // safety-critical). archetype_conventions already array-validated above.
119
+ latencyTier: normalizeLatencyTier(row.latency_tier),
120
+ archetypeConventions: row.archetype_conventions ?? void 0
121
+ };
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+ var VALID_LATENCY_TIERS = /* @__PURE__ */ new Set(["fast", "medium", "slow"]);
127
+ function normalizeLatencyTier(v) {
128
+ return typeof v === "string" && VALID_LATENCY_TIERS.has(v) ? v : void 0;
129
+ }
130
+ function profileToRow(profile, opts = {}) {
131
+ const row = {
132
+ model_id: profile.id,
133
+ provider: profile.provider,
134
+ status: profile.status,
135
+ max_context_tokens: profile.maxContextTokens,
136
+ max_output_tokens: profile.maxOutputTokens,
137
+ max_tools: profile.maxTools,
138
+ parallel_tool_calls: profile.parallelToolCalls,
139
+ structured_output: profile.structuredOutput,
140
+ system_prompt_mode: profile.systemPromptMode,
141
+ streaming: profile.streaming,
142
+ cliffs: profile.cliffs,
143
+ cost_input_per_1m: profile.costInputPer1m,
144
+ cost_output_per_1m: profile.costOutputPer1m,
145
+ lowering: profile.lowering,
146
+ recovery: profile.recovery,
147
+ strengths: profile.strengths,
148
+ weaknesses: profile.weaknesses,
149
+ notes: profile.notes ?? null,
150
+ archetype_perf: profile.archetypePerf ?? null,
151
+ active: opts.active ?? profile.active ?? true,
152
+ // alpha.41 — round-trip family + version_added when present on profile.
153
+ // version_added is operator-controlled via opts; profile-side value is
154
+ // used only when opts didn't override.
155
+ family: profile.family ?? null,
156
+ // alpha.49 — round-trip the executable-knowledge fields (migration 028)
157
+ // so a reseed from bundled profiles makes the latency lever + schema
158
+ // conventions live warm. Closes the silently-dropped-field gap.
159
+ latency_tier: profile.latencyTier ?? null,
160
+ archetype_conventions: profile.archetypeConventions ?? null
161
+ };
162
+ if (opts.verifiedAgainstDocs !== void 0) {
163
+ row.verified_against_docs = opts.verifiedAgainstDocs;
164
+ } else if (profile.verifiedAgainstDocs !== void 0) {
165
+ const v = profile.verifiedAgainstDocs;
166
+ row.verified_against_docs = /^\d{4}-\d{2}-\d{2}/.test(v) ? v : null;
167
+ }
168
+ if (opts.versionAdded !== void 0) row.version_added = opts.versionAdded;
169
+ else if (profile.versionAdded !== void 0) row.version_added = profile.versionAdded;
170
+ if (opts.versionRemoved !== void 0) row.version_removed = opts.versionRemoved;
171
+ return row;
172
+ }
173
+ function mapRowsToModels(rows) {
174
+ const out = /* @__PURE__ */ new Map();
175
+ for (const row of rows) {
176
+ if (!isModelRow(row)) continue;
177
+ const profile = rowToProfile(row);
178
+ if (profile) out.set(profile.id, profile);
179
+ }
180
+ return out;
181
+ }
182
+ function mapRowsToAliases(rows) {
183
+ const out = {};
184
+ for (const row of rows) {
185
+ if (!isAliasRow(row)) continue;
186
+ out[row.alias_id] = row.canonical_id;
187
+ }
188
+ return out;
189
+ }
190
+ function bundledModels() {
191
+ return new Map(allProfilesRaw().map((p) => [p.id, p]));
192
+ }
193
+ function bundledAliases() {
194
+ return { ...ALIASES };
195
+ }
196
+ var loadModelsFromBrain = createBrainQueryCache({
197
+ table: "kgauto_models",
198
+ mapRows: mapRowsToModels,
199
+ bundledFallback: bundledModels
200
+ });
201
+ var loadAliasesFromBrain = createBrainQueryCache({
202
+ table: "kgauto_aliases",
203
+ mapRows: mapRowsToAliases,
204
+ bundledFallback: bundledAliases
205
+ });
206
+ _setProfileBrainHook({
207
+ getProfile: (canonical) => loadModelsFromBrain().get(canonical),
208
+ resolveAlias: (id) => loadAliasesFromBrain()[id]
209
+ });
210
+
211
+ // src/exclusion-findings-brain.ts
212
+ function isValidVerdict(v) {
213
+ return v === "recommend-probe" || v === "unblock" || v === "stay-excluded" || v === "inconclusive";
214
+ }
215
+ function isValidConfidence(v) {
216
+ return v === "high" || v === "medium" || v === "low";
217
+ }
218
+ function isRawFindingRow(x) {
219
+ if (!x || typeof x !== "object") return false;
220
+ const r = x;
221
+ 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";
222
+ }
223
+ function mapRowsToFindings(rows) {
224
+ const out = [];
225
+ for (const row of rows) {
226
+ if (!isRawFindingRow(row)) continue;
227
+ if (!isValidVerdict(row.verdict)) continue;
228
+ if (!isValidConfidence(row.confidence)) continue;
229
+ let savings = null;
230
+ if (typeof row.estimated_savings_usd_30d === "number") {
231
+ savings = Number.isFinite(row.estimated_savings_usd_30d) ? row.estimated_savings_usd_30d : null;
232
+ } else if (typeof row.estimated_savings_usd_30d === "string") {
233
+ const n = Number(row.estimated_savings_usd_30d);
234
+ savings = Number.isFinite(n) ? n : null;
235
+ }
236
+ out.push({
237
+ archetype: row.intent_archetype,
238
+ excludedModel: row.excluded_model,
239
+ excludedProvider: row.excluded_provider,
240
+ verdict: row.verdict,
241
+ estimatedSavingsUsd30d: savings,
242
+ message: row.message,
243
+ suggestion: row.suggestion,
244
+ confidence: row.confidence,
245
+ evidence: row.evidence && typeof row.evidence === "object" ? row.evidence : void 0
246
+ });
247
+ }
248
+ return out;
249
+ }
250
+ var snapshots = /* @__PURE__ */ new Map();
251
+ var runtime;
252
+ var warnedOnce = false;
253
+ var DEFAULT_FINDINGS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/findings/exclusions";
254
+ function configureExclusionFindingsBrain(rt) {
255
+ runtime = rt;
256
+ snapshots.clear();
257
+ warnedOnce = false;
258
+ }
259
+ function isExclusionFindingsBrainActive() {
260
+ return runtime !== void 0;
261
+ }
262
+ function getStaleExclusionFindings(opts) {
263
+ const rt = runtime;
264
+ if (!rt) return [];
265
+ const appId = opts.appId;
266
+ if (!appId) return [];
267
+ let snap = snapshots.get(appId);
268
+ if (!snap) {
269
+ snap = { data: [], expiresAt: 0, refreshing: false };
270
+ snapshots.set(appId, snap);
271
+ }
272
+ const now = Date.now();
273
+ const stale = snap.expiresAt <= now;
274
+ if (stale && !snap.refreshing) {
275
+ snap.refreshing = true;
276
+ void asyncRefresh(rt, appId);
277
+ }
278
+ if (opts.archetype) {
279
+ return snap.data.filter((f) => f.archetype === opts.archetype);
280
+ }
281
+ return snap.data;
282
+ }
283
+ var pendingRefreshes = /* @__PURE__ */ new Map();
284
+ async function asyncRefresh(rt, appId) {
285
+ const promise = doRefresh(rt, appId);
286
+ pendingRefreshes.set(appId, promise);
287
+ try {
288
+ await promise;
289
+ } finally {
290
+ if (pendingRefreshes.get(appId) === promise) {
291
+ pendingRefreshes.delete(appId);
292
+ }
293
+ }
294
+ }
295
+ async function doRefresh(rt, appId) {
296
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
297
+ let snap = snapshots.get(appId);
298
+ if (!snap) {
299
+ snap = { data: [], expiresAt: 0, refreshing: false };
300
+ snapshots.set(appId, snap);
301
+ }
302
+ try {
303
+ const res = await rt.fetchImpl(url, { method: "GET" });
304
+ if (!res.ok) {
305
+ throw new Error(`findings ${res.status}: ${res.statusText}`);
306
+ }
307
+ const body = await res.json();
308
+ if (runtime !== rt) return;
309
+ const rows = Array.isArray(body) ? mapRowsToFindings(body) : [];
310
+ snap.data = rows;
311
+ snap.expiresAt = Date.now() + rt.ttlMs;
312
+ snap.refreshing = false;
313
+ } catch (err) {
314
+ if (runtime !== rt) return;
315
+ snap.refreshing = false;
316
+ snap.expiresAt = Date.now() + rt.ttlMs;
317
+ if (!warnedOnce) {
318
+ warnedOnce = true;
319
+ (rt.onError ?? defaultOnError)(err);
320
+ }
321
+ }
322
+ }
323
+ function defaultOnError(err) {
324
+ console.warn(
325
+ "[kgauto] exclusion-findings fetch failed (using empty fallback):",
326
+ err
327
+ );
328
+ }
329
+
330
+ // src/family-resolution.ts
331
+ var FamilyResolutionError = class extends Error {
332
+ family;
333
+ cause;
334
+ constructor(family, cause) {
335
+ super(
336
+ `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.`
337
+ );
338
+ this.name = "FamilyResolutionError";
339
+ this.family = family;
340
+ this.cause = cause;
341
+ }
342
+ };
343
+ function deriveFamilyFromModelId(modelId) {
344
+ if (typeof modelId !== "string" || modelId.length === 0) return null;
345
+ if (modelId.startsWith("claude-opus-")) return "claude-opus";
346
+ if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
347
+ if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
348
+ if (modelId.startsWith("gemini-") && modelId.includes("flash-lite")) {
349
+ return "gemini-flash-lite";
350
+ }
351
+ if (modelId.startsWith("gemini-") && modelId.includes("flash")) {
352
+ return "gemini-flash";
353
+ }
354
+ if (modelId.startsWith("gemini-") && modelId.includes("pro")) {
355
+ return "gemini-pro";
356
+ }
357
+ if (modelId.startsWith("deepseek-") && modelId.includes("pro")) {
358
+ return "deepseek-reasoner";
359
+ }
360
+ if (modelId.startsWith("deepseek-")) return "deepseek-chat";
361
+ if (modelId.startsWith("gpt-")) return "openai-gpt";
362
+ return null;
363
+ }
364
+ function familyOf(profile) {
365
+ return profile.family ?? deriveFamilyFromModelId(profile.id);
366
+ }
367
+ function archetypePerfFor(profile, archetype) {
368
+ if (!archetype) return 0;
369
+ const score = profile.archetypePerf?.[archetype];
370
+ return typeof score === "number" ? score : 5;
371
+ }
372
+ function selectCandidates(registry, family, archetype, appId) {
373
+ const out = [];
374
+ const excluded = /* @__PURE__ */ new Set();
375
+ if (appId && archetype) {
376
+ const findings = getStaleExclusionFindings({ appId, archetype });
377
+ for (const f of findings) {
378
+ if (f.verdict === "stay-excluded") excluded.add(f.excludedModel);
379
+ }
380
+ }
381
+ for (const profile of registry.values()) {
382
+ if (familyOf(profile) !== family) continue;
383
+ if (profile.status !== "current") continue;
384
+ if (profile.active === false) continue;
385
+ if (archetype) {
386
+ if (archetypePerfFor(profile, archetype) < ARCHETYPE_FLOOR_DEFAULT) continue;
387
+ }
388
+ if (excluded.has(profile.id)) continue;
389
+ out.push(profile);
390
+ }
391
+ return out;
392
+ }
393
+ function sortCandidates(candidates, archetype) {
394
+ const sorted = [...candidates];
395
+ sorted.sort((a, b) => {
396
+ const perfA = archetypePerfFor(a, archetype);
397
+ const perfB = archetypePerfFor(b, archetype);
398
+ if (perfA !== perfB) return perfB - perfA;
399
+ const vA = a.versionAdded ?? "";
400
+ const vB = b.versionAdded ?? "";
401
+ if (vA !== vB) return vA < vB ? 1 : -1;
402
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
403
+ });
404
+ return sorted;
405
+ }
406
+ function getRecommendedPrimary(opts) {
407
+ if (opts.posture === "locked") return opts.fallback;
408
+ const registry = loadModelsFromBrain();
409
+ const candidates = selectCandidates(
410
+ registry,
411
+ opts.family,
412
+ opts.archetype,
413
+ opts.appId
414
+ );
415
+ if (candidates.length === 0) return opts.fallback;
416
+ const sorted = sortCandidates(candidates, opts.archetype);
417
+ return sorted[0]?.id ?? opts.fallback;
418
+ }
419
+ function resolveFamilyEntry(family, ctx) {
420
+ if (typeof family !== "string" || family.length === 0) {
421
+ throw new FamilyResolutionError(
422
+ String(family),
423
+ "Family tag must be a non-empty string"
424
+ );
425
+ }
426
+ const registry = loadModelsFromBrain();
427
+ const candidates = selectCandidates(
428
+ registry,
429
+ family,
430
+ ctx.archetype,
431
+ ctx.appId
432
+ );
433
+ if (candidates.length === 0) {
434
+ let anyInFamily = false;
435
+ for (const profile of registry.values()) {
436
+ if (familyOf(profile) === family) {
437
+ anyInFamily = true;
438
+ break;
439
+ }
440
+ }
441
+ 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`;
442
+ throw new FamilyResolutionError(family, cause);
443
+ }
444
+ const sorted = sortCandidates(candidates, ctx.archetype);
445
+ const winner = sorted[0];
446
+ if (!winner) {
447
+ throw new FamilyResolutionError(
448
+ family,
449
+ "Candidates non-empty but sort returned undefined \u2014 internal invariant violation"
450
+ );
451
+ }
452
+ return winner.id;
453
+ }
19
454
 
20
455
  // src/tokenizer.ts
21
456
  var tokenizerImpl = defaultCharBasedCounter;
@@ -120,38 +555,96 @@ function passToolRelevance(ir, opts = {}) {
120
555
  ]
121
556
  };
122
557
  }
558
+ function totalHistoryTokens(history) {
559
+ let total = 0;
560
+ for (const m of history) {
561
+ if (typeof m.content === "string") total += countTokens(m.content);
562
+ }
563
+ return total;
564
+ }
123
565
  function passCompressHistory(ir, opts = {}) {
124
566
  const history = ir.history;
125
- if (!history || history.length === 0) return { value: ir, mutations: [] };
567
+ if (!history || history.length === 0) {
568
+ return { value: ir, mutations: [], historyTokensTotal: 0 };
569
+ }
126
570
  const keepRecent = opts.keepRecent ?? 4;
127
571
  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)`
572
+ const summarizeAboveTokens = opts.summarizeAboveTokens;
573
+ const historyTokensTotal = totalHistoryTokens(history);
574
+ const countThresholdHit = history.length > summarizeOlderThan;
575
+ const tokenThresholdHit = summarizeAboveTokens !== void 0 && historyTokensTotal > summarizeAboveTokens;
576
+ if (!countThresholdHit && !tokenThresholdHit) {
577
+ return { value: ir, mutations: [], historyTokensTotal };
578
+ }
579
+ if (history.length > keepRecent) {
580
+ const cutIndex = history.length - keepRecent;
581
+ const old = history.slice(0, cutIndex);
582
+ const recent = history.slice(cutIndex);
583
+ const userTurns = old.filter((m) => m.role === "user");
584
+ const firstUserLine = userTurns[0]?.content.split("\n")[0]?.slice(0, 200) ?? "";
585
+ const oldTokens = totalHistoryTokens(old);
586
+ const trigger = tokenThresholdHit && !countThresholdHit ? "tokens" : "count";
587
+ const summary = {
588
+ role: "system",
589
+ content: `[Earlier conversation: ${old.length} turns omitted (~${oldTokens} tokens). First user message: "${firstUserLine}"]`
590
+ };
591
+ return {
592
+ value: { ...ir, history: [summary, ...recent] },
593
+ mutations: [
594
+ {
595
+ id: `compress-history-${old.length}`,
596
+ source: "static_pass",
597
+ passName: "compress_history",
598
+ 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)`
599
+ }
600
+ ],
601
+ historyTokensTotal
602
+ };
603
+ }
604
+ if (tokenThresholdHit) {
605
+ let fattestIdx = -1;
606
+ let fattestTokens = 0;
607
+ for (let i = 0; i < history.length; i++) {
608
+ const m = history[i];
609
+ if (!m || typeof m.content !== "string") continue;
610
+ const t = countTokens(m.content);
611
+ if (t > fattestTokens) {
612
+ fattestTokens = t;
613
+ fattestIdx = i;
146
614
  }
147
- ]
148
- };
615
+ }
616
+ const FAT_DOMINANCE_FLOOR = 0.3;
617
+ const fattest = fattestIdx >= 0 ? history[fattestIdx] : void 0;
618
+ if (fattest && historyTokensTotal > 0 && fattestTokens / historyTokensTotal >= FAT_DOMINANCE_FLOOR) {
619
+ const firstLine = fattest.content.split("\n")[0]?.slice(0, 200) ?? "";
620
+ const newContent = `[Earlier ${fattest.role} message content omitted: ~${fattestTokens} tokens. Preview: "${firstLine}"]`;
621
+ const newHistory = history.slice();
622
+ newHistory[fattestIdx] = { ...fattest, content: newContent };
623
+ return {
624
+ value: { ...ir, history: newHistory },
625
+ mutations: [
626
+ {
627
+ id: `compress-fat-message-${fattestIdx}`,
628
+ source: "static_pass",
629
+ passName: "compress_history",
630
+ 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)`
631
+ }
632
+ ],
633
+ historyTokensTotal
634
+ };
635
+ }
636
+ }
637
+ return { value: ir, mutations: [], historyTokensTotal };
149
638
  }
150
639
  function passApplyCliffs(ir, profile, estimatedInputTokens) {
151
640
  const mutations = [];
152
641
  const hints = { qualityWarning: [] };
153
642
  let nextIR = ir;
643
+ const sequentialMode = nextIR.constraints?.toolOrchestration === "sequential";
154
644
  for (const cliff of profile.cliffs) {
645
+ if (sequentialMode && cliff.reason.includes("L-040")) {
646
+ continue;
647
+ }
155
648
  let triggered = false;
156
649
  switch (cliff.metric) {
157
650
  case "input_tokens":
@@ -243,6 +736,19 @@ function passApplyCliffs(ir, profile, estimatedInputTokens) {
243
736
  }
244
737
  return { value: { ir: nextIR, loweringHints: hints }, mutations };
245
738
  }
739
+ var LATENCY_OVERAGE_WEIGHT = 0.6;
740
+ var LATENCY_PENALTY_CAP = 1;
741
+ var QUALITY_GATE_PENALTY = 4;
742
+ function effectiveConventions(profile) {
743
+ const own = profile.archetypeConventions ?? [];
744
+ if (own.length > 0) return own;
745
+ const family = profile.family ?? deriveFamilyFromModelId(profile.id);
746
+ if (!family) return [];
747
+ const repId = familyRepId(family);
748
+ if (!repId || repId === profile.id) return [];
749
+ const rep = tryGetProfile(repId);
750
+ return rep?.archetypeConventions ?? [];
751
+ }
246
752
  function passScoreTargets(ir, opts) {
247
753
  const constraints = ir.constraints ?? {};
248
754
  const policy = opts.policy ?? {};
@@ -250,7 +756,8 @@ function passScoreTargets(ir, opts) {
250
756
  const preferredSet = new Set(policy.preferredModels ?? []);
251
757
  const scores = [];
252
758
  const policyMutations = [];
253
- for (const modelId of ir.models) {
759
+ const modelIds = ir.models.filter((m) => typeof m === "string");
760
+ for (const modelId of modelIds) {
254
761
  let profile;
255
762
  try {
256
763
  profile = opts.profilesById(modelId);
@@ -294,10 +801,26 @@ function passScoreTargets(ir, opts) {
294
801
  }
295
802
  const baseQuality = profile.strengths.includes("reasoning") ? 0.85 : profile.strengths.includes("quality") ? 0.8 : 0.6;
296
803
  const qualityScore = Math.max(0, baseQuality - qualityPenalty);
297
- const callerOrderBoost = (ir.models.length - ir.models.indexOf(modelId)) * 0.1;
804
+ const callerOrderBoost = (modelIds.length - modelIds.indexOf(modelId)) * 0.1;
298
805
  const costPenalty = estimatedCostUsd * 5;
299
806
  const preferredBoost = preferredSet.has(modelId) ? 0.5 : 0;
300
- const rank = qualityScore + callerOrderBoost - costPenalty - reasons.length * 10 + preferredBoost;
807
+ let latencyPenalty = 0;
808
+ const maxLatencyMs = constraints.maxLatencyMs;
809
+ if (typeof maxLatencyMs === "number" && maxLatencyMs > 0) {
810
+ const tierMs = LATENCY_TIER_MS[latencyTierOf(profile)];
811
+ if (tierMs > maxLatencyMs) {
812
+ const overage = (tierMs - maxLatencyMs) / maxLatencyMs;
813
+ latencyPenalty = Math.min(LATENCY_OVERAGE_WEIGHT * overage, LATENCY_PENALTY_CAP);
814
+ }
815
+ }
816
+ let qualityGatePenalty = 0;
817
+ if (constraints.structuredOutput) {
818
+ const schemaWeak = effectiveConventions(profile).some(
819
+ (c) => c.archetype === ir.intent.archetype && c.structuredOutputHint === "avoid"
820
+ );
821
+ if (schemaWeak) qualityGatePenalty = QUALITY_GATE_PENALTY;
822
+ }
823
+ const rank = qualityScore + callerOrderBoost - costPenalty - reasons.length * 10 + preferredBoost - latencyPenalty - qualityGatePenalty;
301
824
  scores.push({
302
825
  modelId,
303
826
  estimatedCostUsd,
@@ -330,6 +853,23 @@ function passScoreTargets(ir, opts) {
330
853
  description: `Model ${modelId} rank boosted by CompilePolicy.preferredModels`
331
854
  });
332
855
  }
856
+ if (latencyPenalty > 0) {
857
+ const tier = latencyTierOf(profile);
858
+ policyMutations.push({
859
+ id: `latency-downrank-${modelId}`,
860
+ source: "latency_guard",
861
+ passName: "score_targets",
862
+ description: `Model ${modelId} rank down ${latencyPenalty.toFixed(2)} \u2014 latency tier '${tier}' (~${LATENCY_TIER_MS[tier]}ms) exceeds constraints.maxLatencyMs (${maxLatencyMs}ms)`
863
+ });
864
+ }
865
+ if (qualityGatePenalty > 0) {
866
+ policyMutations.push({
867
+ id: `quality-gate-structured-${modelId}`,
868
+ source: "quality_gate",
869
+ passName: "score_targets",
870
+ description: `Model ${modelId} gated below the quality floor for archetype '${ir.intent.archetype}' \u2014 declared structuredOutput + kgauto convention flags it schema-weak for this contract (structuredOutputHint:'avoid'). Down-ranked out of leadership; retained as graceful fallback only.`
871
+ });
872
+ }
333
873
  }
334
874
  return { value: scores, mutations: policyMutations };
335
875
  }
@@ -356,6 +896,175 @@ function simpleHash(s) {
356
896
  }
357
897
  return (h >>> 0).toString(36);
358
898
  }
899
+ function resolveConventionsForProfile(profile) {
900
+ const own = profile.archetypeConventions ?? [];
901
+ const family = profile.family ?? deriveFamilyFromModelId(profile.id);
902
+ if (!family) return own;
903
+ return own;
904
+ }
905
+ function applyArchetypeConvention(promptText, archetype, family) {
906
+ if (typeof promptText !== "string" || promptText.length === 0) return promptText;
907
+ const repId = familyRepId(family);
908
+ if (!repId) return promptText;
909
+ const profile = tryGetProfile(repId);
910
+ if (!profile || !profile.archetypeConventions) return promptText;
911
+ const conventions = profile.archetypeConventions.filter((c) => c.archetype === archetype);
912
+ if (conventions.length === 0) return promptText;
913
+ let next = promptText;
914
+ for (const c of conventions) {
915
+ if (c.promptPrefix) {
916
+ if (!next.startsWith(c.promptPrefix)) {
917
+ next = c.promptPrefix + next;
918
+ }
919
+ }
920
+ if (c.promptSuffix) {
921
+ if (!next.endsWith(c.promptSuffix)) {
922
+ next = next + c.promptSuffix;
923
+ }
924
+ }
925
+ }
926
+ return next;
927
+ }
928
+ function familyRepId(family) {
929
+ switch (family) {
930
+ case "deepseek-reasoner":
931
+ return "deepseek-v4-pro";
932
+ case "deepseek-chat":
933
+ return "deepseek-v4-flash";
934
+ default:
935
+ return void 0;
936
+ }
937
+ }
938
+ function passApplyConventions(ir, profile) {
939
+ const mutations = [];
940
+ const cliffWarnings = [];
941
+ const structuredOutputHints = [];
942
+ const own = profile.archetypeConventions ?? [];
943
+ let effective = own;
944
+ if (effective.length === 0) {
945
+ const family2 = profile.family ?? deriveFamilyFromModelId(profile.id);
946
+ if (family2) {
947
+ const repId = familyRepId(family2);
948
+ if (repId && repId !== profile.id) {
949
+ const repProfile = tryGetProfile(repId);
950
+ if (repProfile && repProfile.archetypeConventions) {
951
+ effective = repProfile.archetypeConventions;
952
+ }
953
+ }
954
+ }
955
+ }
956
+ if (effective.length === 0) {
957
+ return {
958
+ value: { ir, cliffWarnings, structuredOutputHints },
959
+ mutations
960
+ };
961
+ }
962
+ const archetype = ir.intent.archetype;
963
+ const matching = effective.filter((c) => c.archetype === archetype);
964
+ if (matching.length === 0) {
965
+ return {
966
+ value: { ir, cliffWarnings, structuredOutputHints },
967
+ mutations
968
+ };
969
+ }
970
+ const family = profile.family ?? deriveFamilyFromModelId(profile.id) ?? "unknown";
971
+ let nextIR = ir;
972
+ for (const convention of matching) {
973
+ let touched = false;
974
+ if (convention.promptPrefix) {
975
+ const sections = nextIR.sections ?? [];
976
+ const alreadyPresent = sections.some(
977
+ (s) => s.text.includes(convention.promptPrefix)
978
+ );
979
+ if (!alreadyPresent) {
980
+ const prefixSection = {
981
+ id: `convention-prefix-${family}-${archetype}`,
982
+ text: convention.promptPrefix
983
+ };
984
+ nextIR = { ...nextIR, sections: [prefixSection, ...sections] };
985
+ touched = true;
986
+ }
987
+ }
988
+ if (convention.promptSuffix) {
989
+ const history = nextIR.history ?? [];
990
+ let lastUserIdx = -1;
991
+ for (let i = history.length - 1; i >= 0; i--) {
992
+ if (history[i]?.role === "user") {
993
+ lastUserIdx = i;
994
+ break;
995
+ }
996
+ }
997
+ if (lastUserIdx >= 0) {
998
+ const target = history[lastUserIdx];
999
+ if (!target.content.endsWith(convention.promptSuffix)) {
1000
+ const newHistory = history.slice();
1001
+ newHistory[lastUserIdx] = {
1002
+ ...target,
1003
+ content: target.content + convention.promptSuffix
1004
+ };
1005
+ nextIR = { ...nextIR, history: newHistory };
1006
+ touched = true;
1007
+ }
1008
+ } else if (nextIR.currentTurn && nextIR.currentTurn.role === "user") {
1009
+ const target = nextIR.currentTurn;
1010
+ if (!target.content.endsWith(convention.promptSuffix)) {
1011
+ nextIR = {
1012
+ ...nextIR,
1013
+ currentTurn: {
1014
+ ...target,
1015
+ content: target.content + convention.promptSuffix
1016
+ }
1017
+ };
1018
+ touched = true;
1019
+ }
1020
+ } else {
1021
+ nextIR = {
1022
+ ...nextIR,
1023
+ currentTurn: {
1024
+ role: "user",
1025
+ content: convention.promptSuffix.trim()
1026
+ }
1027
+ };
1028
+ touched = true;
1029
+ }
1030
+ }
1031
+ if (convention.structuredOutputHint) {
1032
+ const wantsStructured = !!nextIR.constraints?.structuredOutput;
1033
+ const shouldSurface = convention.structuredOutputHint === "avoid" && wantsStructured || convention.structuredOutputHint === "enforce" && !wantsStructured;
1034
+ if (shouldSurface) {
1035
+ structuredOutputHints.push({
1036
+ archetype,
1037
+ hint: convention.structuredOutputHint,
1038
+ reason: convention.reason
1039
+ });
1040
+ if (convention.cliffWarning) {
1041
+ cliffWarnings.push(`${profile.id}: ${convention.cliffWarning}`);
1042
+ }
1043
+ touched = true;
1044
+ }
1045
+ }
1046
+ if (convention.cliffWarning && !convention.structuredOutputHint) {
1047
+ const toolCount = nextIR.tools?.length ?? 0;
1048
+ const thresholdOk = convention.whenToolCountAtLeast === void 0 || toolCount >= convention.whenToolCountAtLeast;
1049
+ if (thresholdOk) {
1050
+ cliffWarnings.push(`${profile.id}: ${convention.cliffWarning}`);
1051
+ touched = true;
1052
+ }
1053
+ }
1054
+ if (touched) {
1055
+ mutations.push({
1056
+ id: `apply-convention-${family}-${archetype}`,
1057
+ source: "archetype_convention",
1058
+ passName: "apply_conventions",
1059
+ description: `${profile.id}: applied ${family}-family convention for archetype=${archetype} \u2014 ${convention.reason}`
1060
+ });
1061
+ }
1062
+ }
1063
+ return {
1064
+ value: { ir: nextIR, cliffWarnings, structuredOutputHints },
1065
+ mutations
1066
+ };
1067
+ }
359
1068
 
360
1069
  // src/lower.ts
361
1070
  function lower(ir, profile, hints = {}) {
@@ -383,6 +1092,7 @@ function lowerAnthropic(ir, profile, hints) {
383
1092
  const historyCacheableTokens = markIndex >= 0 ? sumHistoryTokens(history, markIndex) : 0;
384
1093
  const totalCacheableTokens = cacheableTokens + historyCacheableTokens;
385
1094
  const cacheSavings = totalCacheableTokens / 1e6 * profile.costInputPer1m * (1 - (profile.lowering.cache.discount ?? 0.1));
1095
+ const toolChoice = hints.wireOverrides?.parallelToolCalls === false && tools && tools.length > 0 ? { type: "auto", disable_parallel_tool_use: true } : void 0;
386
1096
  return {
387
1097
  request: {
388
1098
  provider: "anthropic",
@@ -390,7 +1100,12 @@ function lowerAnthropic(ir, profile, hints) {
390
1100
  system: systemBlocks,
391
1101
  messages,
392
1102
  tools,
393
- max_tokens: hints.forceTerseOutput ? 200 : Math.min(profile.maxOutputTokens, 4096)
1103
+ // alpha.8: trust profile.maxOutputTokens. The historical Math.min(_, 4096)
1104
+ // floor surprised every consumer once (PB-Cairn contract-gaps brief, Gap 3).
1105
+ // Profile is the single source of truth; consumers wanting a tighter
1106
+ // budget can pass providerOverrides.anthropic.max_tokens explicitly.
1107
+ max_tokens: hints.forceTerseOutput ? 200 : profile.maxOutputTokens,
1108
+ tool_choice: toolChoice
394
1109
  },
395
1110
  diagnostics: {
396
1111
  cacheableTokens,
@@ -584,6 +1299,7 @@ function lowerOpenAI(ir, profile, hints) {
584
1299
  const history = (ir.history ?? []).filter((m) => m.role !== "system");
585
1300
  const histMarkIndex = resolveHistoryMarkIndex(history.length, ir.historyCachePolicy);
586
1301
  const historyCacheableTokens = histMarkIndex >= 0 ? sumHistoryTokens(history, histMarkIndex) : 0;
1302
+ const openaiParallelToolCalls = hints.wireOverrides?.parallelToolCalls === false && ir.tools && ir.tools.length > 0 ? false : void 0;
587
1303
  return {
588
1304
  request: {
589
1305
  provider: "openai",
@@ -591,7 +1307,8 @@ function lowerOpenAI(ir, profile, hints) {
591
1307
  messages,
592
1308
  tools: ir.tools && ir.tools.length > 0 ? toOpenAITools(ir.tools) : void 0,
593
1309
  response_format: ir.constraints?.structuredOutput ? { type: "json_object" } : void 0,
594
- reasoning_effort: hints.forceTerseOutput ? "low" : void 0
1310
+ reasoning_effort: hints.forceTerseOutput ? "low" : void 0,
1311
+ parallel_tool_calls: openaiParallelToolCalls
595
1312
  },
596
1313
  diagnostics: {
597
1314
  cacheableTokens: 0,
@@ -668,101 +1385,897 @@ function setNestedField(obj, path, value) {
668
1385
  cursor[parts[parts.length - 1]] = value;
669
1386
  }
670
1387
 
671
- // src/advisor.ts
672
- function runAdvisor(ir, result, profile) {
673
- const out = [];
674
- out.push(...detectCachingOff(ir, profile));
675
- out.push(...detectSingleChunkSystem(ir, profile));
676
- out.push(...detectToolBloat(ir, result));
677
- out.push(...detectHistoryUncached(ir, profile));
1388
+ // src/archetype-perf-brain.ts
1389
+ function isPerfRow(x) {
1390
+ if (!x || typeof x !== "object") return false;
1391
+ const r = x;
1392
+ return typeof r.model_id === "string" && typeof r.archetype === "string" && typeof r.perf_score === "number";
1393
+ }
1394
+ function mapRowsToPerfMap(rows) {
1395
+ const out = /* @__PURE__ */ new Map();
1396
+ for (const row of rows) {
1397
+ if (!isPerfRow(row)) continue;
1398
+ const existing = out.get(row.model_id) ?? {};
1399
+ existing[row.archetype] = row.perf_score;
1400
+ out.set(row.model_id, existing);
1401
+ }
678
1402
  return out;
679
1403
  }
680
- function detectCachingOff(ir, profile) {
681
- if (profile.provider !== "anthropic") return [];
682
- const totalChars = ir.sections.reduce((s, sec) => s + sec.text.length, 0);
683
- if (totalChars < 2e3) return [];
684
- const anyCacheable = ir.sections.some((s) => s.cacheable === true);
685
- if (anyCacheable) return [];
686
- return [
687
- {
688
- level: "warn",
689
- code: "caching-off-on-claude",
690
- 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.`,
691
- 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.",
692
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
693
- }
694
- ];
1404
+ function mapRowsToNMap(rows) {
1405
+ const out = /* @__PURE__ */ new Map();
1406
+ for (const row of rows) {
1407
+ if (!isPerfRow(row)) continue;
1408
+ if (typeof row.n !== "number") continue;
1409
+ const existing = out.get(row.model_id) ?? {};
1410
+ existing[row.archetype] = row.n;
1411
+ out.set(row.model_id, existing);
1412
+ }
1413
+ return out;
695
1414
  }
696
- function detectSingleChunkSystem(ir, profile) {
697
- if (profile.provider !== "anthropic") return [];
698
- if (ir.sections.length !== 1) return [];
699
- const only = ir.sections[0];
700
- if (!only || only.text.length <= 1e3) return [];
701
- return [
702
- {
703
- level: "info",
704
- code: "single-chunk-system",
705
- 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.`,
706
- 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.",
707
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
708
- }
709
- ];
1415
+ function bundledArchetypePerf() {
1416
+ const out = /* @__PURE__ */ new Map();
1417
+ for (const profile of allProfiles()) {
1418
+ if (profile.archetypePerf) out.set(profile.id, profile.archetypePerf);
1419
+ }
1420
+ return out;
710
1421
  }
711
- function detectToolBloat(ir, result) {
712
- const SHORT_OUTPUT = /* @__PURE__ */ new Set([
713
- "classify",
714
- "extract",
715
- "summarize",
716
- "transform",
717
- "critique"
718
- ]);
719
- if (!ir.tools || ir.tools.length === 0) return [];
720
- const toolsKept = result.diagnostics.toolsKept;
721
- if (toolsKept <= 10) return [];
722
- if (!SHORT_OUTPUT.has(ir.intent.archetype)) return [];
723
- return [
724
- {
725
- level: "warn",
726
- code: "tool-bloat",
727
- 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.`,
728
- 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.",
729
- docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
730
- }
731
- ];
1422
+ function bundledArchetypePerfN() {
1423
+ return /* @__PURE__ */ new Map();
732
1424
  }
733
- function detectHistoryUncached(ir, profile) {
734
- if (profile.provider !== "anthropic") return [];
735
- if (!ir.history || ir.history.length < 2) return [];
736
- if (ir.historyCachePolicy && ir.historyCachePolicy.strategy !== "none") {
737
- return [];
1425
+ var loadArchetypePerfFromBrain = createBrainQueryCache({
1426
+ table: "kgauto_archetype_perf",
1427
+ mapRows: mapRowsToPerfMap,
1428
+ bundledFallback: bundledArchetypePerf
1429
+ });
1430
+ var loadArchetypePerfNFromBrain = createBrainQueryCache(
1431
+ {
1432
+ table: "kgauto_archetype_perf",
1433
+ mapRows: mapRowsToNMap,
1434
+ bundledFallback: bundledArchetypePerfN
738
1435
  }
739
- return [
740
- {
741
- level: "warn",
742
- code: "history-uncached-on-claude",
743
- 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.`,
744
- 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.",
745
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
746
- }
747
- ];
1436
+ );
1437
+ var MEASURED_GROUNDING_MIN_N = 10;
1438
+ function getArchetypePerfScore(modelId, archetype) {
1439
+ const score = loadArchetypePerfFromBrain().get(modelId)?.[archetype] ?? 5;
1440
+ const n = loadArchetypePerfNFromBrain().get(modelId)?.[archetype] ?? 0;
1441
+ const grounding = n >= MEASURED_GROUNDING_MIN_N ? "measured" : "judgment";
1442
+ return { score, n, grounding };
748
1443
  }
749
1444
 
750
- // src/compile.ts
751
- var counter = 0;
752
- function makeHandle() {
753
- counter = (counter + 1) % 1e6;
1445
+ // src/promote-ready-brain.ts
1446
+ function isRawPromoteReadyRow(x) {
1447
+ if (!x || typeof x !== "object") return false;
1448
+ const r = x;
1449
+ 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";
1450
+ }
1451
+ function coerceNumber(v) {
1452
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
1453
+ if (typeof v === "string") {
1454
+ const n = Number(v);
1455
+ return Number.isFinite(n) ? n : null;
1456
+ }
1457
+ return null;
1458
+ }
1459
+ function mapRowsToFindings2(rows) {
1460
+ const out = [];
1461
+ for (const row of rows) {
1462
+ if (!isRawPromoteReadyRow(row)) continue;
1463
+ const sampleN = coerceNumber(row.sample_n);
1464
+ const passRate = coerceNumber(row.judge_pass_rate);
1465
+ const avgScore = coerceNumber(row.judge_avg_score);
1466
+ if (sampleN === null || passRate === null || avgScore === null) continue;
1467
+ out.push({
1468
+ archetype: row.intent_archetype,
1469
+ family: row.family,
1470
+ candidateModel: row.candidate_model,
1471
+ currentModel: row.current_model,
1472
+ sampleN,
1473
+ judgePassRate: passRate,
1474
+ judgeAvgScore: avgScore,
1475
+ costDeltaPct: coerceNumber(row.cost_delta_pct),
1476
+ detectedAt: row.detected_at
1477
+ });
1478
+ }
1479
+ return out;
1480
+ }
1481
+ var snapshots2 = /* @__PURE__ */ new Map();
1482
+ var runtime2;
1483
+ var warnedOnce2 = false;
1484
+ function isPromoteReadyBrainActive() {
1485
+ return runtime2 !== void 0;
1486
+ }
1487
+ function loadPromoteReadyFindings(opts) {
1488
+ const rt = runtime2;
1489
+ if (!rt) return [];
1490
+ const appId = opts.appId;
1491
+ if (!appId) return [];
1492
+ let snap = snapshots2.get(appId);
1493
+ if (!snap) {
1494
+ snap = { data: [], expiresAt: 0, refreshing: false };
1495
+ snapshots2.set(appId, snap);
1496
+ }
1497
+ const now = Date.now();
1498
+ const stale = snap.expiresAt <= now;
1499
+ if (stale && !snap.refreshing) {
1500
+ snap.refreshing = true;
1501
+ void asyncRefresh2(rt, appId);
1502
+ }
1503
+ let rows = snap.data;
1504
+ if (opts.archetype) {
1505
+ rows = rows.filter((f) => f.archetype === opts.archetype);
1506
+ }
1507
+ if (opts.family) {
1508
+ rows = rows.filter((f) => f.family === opts.family);
1509
+ }
1510
+ return rows;
1511
+ }
1512
+ var pendingRefreshes2 = /* @__PURE__ */ new Map();
1513
+ async function asyncRefresh2(rt, appId) {
1514
+ const promise = doRefresh2(rt, appId);
1515
+ pendingRefreshes2.set(appId, promise);
1516
+ try {
1517
+ await promise;
1518
+ } finally {
1519
+ if (pendingRefreshes2.get(appId) === promise) {
1520
+ pendingRefreshes2.delete(appId);
1521
+ }
1522
+ }
1523
+ }
1524
+ async function doRefresh2(rt, appId) {
1525
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
1526
+ let snap = snapshots2.get(appId);
1527
+ if (!snap) {
1528
+ snap = { data: [], expiresAt: 0, refreshing: false };
1529
+ snapshots2.set(appId, snap);
1530
+ }
1531
+ try {
1532
+ const res = await rt.fetchImpl(url, { method: "GET" });
1533
+ if (!res.ok) {
1534
+ throw new Error(`promote-ready ${res.status}: ${res.statusText}`);
1535
+ }
1536
+ const body = await res.json();
1537
+ if (runtime2 !== rt) return;
1538
+ const rows = Array.isArray(body) ? mapRowsToFindings2(body) : [];
1539
+ snap.data = rows;
1540
+ snap.expiresAt = Date.now() + rt.ttlMs;
1541
+ snap.refreshing = false;
1542
+ } catch (err) {
1543
+ if (runtime2 !== rt) return;
1544
+ snap.refreshing = false;
1545
+ snap.expiresAt = Date.now() + rt.ttlMs;
1546
+ if (!warnedOnce2) {
1547
+ warnedOnce2 = true;
1548
+ (rt.onError ?? defaultOnError2)(err);
1549
+ }
1550
+ }
1551
+ }
1552
+ function defaultOnError2(err) {
1553
+ console.warn(
1554
+ "[kgauto] promote-ready fetch failed (using empty fallback):",
1555
+ err
1556
+ );
1557
+ }
1558
+ function resolveFetchImpl(injected) {
1559
+ return injected ?? ((...args) => globalThis.fetch(...args));
1560
+ }
1561
+ function normalizeEndpoint(endpoint) {
1562
+ return endpoint.replace(/\/+$/, "");
1563
+ }
1564
+ async function markPromoteReadyHandled(opts) {
1565
+ const {
1566
+ appId,
1567
+ archetype,
1568
+ family,
1569
+ resolution,
1570
+ resolutionNote,
1571
+ brainEndpoint,
1572
+ brainJwt,
1573
+ brainAnonKey,
1574
+ fetch: injectedFetch
1575
+ } = opts;
1576
+ if (!appId) return { ok: false, reason: "app_id_required" };
1577
+ if (!archetype) return { ok: false, reason: "archetype_required" };
1578
+ if (!family) return { ok: false, reason: "family_required" };
1579
+ if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
1580
+ return { ok: false, reason: "resolution_invalid" };
1581
+ }
1582
+ const doFetch = resolveFetchImpl(injectedFetch);
1583
+ const base = normalizeEndpoint(brainEndpoint);
1584
+ 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`;
1585
+ const patchBody = {
1586
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
1587
+ resolution
1588
+ };
1589
+ if (resolutionNote !== void 0) {
1590
+ patchBody.resolution_note = resolutionNote;
1591
+ }
1592
+ let res;
1593
+ try {
1594
+ res = await doFetch(url, {
1595
+ method: "PATCH",
1596
+ headers: {
1597
+ Authorization: `Bearer ${brainJwt}`,
1598
+ apikey: brainAnonKey,
1599
+ "Content-Type": "application/json",
1600
+ Accept: "application/json",
1601
+ Prefer: "return=minimal"
1602
+ },
1603
+ body: JSON.stringify(patchBody)
1604
+ });
1605
+ } catch (err) {
1606
+ const msg = err instanceof Error ? err.message : String(err);
1607
+ return { ok: false, reason: `network_error:${msg}` };
1608
+ }
1609
+ if (res.status === 401 || res.status === 403) {
1610
+ return { ok: false, reason: "brain_auth_misconfig" };
1611
+ }
1612
+ if (res.status >= 500) {
1613
+ return { ok: false, reason: "brain_unavailable" };
1614
+ }
1615
+ if (!res.ok) {
1616
+ return { ok: false, reason: `patch_failed:${res.status}` };
1617
+ }
1618
+ return { ok: true };
1619
+ }
1620
+
1621
+ // src/advisor-rules/promote-ready.ts
1622
+ var PROMOTE_READY_THRESHOLDS = {
1623
+ minPassRate: 0.8,
1624
+ minAvgScore: 4
1625
+ };
1626
+ function shouldFirePromoteReady(finding, resolvedPrimary) {
1627
+ if (finding.currentModel !== resolvedPrimary) return false;
1628
+ if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
1629
+ if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
1630
+ return true;
1631
+ }
1632
+ function deriveFamilyLocal(modelId) {
1633
+ if (modelId.startsWith("claude-opus-")) return "claude-opus";
1634
+ if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
1635
+ if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
1636
+ if (/^gemini-.*-flash-lite/.test(modelId)) return "gemini-flash-lite";
1637
+ if (/^gemini-.*-flash/.test(modelId)) return "gemini-flash";
1638
+ if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
1639
+ if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
1640
+ if (modelId.startsWith("deepseek-")) return "deepseek-chat";
1641
+ if (modelId.startsWith("gpt-")) return "openai-gpt";
1642
+ return null;
1643
+ }
1644
+ function advisorRulePromoteReady(ctx) {
1645
+ if (!isPromoteReadyBrainActive()) return [];
1646
+ if (!ctx.appId) return [];
1647
+ if (!ctx.resolvedPrimary) return [];
1648
+ const family = deriveFamilyLocal(ctx.resolvedPrimary);
1649
+ if (!family) return [];
1650
+ const findings = loadPromoteReadyFindings({
1651
+ appId: ctx.appId,
1652
+ archetype: ctx.archetype,
1653
+ family
1654
+ });
1655
+ if (findings.length === 0) return [];
1656
+ const qualifying = findings.filter(
1657
+ (f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
1658
+ );
1659
+ if (qualifying.length === 0) return [];
1660
+ qualifying.sort((a, b) => {
1661
+ if (a.judgeAvgScore !== b.judgeAvgScore) {
1662
+ return b.judgeAvgScore - a.judgeAvgScore;
1663
+ }
1664
+ return b.judgePassRate - a.judgePassRate;
1665
+ });
1666
+ const top = qualifying[0];
1667
+ const pctPass = Math.round(top.judgePassRate * 100);
1668
+ const score = top.judgeAvgScore.toFixed(2);
1669
+ let costClause = "";
1670
+ if (top.costDeltaPct !== null) {
1671
+ const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
1672
+ const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
1673
+ costClause = `, cost ${magnitude}% ${sign}`;
1674
+ }
1675
+ 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.`;
1676
+ return [
1677
+ {
1678
+ level: "info",
1679
+ code: "promote-ready",
1680
+ message,
1681
+ 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.`,
1682
+ // alpha.36 architectural field — not a no-ai-needed case.
1683
+ recommendedArchitecture: void 0,
1684
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1685
+ }
1686
+ ];
1687
+ }
1688
+
1689
+ // src/advisor-rules/consumer-on-stale-model.ts
1690
+ function isStaleStatus(v) {
1691
+ return v === "legacy" || v === "deprecated";
1692
+ }
1693
+ function asString(v) {
1694
+ return typeof v === "string" && v.length > 0 ? v : void 0;
1695
+ }
1696
+ function mapRowsToFindings3(rows) {
1697
+ const out = [];
1698
+ for (const raw of rows) {
1699
+ if (!raw || typeof raw !== "object") continue;
1700
+ const r = raw;
1701
+ const archetype = asString(r.intent_archetype) ?? asString(r.applies_to_archetype);
1702
+ const staleModel = asString(r.stale_model) ?? asString(r.applies_to_model);
1703
+ const staleProvider = asString(r.stale_provider);
1704
+ const recommendedModel = asString(r.recommended_model);
1705
+ const family = asString(r.family);
1706
+ const message = asString(r.message);
1707
+ if (!archetype || !staleModel || !recommendedModel || !family || !message) {
1708
+ continue;
1709
+ }
1710
+ if (!isStaleStatus(r.stale_status)) continue;
1711
+ const row = {
1712
+ archetype,
1713
+ staleModel,
1714
+ staleProvider: staleProvider ?? "unknown",
1715
+ staleStatus: r.stale_status,
1716
+ recommendedModel,
1717
+ family,
1718
+ message
1719
+ };
1720
+ const suggestion = asString(r.suggestion);
1721
+ if (suggestion) row.suggestion = suggestion;
1722
+ if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
1723
+ row.observationCount = r.observation_count;
1724
+ }
1725
+ out.push(row);
1726
+ }
1727
+ return out;
1728
+ }
1729
+ var snapshots3 = /* @__PURE__ */ new Map();
1730
+ var runtime3;
1731
+ var warnedOnce3 = false;
1732
+ var pendingRefreshes3 = /* @__PURE__ */ new Map();
1733
+ function isStaleModelFindingsBrainActive() {
1734
+ return runtime3 !== void 0;
1735
+ }
1736
+ function getStaleModelFindings(opts) {
1737
+ const rt = runtime3;
1738
+ if (!rt) return [];
1739
+ const appId = opts.appId;
1740
+ if (!appId) return [];
1741
+ let snap = snapshots3.get(appId);
1742
+ if (!snap) {
1743
+ snap = { data: [], expiresAt: 0, refreshing: false };
1744
+ snapshots3.set(appId, snap);
1745
+ }
1746
+ const now = Date.now();
1747
+ const stale = snap.expiresAt <= now;
1748
+ if (stale && !snap.refreshing) {
1749
+ snap.refreshing = true;
1750
+ void asyncRefresh3(rt, appId);
1751
+ }
1752
+ if (opts.archetype) {
1753
+ return snap.data.filter((f) => f.archetype === opts.archetype);
1754
+ }
1755
+ return snap.data;
1756
+ }
1757
+ async function asyncRefresh3(rt, appId) {
1758
+ const promise = doRefresh3(rt, appId);
1759
+ pendingRefreshes3.set(appId, promise);
1760
+ try {
1761
+ await promise;
1762
+ } finally {
1763
+ if (pendingRefreshes3.get(appId) === promise) {
1764
+ pendingRefreshes3.delete(appId);
1765
+ }
1766
+ }
1767
+ }
1768
+ async function doRefresh3(rt, appId) {
1769
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
1770
+ let snap = snapshots3.get(appId);
1771
+ if (!snap) {
1772
+ snap = { data: [], expiresAt: 0, refreshing: false };
1773
+ snapshots3.set(appId, snap);
1774
+ }
1775
+ try {
1776
+ const res = await rt.fetchImpl(url, { method: "GET" });
1777
+ if (!res.ok) {
1778
+ throw new Error(`stale-model findings ${res.status}: ${res.statusText}`);
1779
+ }
1780
+ const body = await res.json();
1781
+ if (runtime3 !== rt) return;
1782
+ const rows = Array.isArray(body) ? mapRowsToFindings3(body) : [];
1783
+ snap.data = rows;
1784
+ snap.expiresAt = Date.now() + rt.ttlMs;
1785
+ snap.refreshing = false;
1786
+ } catch (err) {
1787
+ if (runtime3 !== rt) return;
1788
+ snap.refreshing = false;
1789
+ snap.expiresAt = Date.now() + rt.ttlMs;
1790
+ if (!warnedOnce3) {
1791
+ warnedOnce3 = true;
1792
+ (rt.onError ?? defaultOnError3)(err);
1793
+ }
1794
+ }
1795
+ }
1796
+ function defaultOnError3(err) {
1797
+ console.warn(
1798
+ "[kgauto] stale-model findings fetch failed (using empty fallback):",
1799
+ err
1800
+ );
1801
+ }
1802
+ var CONSUMER_ON_STALE_MODEL_RULE_CODE = "consumer-on-stale-model";
1803
+ function advisorRuleConsumerOnStaleModel(ir) {
1804
+ if (!isStaleModelFindingsBrainActive()) return [];
1805
+ if (!ir.appId) return [];
1806
+ const findings = getStaleModelFindings({
1807
+ appId: ir.appId,
1808
+ archetype: ir.intent.archetype
1809
+ });
1810
+ if (findings.length === 0) return [];
1811
+ const ranked = [...findings].sort((a, b) => {
1812
+ if (a.staleStatus !== b.staleStatus) {
1813
+ return a.staleStatus === "deprecated" ? -1 : 1;
1814
+ }
1815
+ return a.staleModel.localeCompare(b.staleModel);
1816
+ });
1817
+ const top = ranked[0];
1818
+ const extraCount = findings.length - 1;
1819
+ const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
1820
+ return [
1821
+ {
1822
+ level: "warn",
1823
+ code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
1824
+ message: `${top.message}${extraNote}`,
1825
+ 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}.`,
1826
+ recommendationType: "model-swap",
1827
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1828
+ }
1829
+ ];
1830
+ }
1831
+
1832
+ // src/archetype-fits.ts
1833
+ var ARCHETYPE_FAMILY_FITS = Object.freeze([
1834
+ {
1835
+ archetype: "plan",
1836
+ betterFitFamily: "deepseek-reasoner",
1837
+ reason: "Plan archetype is reasoning-shaped (multi-step chains, hypothesis-and-check, sub-goal decomposition) \u2014 exactly where reasoner-family models excel. Sonnet/Opus produce plans but at higher cost; reasoners produce equivalent-or-better plans at 7-17x lower cost at current promo pricing (deepseek-v4-pro $0.435/$0.87 per 1M promo through 2026-05-31 vs sonnet $3/$15).",
1838
+ costGuidance: "substantially cheaper at current pricing (deepseek-v4-pro promo: ~7-17x cheaper than sonnet)"
1839
+ },
1840
+ {
1841
+ archetype: "critique",
1842
+ betterFitFamily: "deepseek-reasoner",
1843
+ reason: "Critique archetype rewards epistemic humility and explicit reasoning \u2014 reasoner-family default behavior. Sonnet/Opus over-confident on critique tasks; reasoners surface uncertainty productively.",
1844
+ costGuidance: "comparable or cheaper at current pricing"
1845
+ }
1846
+ ]);
1847
+ function findBetterFit(archetype, currentFamily) {
1848
+ for (const fit of ARCHETYPE_FAMILY_FITS) {
1849
+ if (fit.archetype !== archetype) continue;
1850
+ if (fit.betterFitFamily === currentFamily) return null;
1851
+ return fit;
1852
+ }
1853
+ return null;
1854
+ }
1855
+
1856
+ // src/advisor-rules/cross-family-fit.ts
1857
+ function familyHasCurrentActiveModel(family) {
1858
+ for (const profile of allProfiles()) {
1859
+ const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
1860
+ if (profileFamily !== family) continue;
1861
+ if (profile.status !== "current") continue;
1862
+ if (profile.active === false) continue;
1863
+ return true;
1864
+ }
1865
+ return false;
1866
+ }
1867
+ function listCandidatesInFamily(family) {
1868
+ const candidates = [];
1869
+ for (const profile of allProfiles()) {
1870
+ const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
1871
+ if (profileFamily !== family) continue;
1872
+ if (profile.status !== "current") continue;
1873
+ if (profile.active === false) continue;
1874
+ candidates.push(profile.id);
1875
+ if (candidates.length >= 3) break;
1876
+ }
1877
+ return candidates;
1878
+ }
1879
+ function advisorRuleCrossFamilyFit(ctx) {
1880
+ if (!ctx.resolvedPrimary) return [];
1881
+ const currentFamily = deriveFamilyFromModelId(ctx.resolvedPrimary);
1882
+ if (!currentFamily) return [];
1883
+ const fit = findBetterFit(ctx.archetype, currentFamily);
1884
+ if (!fit) return [];
1885
+ if (!familyHasCurrentActiveModel(fit.betterFitFamily)) return [];
1886
+ const candidates = listCandidatesInFamily(fit.betterFitFamily);
1887
+ if (candidates.length === 0) return [];
1888
+ const candidateStr = candidates.join(", ");
1889
+ const message = `Your ${currentFamily} call on ${ctx.archetype} could shift to ${fit.betterFitFamily} \u2014 typically better quality + ${fit.costGuidance}. Suggested candidates: ${candidateStr}.`;
1890
+ return [
1891
+ {
1892
+ level: "info",
1893
+ code: "cross-family-fit-candidate",
1894
+ ownership: "consumer-actionable",
1895
+ message,
1896
+ suggestion: `Swap the model literal in \`ir.models\` to one of: ${candidateStr}. Or call \`getRecommendedPrimary({ family: '${fit.betterFitFamily}', archetype: '${ctx.archetype}', fallback: { id: '${candidates[0]}', reason: 'cross-family-fit-recommendation' } })\` to let kgauto resolve to the current+active family member.`,
1897
+ recommendationType: "model-swap",
1898
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1899
+ }
1900
+ ];
1901
+ }
1902
+
1903
+ // src/advisor.ts
1904
+ var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
1905
+ var TIER_DOWN_COST_RATIO = 0.5;
1906
+ var COST_MISMATCHED_CHOSEN_SCORE_CEILING = 7;
1907
+ var PRODUCER_OWNED_RULE_CODES = Object.freeze(
1908
+ /* @__PURE__ */ new Set(["model-stale-evidence", "promote-ready"])
1909
+ );
1910
+ function deriveOwnership(code, selfDeclared) {
1911
+ if (selfDeclared) return selfDeclared;
1912
+ return PRODUCER_OWNED_RULE_CODES.has(code) ? "producer-owned" : "consumer-actionable";
1913
+ }
1914
+ function runAdvisor(ir, result, profile, policy, phase2) {
1915
+ const out = [];
1916
+ out.push(...detectCachingOff(ir, profile));
1917
+ out.push(...detectSingleChunkSystem(ir, profile));
1918
+ out.push(...detectToolBloat(ir, result));
1919
+ out.push(...detectHistoryUncached(ir, profile));
1920
+ out.push(...detectSingleModelArray(ir, policy));
1921
+ if (policy?.posture !== "locked") {
1922
+ out.push(...detectCostMismatchedArchetype(ir, profile, phase2));
1923
+ out.push(...detectModelStaleEvidence(ir, profile));
1924
+ out.push(...detectTierDown(ir, profile, phase2));
1925
+ }
1926
+ if (!translatorClearedToolCallCliff(phase2)) {
1927
+ out.push(...detectArchetypePerfFloorBreach(ir, profile));
1928
+ }
1929
+ if (policy?.posture !== "locked") {
1930
+ out.push(...detectStaleExclusionCandidate(ir));
1931
+ }
1932
+ if (policy?.posture !== "locked" && ir.appId) {
1933
+ out.push(
1934
+ ...advisorRulePromoteReady({
1935
+ appId: ir.appId,
1936
+ archetype: ir.intent.archetype,
1937
+ resolvedPrimary: profile.id
1938
+ })
1939
+ );
1940
+ out.push(...advisorRuleConsumerOnStaleModel(ir));
1941
+ }
1942
+ if (policy?.posture !== "locked") {
1943
+ out.push(
1944
+ ...advisorRuleCrossFamilyFit({
1945
+ archetype: ir.intent.archetype,
1946
+ resolvedPrimary: profile.id
1947
+ })
1948
+ );
1949
+ }
1950
+ return out;
1951
+ }
1952
+ function translatorClearedToolCallCliff(phase2) {
1953
+ const rewrites = phase2?.sectionRewritesApplied;
1954
+ if (!rewrites || rewrites.length === 0) return false;
1955
+ for (const rw of rewrites) {
1956
+ if (rw.kind === "tool_call_contract") return true;
1957
+ }
1958
+ return false;
1959
+ }
1960
+ function detectCachingOff(ir, profile) {
1961
+ if (profile.provider !== "anthropic") return [];
1962
+ const totalChars = ir.sections.reduce((s, sec) => s + sec.text.length, 0);
1963
+ if (totalChars < 2e3) return [];
1964
+ const anyCacheable = ir.sections.some((s) => s.cacheable === true);
1965
+ if (anyCacheable) return [];
1966
+ return [
1967
+ {
1968
+ level: "warn",
1969
+ code: "caching-off-on-claude",
1970
+ 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.`,
1971
+ 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.",
1972
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1973
+ }
1974
+ ];
1975
+ }
1976
+ function detectSingleChunkSystem(ir, profile) {
1977
+ if (profile.provider !== "anthropic") return [];
1978
+ if (ir.sections.length !== 1) return [];
1979
+ const only = ir.sections[0];
1980
+ if (!only || only.text.length <= 1e3) return [];
1981
+ return [
1982
+ {
1983
+ level: "info",
1984
+ code: "single-chunk-system",
1985
+ 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.`,
1986
+ 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.",
1987
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1988
+ }
1989
+ ];
1990
+ }
1991
+ function detectToolBloat(ir, result) {
1992
+ const SHORT_OUTPUT = /* @__PURE__ */ new Set([
1993
+ "classify",
1994
+ "extract",
1995
+ "summarize",
1996
+ "transform",
1997
+ "critique"
1998
+ ]);
1999
+ if (!ir.tools || ir.tools.length === 0) return [];
2000
+ const toolsKept = result.diagnostics.toolsKept;
2001
+ if (toolsKept <= 10) return [];
2002
+ if (!SHORT_OUTPUT.has(ir.intent.archetype)) return [];
2003
+ return [
2004
+ {
2005
+ level: "warn",
2006
+ code: "tool-bloat",
2007
+ 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.`,
2008
+ 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.",
2009
+ docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
2010
+ }
2011
+ ];
2012
+ }
2013
+ function detectHistoryUncached(ir, profile) {
2014
+ if (profile.provider !== "anthropic") return [];
2015
+ if (!ir.history || ir.history.length < 2) return [];
2016
+ if (ir.historyCachePolicy && ir.historyCachePolicy.strategy !== "none") {
2017
+ return [];
2018
+ }
2019
+ return [
2020
+ {
2021
+ level: "warn",
2022
+ code: "history-uncached-on-claude",
2023
+ 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.`,
2024
+ 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.",
2025
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
2026
+ }
2027
+ ];
2028
+ }
2029
+ function detectSingleModelArray(ir, policy) {
2030
+ if (ir.models.length !== 1) return [];
2031
+ if (policy?.posture === "locked") return [];
2032
+ const only = ir.models[0];
2033
+ return [
2034
+ {
2035
+ level: "warn",
2036
+ code: "single-model-array",
2037
+ 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.`,
2038
+ 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.",
2039
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
2040
+ }
2041
+ ];
2042
+ }
2043
+ function detectCostMismatchedArchetype(ir, profile, phase2) {
2044
+ if (!phase2 || phase2.fallbackChain.length === 0) return [];
2045
+ if (!phase2.profileResolver) return [];
2046
+ const archetype = ir.intent.archetype;
2047
+ const chosenScore = getArchetypePerfScore(profile.id, archetype);
2048
+ const chosenHasRoomToGrow = chosenScore.grounding === "judgment" || chosenScore.score < COST_MISMATCHED_CHOSEN_SCORE_CEILING;
2049
+ if (!chosenHasRoomToGrow) return [];
2050
+ let bestAlt = null;
2051
+ for (const altId of phase2.fallbackChain) {
2052
+ const altProfile = phase2.profileResolver(altId);
2053
+ if (!altProfile) continue;
2054
+ if (altProfile.id === profile.id) continue;
2055
+ const altScore = getArchetypePerfScore(altProfile.id, archetype);
2056
+ if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
2057
+ if (altScore.score < chosenScore.score) continue;
2058
+ if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
2059
+ if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
2060
+ bestAlt = { id: altId, profile: altProfile, score: altScore };
2061
+ }
2062
+ }
2063
+ if (!bestAlt) return [];
2064
+ const tierDownWouldFire = bestAlt.score.grounding === "measured" && bestAlt.profile.costInputPer1m <= profile.costInputPer1m * TIER_DOWN_COST_RATIO;
2065
+ if (tierDownWouldFire) return [];
2066
+ const chosenGrounding = chosenScore.grounding === "judgment" ? `archetypePerf.${archetype}=judgment` : `archetypePerf.${archetype}=${chosenScore.score}`;
2067
+ const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
2068
+ return [
2069
+ {
2070
+ level: "warn",
2071
+ code: "cost-mismatched-archetype",
2072
+ 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.`,
2073
+ 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.`,
2074
+ recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
2075
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
2076
+ }
2077
+ ];
2078
+ }
2079
+ function detectModelStaleEvidence(ir, profile) {
2080
+ if (!isBrainQueryActiveFor("kgauto_archetype_perf")) return [];
2081
+ const archetype = ir.intent.archetype;
2082
+ const chosen = getArchetypePerfScore(profile.id, archetype);
2083
+ if (chosen.grounding !== "judgment") return [];
2084
+ return [
2085
+ {
2086
+ level: "info",
2087
+ code: "model-stale-evidence",
2088
+ 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.`,
2089
+ 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.",
2090
+ recommendationType: "prompt-fix",
2091
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
2092
+ }
2093
+ ];
2094
+ }
2095
+ function detectTierDown(ir, profile, phase2) {
2096
+ if (!phase2 || phase2.fallbackChain.length === 0) return [];
2097
+ if (!phase2.profileResolver) return [];
2098
+ const archetype = ir.intent.archetype;
2099
+ const chosenScore = getArchetypePerfScore(profile.id, archetype);
2100
+ const chosenCost = profile.costInputPer1m;
2101
+ let bestAlt = null;
2102
+ for (const altId of phase2.fallbackChain) {
2103
+ const altProfile = phase2.profileResolver(altId);
2104
+ if (!altProfile) continue;
2105
+ if (altProfile.id === profile.id) continue;
2106
+ const altScore = getArchetypePerfScore(altProfile.id, archetype);
2107
+ if (altScore.grounding !== "measured") continue;
2108
+ if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
2109
+ if (altScore.score < chosenScore.score) continue;
2110
+ if (altProfile.costInputPer1m > chosenCost * TIER_DOWN_COST_RATIO) continue;
2111
+ if (!bestAlt || altProfile.costInputPer1m < bestAlt.profile.costInputPer1m || altProfile.costInputPer1m === bestAlt.profile.costInputPer1m && altScore.score > bestAlt.score.score) {
2112
+ bestAlt = { id: altId, profile: altProfile, score: altScore };
2113
+ }
2114
+ }
2115
+ if (!bestAlt) return [];
2116
+ const chosenDesc = chosenScore.grounding === "measured" ? `archetypePerf.${archetype}=${chosenScore.score} (measured, n=${chosenScore.n})` : `archetypePerf.${archetype}=${chosenScore.score} (${chosenScore.grounding})`;
2117
+ return [
2118
+ {
2119
+ level: "warn",
2120
+ code: "tier-down",
2121
+ 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.`,
2122
+ 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.`,
2123
+ recommendationType: "tier-down",
2124
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
2125
+ }
2126
+ ];
2127
+ }
2128
+ function detectArchetypePerfFloorBreach(ir, profile) {
2129
+ const compat = getModelCompatibility(profile.id, {
2130
+ archetype: ir.intent.archetype,
2131
+ toolOrchestration: ir.constraints?.toolOrchestration
2132
+ });
2133
+ if (compat.status === "compatible") return [];
2134
+ if (compat.status === "requires-adapter") {
2135
+ return [
2136
+ {
2137
+ level: "warn",
2138
+ code: "archetype-perf-floor-breach",
2139
+ 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}`,
2140
+ 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.`,
2141
+ recommendationType: "prompt-fix",
2142
+ suggestedAdaptation: compat.adapter,
2143
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
2144
+ }
2145
+ ];
2146
+ }
2147
+ return [
2148
+ {
2149
+ level: "critical",
2150
+ code: "archetype-perf-floor-breach",
2151
+ 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}`,
2152
+ 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.`,
2153
+ recommendationType: "model-swap",
2154
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
2155
+ }
2156
+ ];
2157
+ }
2158
+ function detectStaleExclusionCandidate(ir) {
2159
+ if (!isExclusionFindingsBrainActive()) return [];
2160
+ if (!ir.appId) return [];
2161
+ const findings = getStaleExclusionFindings({
2162
+ appId: ir.appId,
2163
+ archetype: ir.intent.archetype
2164
+ });
2165
+ if (findings.length === 0) return [];
2166
+ const ranked = [...findings].sort((a, b) => {
2167
+ const sa = a.estimatedSavingsUsd30d ?? -Infinity;
2168
+ const sb = b.estimatedSavingsUsd30d ?? -Infinity;
2169
+ if (sa !== sb) return sb - sa;
2170
+ return confidenceRank(b.confidence) - confidenceRank(a.confidence);
2171
+ });
2172
+ const top = ranked[0];
2173
+ const extraCount = findings.length - 1;
2174
+ const extraNote = extraCount > 0 ? ` (+ ${extraCount} more excluded model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
2175
+ return [
2176
+ {
2177
+ level: "info",
2178
+ code: "stale-exclusion-candidate",
2179
+ message: `${top.message}${extraNote}`,
2180
+ suggestion: top.suggestion,
2181
+ recommendationType: "tier-down",
2182
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
2183
+ }
2184
+ ];
2185
+ }
2186
+ function confidenceRank(c) {
2187
+ if (c === "high") return 3;
2188
+ if (c === "medium") return 2;
2189
+ return 1;
2190
+ }
2191
+
2192
+ // src/translator.ts
2193
+ var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
2194
+ var RULE_SEQUENTIAL_TOOL_CLIFF = "sequential-tool-cliff-below-floor";
2195
+ var RULE_NARRATION_DRIFT_ANTHROPIC = "narration-drift-anthropic";
2196
+ var RULE_NARRATION_THINKING_LEAK_DEEPSEEK = "narration-thinking-leak-deepseek";
2197
+ 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.";
2198
+ var NARRATION_DRIFT_ANTHROPIC_PREAMBLE = "Output ONLY the requested content. Do not narrate your thought process. Each line \u2264 12 words.";
2199
+ 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.";
2200
+ function matchRule(kind, profile, archetype) {
2201
+ if (kind === "tool_call_contract") {
2202
+ if (!profile.archetypePerf) return null;
2203
+ const archetypeScore = profile.archetypePerf[archetype];
2204
+ if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
2205
+ return null;
2206
+ }
2207
+ return {
2208
+ id: RULE_SEQUENTIAL_TOOL_CLIFF,
2209
+ preamble: SEQUENTIAL_TOOL_PREAMBLE,
2210
+ wireOverrides: { parallelToolCalls: false }
2211
+ };
2212
+ }
2213
+ if (kind === "narration_contract") {
2214
+ if (profile.provider === "anthropic") {
2215
+ return {
2216
+ id: RULE_NARRATION_DRIFT_ANTHROPIC,
2217
+ preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
2218
+ };
2219
+ }
2220
+ if (profile.provider === "deepseek") {
2221
+ return {
2222
+ id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
2223
+ preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
2224
+ };
2225
+ }
2226
+ return null;
2227
+ }
2228
+ return null;
2229
+ }
2230
+ function applySectionRewrites(args) {
2231
+ const { ir, profile, archetype } = args;
2232
+ if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
2233
+ return { rewrittenIR: ir, rewrites: [] };
2234
+ }
2235
+ const rewrites = [];
2236
+ const newSections = ir.sections.map((section) => {
2237
+ if (!section.kind || section.kind === "arbitrary") return section;
2238
+ const rule = matchRule(section.kind, profile, archetype);
2239
+ if (!rule) return section;
2240
+ const originalText = section.text;
2241
+ const transformedText = `${rule.preamble}
2242
+
2243
+ ${originalText}`;
2244
+ rewrites.push({
2245
+ sectionId: section.id,
2246
+ kind: section.kind,
2247
+ rule: rule.id,
2248
+ originalText,
2249
+ transformedText,
2250
+ ...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
2251
+ });
2252
+ return { ...section, text: transformedText };
2253
+ });
2254
+ if (rewrites.length === 0) {
2255
+ return { rewrittenIR: ir, rewrites: [] };
2256
+ }
2257
+ const rewrittenIR = { ...ir, sections: newSections };
2258
+ return { rewrittenIR, rewrites };
2259
+ }
2260
+
2261
+ // src/compile.ts
2262
+ var counter = 0;
2263
+ function makeHandle() {
2264
+ counter = (counter + 1) % 1e6;
754
2265
  return `c${Date.now().toString(36)}-${counter.toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
755
2266
  }
756
2267
  function compile(ir, opts = {}) {
757
2268
  const resolver = opts.profileResolver ?? getProfile;
758
2269
  validateIR(ir);
2270
+ ir = resolveModelEntries(ir);
759
2271
  const sliced = passSlice(ir);
760
2272
  const deduped = passDedupe(sliced.value);
761
2273
  const toolFiltered = passToolRelevance(deduped.value, {
762
2274
  threshold: opts.toolRelevanceThreshold
763
2275
  });
764
2276
  const compressed = passCompressHistory(toolFiltered.value, {
765
- summarizeOlderThan: opts.compressHistoryAfter
2277
+ summarizeOlderThan: opts.compressHistoryAfter,
2278
+ summarizeAboveTokens: opts.compressHistoryAboveTokens
766
2279
  });
767
2280
  let workingIR = compressed.value;
768
2281
  const accumulatedMutations = [
@@ -789,14 +2302,44 @@ function compile(ir, opts = {}) {
789
2302
  const cliffs = passApplyCliffs(workingIR, profile, inputTokens);
790
2303
  workingIR = cliffs.value.ir;
791
2304
  accumulatedMutations.push(...cliffs.mutations);
2305
+ const conventions = passApplyConventions(workingIR, profile);
2306
+ workingIR = conventions.value.ir;
2307
+ accumulatedMutations.push(...conventions.mutations);
2308
+ const translated = applySectionRewrites({
2309
+ ir: workingIR,
2310
+ profile,
2311
+ archetype: ir.intent.archetype
2312
+ });
2313
+ workingIR = translated.rewrittenIR;
2314
+ const sectionRewritesApplied = translated.rewrites;
2315
+ let wireOverrides;
2316
+ for (const rw of sectionRewritesApplied) {
2317
+ if (!rw.wireOverrides) continue;
2318
+ if (!wireOverrides) wireOverrides = {};
2319
+ if (rw.wireOverrides.parallelToolCalls !== void 0) {
2320
+ wireOverrides.parallelToolCalls = rw.wireOverrides.parallelToolCalls;
2321
+ }
2322
+ }
2323
+ for (const rw of sectionRewritesApplied) {
2324
+ accumulatedMutations.push({
2325
+ id: `translator:${rw.rule}:${rw.sectionId}`,
2326
+ source: "translator",
2327
+ passName: "translator",
2328
+ description: `Rewrote section "${rw.sectionId}" (kind=${rw.kind}) via rule "${rw.rule}".`
2329
+ });
2330
+ }
792
2331
  const lowered = lower(workingIR, profile, {
793
2332
  forceThinkingZero: cliffs.value.loweringHints.forceThinkingZero,
794
- forceTerseOutput: cliffs.value.loweringHints.forceTerseOutput
2333
+ forceTerseOutput: cliffs.value.loweringHints.forceTerseOutput,
2334
+ wireOverrides
795
2335
  });
796
2336
  validateFinalFit(workingIR, profile, inputTokens);
797
2337
  const handle = makeHandle();
798
2338
  const finalShape = computeShape(workingIR, inputTokens);
799
2339
  const _learningKey = learningKey(ir.intent.archetype, profile.id, finalShape);
2340
+ const historyCacheMarkIndex = computeHistoryCacheMarkIndex(workingIR);
2341
+ const systemMessages = buildSystemMessages(workingIR, profile.provider);
2342
+ const systemCacheMarkIndex = lastCacheableSystemIndex(systemMessages);
800
2343
  const diagnostics = {
801
2344
  sectionsKept: workingIR.sections.length,
802
2345
  sectionsDropped: ir.sections.length - workingIR.sections.length,
@@ -806,9 +2349,39 @@ function compile(ir, opts = {}) {
806
2349
  historyDropped: (ir.history?.length ?? 0) - (workingIR.history?.length ?? 0),
807
2350
  cacheableTokens: lowered.diagnostics.cacheableTokens,
808
2351
  estimatedCacheSavingsUsd: lowered.diagnostics.estimatedCacheSavingsUsd,
809
- historyCacheableTokens: lowered.diagnostics.historyCacheableTokens
2352
+ historyCacheableTokens: lowered.diagnostics.historyCacheableTokens,
2353
+ historyTokensTotal: compressed.historyTokensTotal,
2354
+ // alpha.20 E3: mirror the consumer's declared mode for Glass-Box +
2355
+ // brain observability. Undefined when not declared (pre-alpha.20).
2356
+ toolOrchestration: ir.constraints?.toolOrchestration,
2357
+ // alpha.33 — see top-of-block comment.
2358
+ historyCacheMarkIndex,
2359
+ systemCacheMarkIndex,
2360
+ // alpha.43 — cliff-style warnings emitted by passApplyConventions.
2361
+ // Merge convention-pass cliffWarnings with any cliff-guard quality
2362
+ // warnings the cliff pass surfaced (the same shape — informational
2363
+ // text the consumer can route on without changing behavior).
2364
+ cliffWarnings: [
2365
+ ...cliffs.value.loweringHints.qualityWarning ?? [],
2366
+ ...conventions.value.cliffWarnings
2367
+ ]
810
2368
  };
811
- const advisories = runAdvisor(
2369
+ if (ir.intent.archetype === "hunt" && ir.constraints?.toolOrchestration === "sequential") {
2370
+ accumulatedMutations.push({
2371
+ id: "sequential-mode-chain-selected",
2372
+ source: "tool_orchestration",
2373
+ passName: "compile",
2374
+ 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)."
2375
+ });
2376
+ }
2377
+ const phase2ProfileResolver = opts.profileResolver ? (id) => {
2378
+ try {
2379
+ return opts.profileResolver(id);
2380
+ } catch {
2381
+ return void 0;
2382
+ }
2383
+ } : tryGetProfile;
2384
+ const rawAdvisories = runAdvisor(
812
2385
  ir,
813
2386
  {
814
2387
  target: profile.id,
@@ -816,8 +2389,24 @@ function compile(ir, opts = {}) {
816
2389
  tokensIn: inputTokens,
817
2390
  diagnostics
818
2391
  },
819
- profile
2392
+ profile,
2393
+ opts.policy,
2394
+ {
2395
+ fallbackChain,
2396
+ profileResolver: phase2ProfileResolver,
2397
+ // alpha.29 — feed translator rewrites to the advisor so the
2398
+ // `archetype-perf-floor-breach` rule can suppress when the translator
2399
+ // already cleared the cliff for the same archetype. Without this,
2400
+ // both the rewrite AND the advisory fire — noisy, and the advisory
2401
+ // would mislead consumers into thinking the cliff is unaddressed.
2402
+ sectionRewritesApplied
2403
+ }
820
2404
  );
2405
+ const advisories = rawAdvisories.map((a) => ({
2406
+ ...a,
2407
+ kgautoRequestId: handle,
2408
+ ownership: deriveOwnership(a.code, a.ownership)
2409
+ }));
821
2410
  return {
822
2411
  handle,
823
2412
  target: profile.id,
@@ -828,9 +2417,47 @@ function compile(ir, opts = {}) {
828
2417
  mutationsApplied: accumulatedMutations,
829
2418
  fallbackChain,
830
2419
  advisories,
831
- diagnostics
2420
+ diagnostics,
2421
+ sectionRewritesApplied,
2422
+ wireOverrides,
2423
+ systemMessages
832
2424
  };
833
2425
  }
2426
+ function computeHistoryCacheMarkIndex(ir) {
2427
+ const policy = ir.historyCachePolicy;
2428
+ if (!policy || policy.strategy === "none") return void 0;
2429
+ const historyLen = ir.history?.length ?? 0;
2430
+ if (historyLen === 0) return void 0;
2431
+ if (policy.strategy === "all-but-latest") {
2432
+ return historyLen - 1;
2433
+ }
2434
+ if (policy.strategy === "fixed-suffix") {
2435
+ const idx = historyLen - 1 - policy.suffix;
2436
+ if (idx < 0) return void 0;
2437
+ return idx;
2438
+ }
2439
+ return void 0;
2440
+ }
2441
+ function buildSystemMessages(ir, provider) {
2442
+ const sections = ir.sections;
2443
+ if (!sections || sections.length === 0) return [];
2444
+ return sections.map((s) => {
2445
+ const base = { role: "system", content: s.text };
2446
+ if (provider === "anthropic" && s.cacheable) {
2447
+ base.providerOptions = { anthropic: { cacheControl: { type: "ephemeral" } } };
2448
+ }
2449
+ return base;
2450
+ });
2451
+ }
2452
+ function lastCacheableSystemIndex(systemMessages) {
2453
+ for (let i = systemMessages.length - 1; i >= 0; i--) {
2454
+ const entry = systemMessages[i];
2455
+ if (entry && entry.providerOptions?.anthropic?.cacheControl) {
2456
+ return i;
2457
+ }
2458
+ }
2459
+ return void 0;
2460
+ }
834
2461
  function validateIR(ir) {
835
2462
  if (!ir.appId) throw new Error("compile(): ir.appId is required");
836
2463
  if (!ir.intent || !ir.intent.archetype) {
@@ -843,6 +2470,29 @@ function validateIR(ir) {
843
2470
  throw new Error("compile(): ir.sections must be an array");
844
2471
  }
845
2472
  }
2473
+ function resolveModelEntries(ir) {
2474
+ const out = [];
2475
+ const seen = /* @__PURE__ */ new Set();
2476
+ for (const entry of ir.models) {
2477
+ let id;
2478
+ if (typeof entry === "string") {
2479
+ id = entry;
2480
+ } else if (entry && typeof entry === "object" && "family" in entry) {
2481
+ id = resolveFamilyEntry(entry.family, {
2482
+ archetype: ir.intent.archetype,
2483
+ appId: ir.appId
2484
+ });
2485
+ } else {
2486
+ throw new Error(
2487
+ `compile(): ir.models entry must be a string or { family: string }; got ${JSON.stringify(entry)}`
2488
+ );
2489
+ }
2490
+ if (seen.has(id)) continue;
2491
+ seen.add(id);
2492
+ out.push(id);
2493
+ }
2494
+ return { ...ir, models: out };
2495
+ }
846
2496
  function pickTarget(ir, scores) {
847
2497
  if (ir.constraints?.forceModel) {
848
2498
  const forced = scores.find((s) => s.modelId === ir.constraints.forceModel);
@@ -869,14 +2519,240 @@ function validateFinalFit(ir, profile, tokens) {
869
2519
  }
870
2520
  }
871
2521
 
2522
+ // src/pricing-brain.ts
2523
+ function isPricingRow(x) {
2524
+ if (!x || typeof x !== "object") return false;
2525
+ const r = x;
2526
+ 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";
2527
+ }
2528
+ function mapRowsToPricing(rows) {
2529
+ const out = [];
2530
+ for (const row of rows) {
2531
+ if (!isPricingRow(row)) continue;
2532
+ out.push({
2533
+ modelId: row.model_id,
2534
+ costInputPer1m: row.cost_input_per_1m,
2535
+ costOutputPer1m: row.cost_output_per_1m,
2536
+ cacheInputPer1m: row.cache_input_per_1m ?? void 0,
2537
+ cacheCreationPer1m: row.cache_creation_per_1m ?? void 0,
2538
+ validFrom: Date.parse(row.valid_from),
2539
+ validUntil: row.valid_until == null ? void 0 : Date.parse(row.valid_until),
2540
+ source: row.source ?? void 0
2541
+ });
2542
+ }
2543
+ return out;
2544
+ }
2545
+ function bundledPricing() {
2546
+ const out = [];
2547
+ for (const profile of allProfiles()) {
2548
+ out.push({
2549
+ modelId: profile.id,
2550
+ costInputPer1m: profile.costInputPer1m,
2551
+ costOutputPer1m: profile.costOutputPer1m,
2552
+ cacheInputPer1m: profile.lowering.cache.discount !== void 0 && profile.lowering.cache.discount > 0 ? profile.costInputPer1m * profile.lowering.cache.discount : void 0,
2553
+ validFrom: 0,
2554
+ validUntil: void 0,
2555
+ source: "profile_seed"
2556
+ });
2557
+ }
2558
+ return out;
2559
+ }
2560
+ var loadPricingFromBrain = createBrainQueryCache({
2561
+ table: "kgauto_pricing",
2562
+ mapRows: mapRowsToPricing,
2563
+ bundledFallback: bundledPricing
2564
+ });
2565
+ function resolvePricingAt(modelId, at = /* @__PURE__ */ new Date()) {
2566
+ const ts = at.getTime();
2567
+ const all = loadPricingFromBrain();
2568
+ let best;
2569
+ for (const row of all) {
2570
+ if (row.modelId !== modelId) continue;
2571
+ if (row.validFrom > ts) continue;
2572
+ if (row.validUntil !== void 0 && row.validUntil <= ts) continue;
2573
+ if (!best || row.validFrom > best.validFrom) best = row;
2574
+ }
2575
+ return best;
2576
+ }
2577
+
872
2578
  // src/brain.ts
2579
+ var DEFAULT_BRAIN_WRITE_TIMEOUT_MS = 1e4;
2580
+ function brainWriteFetch(config) {
2581
+ const base = config.fetchImpl ?? fetch;
2582
+ const timeoutMs = config.timeoutMs ?? DEFAULT_BRAIN_WRITE_TIMEOUT_MS;
2583
+ if (timeoutMs <= 0 || typeof AbortSignal === "undefined" || typeof AbortSignal.timeout !== "function") {
2584
+ return base;
2585
+ }
2586
+ return (input, init) => base(input, { ...init, signal: AbortSignal.timeout(timeoutMs) });
2587
+ }
873
2588
  var activeConfig;
874
2589
  function configureBrain(config) {
875
2590
  const endpoint = config.endpoint.replace(/\/outcomes\/?$/, "");
876
2591
  activeConfig = { ...config, endpoint };
2592
+ const bq = config.brainQuery ?? {};
2593
+ const enabledTables = /* @__PURE__ */ new Set();
2594
+ if (bq.chains !== false) enabledTables.add("kgauto_chains");
2595
+ if (bq.perf !== false) enabledTables.add("kgauto_archetype_perf");
2596
+ if (bq.pricing !== false) enabledTables.add("kgauto_pricing");
2597
+ if (bq.models !== false) {
2598
+ enabledTables.add("kgauto_models");
2599
+ enabledTables.add("kgauto_aliases");
2600
+ }
2601
+ if (enabledTables.size === 0) {
2602
+ configureBrainQuery(void 0);
2603
+ } else {
2604
+ configureBrainQuery({
2605
+ endpoint,
2606
+ configEndpoint: bq.configEndpoint,
2607
+ ttlMs: bq.cacheTtlMs ?? 3e5,
2608
+ fetchImpl: config.fetchImpl ?? fetch,
2609
+ enabledTables,
2610
+ onError: config.onError
2611
+ });
2612
+ }
2613
+ if (bq.findingsExclusions !== false) {
2614
+ configureExclusionFindingsBrain({
2615
+ endpoint: bq.findingsEndpoint ?? DEFAULT_FINDINGS_ENDPOINT,
2616
+ ttlMs: bq.cacheTtlMs ?? 3e5,
2617
+ fetchImpl: config.fetchImpl ?? fetch,
2618
+ onError: config.onError
2619
+ });
2620
+ } else {
2621
+ configureExclusionFindingsBrain(void 0);
2622
+ }
877
2623
  }
878
2624
  function clearBrain() {
879
2625
  activeConfig = void 0;
2626
+ configureBrainQuery(void 0);
2627
+ configureExclusionFindingsBrain(void 0);
2628
+ }
2629
+ var DEAD_LETTER_MAX_ENTRIES = 50;
2630
+ var DEAD_LETTER_MAX_ATTEMPTS = 3;
2631
+ var ledger = {
2632
+ sent: 0,
2633
+ acked: 0,
2634
+ failed: 0,
2635
+ redirected: 0,
2636
+ lastAckAt: void 0,
2637
+ lastFailureAt: void 0,
2638
+ lastFailure: void 0
2639
+ };
2640
+ var deadLetter = [];
2641
+ var flushing = false;
2642
+ function noteSent() {
2643
+ ledger.sent += 1;
2644
+ }
2645
+ function noteAck() {
2646
+ ledger.acked += 1;
2647
+ ledger.lastAckAt = (/* @__PURE__ */ new Date()).toISOString();
2648
+ }
2649
+ function noteFailure(err) {
2650
+ ledger.failed += 1;
2651
+ ledger.lastFailureAt = (/* @__PURE__ */ new Date()).toISOString();
2652
+ const msg = err instanceof Error ? err.message : String(err);
2653
+ ledger.lastFailure = msg.length > 300 ? `${msg.slice(0, 300)}\u2026` : msg;
2654
+ }
2655
+ function assertNotRedirected(res, route) {
2656
+ if (!res.redirected) return;
2657
+ ledger.redirected += 1;
2658
+ throw new Error(
2659
+ `brain ${route} POST was redirected to ${res.url} \u2014 the write did NOT land. Likely cause: consumer middleware (auth/login allowlist) intercepts the brain proxy path. Add your kgauto proxy routes to the middleware public paths (the 2026-07-02 tt-intel/IC middleware-drift class).`
2660
+ );
2661
+ }
2662
+ function pushDeadLetter(route, endpoint, payload, err) {
2663
+ if (deadLetter.length >= DEAD_LETTER_MAX_ENTRIES) deadLetter.shift();
2664
+ deadLetter.push({
2665
+ route,
2666
+ endpoint,
2667
+ payload,
2668
+ failedAt: (/* @__PURE__ */ new Date()).toISOString(),
2669
+ attempts: 1,
2670
+ lastError: err instanceof Error ? err.message : String(err)
2671
+ });
2672
+ }
2673
+ function maybeAutoFlush() {
2674
+ if (deadLetter.length === 0 || flushing) return;
2675
+ void flushBrainDeadLetter();
2676
+ }
2677
+ function brainHealth() {
2678
+ let endpointHost;
2679
+ if (activeConfig) {
2680
+ try {
2681
+ endpointHost = new URL(activeConfig.endpoint).host;
2682
+ } catch {
2683
+ endpointHost = void 0;
2684
+ }
2685
+ }
2686
+ return {
2687
+ configState: activeConfig ? "configured" : "not_configured",
2688
+ endpointHost,
2689
+ sync: activeConfig?.sync === true,
2690
+ sent: ledger.sent,
2691
+ acked: ledger.acked,
2692
+ failed: ledger.failed,
2693
+ redirected: ledger.redirected,
2694
+ lastAckAt: ledger.lastAckAt,
2695
+ lastFailureAt: ledger.lastFailureAt,
2696
+ lastFailure: ledger.lastFailure,
2697
+ deadLetterCount: deadLetter.length
2698
+ };
2699
+ }
2700
+ function peekBrainDeadLetter() {
2701
+ return deadLetter.slice();
2702
+ }
2703
+ async function flushBrainDeadLetter() {
2704
+ if (!activeConfig || flushing || deadLetter.length === 0) {
2705
+ return { attempted: 0, delivered: 0, remaining: deadLetter.length };
2706
+ }
2707
+ flushing = true;
2708
+ const config = activeConfig;
2709
+ const fetchFn = brainWriteFetch(config);
2710
+ let attempted = 0;
2711
+ let delivered = 0;
2712
+ try {
2713
+ const batch = deadLetter.filter((e) => e.endpoint === config.endpoint);
2714
+ for (const entry of batch) {
2715
+ attempted += 1;
2716
+ noteSent();
2717
+ try {
2718
+ const res = await fetchFn(`${config.endpoint}/${entry.route}`, {
2719
+ method: "POST",
2720
+ headers: {
2721
+ "Content-Type": "application/json",
2722
+ // Replay never chains a secondary — minimal is correct for all routes.
2723
+ Prefer: "return=minimal",
2724
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
2725
+ },
2726
+ body: JSON.stringify(entry.payload)
2727
+ });
2728
+ assertNotRedirected(res, entry.route);
2729
+ if (!res.ok) {
2730
+ const text = await res.text().catch(() => "<no body>");
2731
+ throw new Error(describeBrainWriteFailure(res.status, entry.route, text));
2732
+ }
2733
+ noteAck();
2734
+ delivered += 1;
2735
+ const idx = deadLetter.indexOf(entry);
2736
+ if (idx !== -1) deadLetter.splice(idx, 1);
2737
+ } catch (err) {
2738
+ noteFailure(err);
2739
+ entry.attempts += 1;
2740
+ entry.lastError = err instanceof Error ? err.message : String(err);
2741
+ if (entry.attempts >= DEAD_LETTER_MAX_ATTEMPTS) {
2742
+ const idx = deadLetter.indexOf(entry);
2743
+ if (idx !== -1) deadLetter.splice(idx, 1);
2744
+ (config.onError ?? defaultOnError4)(
2745
+ new Error(
2746
+ `brain dead-letter: dropping ${entry.route} payload after ${entry.attempts} attempts \u2014 last error: ${entry.lastError}`
2747
+ )
2748
+ );
2749
+ }
2750
+ }
2751
+ }
2752
+ } finally {
2753
+ flushing = false;
2754
+ }
2755
+ return { attempted, delivered, remaining: deadLetter.length };
880
2756
  }
881
2757
  var compileRegistry = /* @__PURE__ */ new Map();
882
2758
  var REGISTRY_MAX_ENTRIES = 1e4;
@@ -903,6 +2779,9 @@ function registerCompile(appId, archetype, ir, result) {
903
2779
  tokens
904
2780
  );
905
2781
  const shapeKey = `${shape.contextBucket}-${shape.toolCountBucket}-${shape.historyDepth}-${shape.outputMode}`;
2782
+ const toolsCount = result.diagnostics.toolsKept;
2783
+ const historyDepth = Array.isArray(ir.history) ? ir.history.length : 0;
2784
+ const systemPromptChars = estimateSystemPromptChars(ir.sections);
906
2785
  compileRegistry.set(result.handle, {
907
2786
  appId,
908
2787
  archetype,
@@ -912,35 +2791,135 @@ function registerCompile(appId, archetype, ir, result) {
912
2791
  learningKey: learningKey(archetype, result.target, shape),
913
2792
  estimatedTokensIn: tokens,
914
2793
  mutationsApplied: result.mutationsApplied.map((m) => m.id),
2794
+ // alpha.30: cache the in-memory advisories so record() can auto-persist
2795
+ // to `compile_outcome_advisories` without consumer-side threading.
2796
+ advisoriesFromCompile: result.advisories ?? [],
915
2797
  startedAt: Date.now(),
916
- historyCacheableTokens: result.diagnostics.historyCacheableTokens
2798
+ historyCacheableTokens: result.diagnostics.historyCacheableTokens,
2799
+ historyTokensTotal: result.diagnostics.historyTokensTotal,
2800
+ // alpha.20 E3: capture consumer's declared mode for the brain payload.
2801
+ toolOrchestration: result.diagnostics.toolOrchestration,
2802
+ // alpha.28: shape fields for Glass-Box renderer.
2803
+ toolsCount,
2804
+ historyDepth,
2805
+ systemPromptChars,
2806
+ // alpha.29: translator activity — persisted on the brain row so
2807
+ // cross-app aggregates can answer "Sonnet narration rule fired N times,
2808
+ // outcome quality lifted to M."
2809
+ sectionRewritesApplied: result.sectionRewritesApplied,
2810
+ // alpha.57: cache the declared effort tier for record()'s auto-enrich.
2811
+ effortFromCompile: ir.constraints?.effort
917
2812
  });
918
2813
  }
2814
+ function estimateSystemPromptChars(sections) {
2815
+ if (!Array.isArray(sections) || sections.length === 0) return void 0;
2816
+ let total = 0;
2817
+ for (const s of sections) {
2818
+ if (s && typeof s === "object") {
2819
+ const content = s.content;
2820
+ if (typeof content === "string") total += content.length;
2821
+ }
2822
+ }
2823
+ return total > 0 ? total : void 0;
2824
+ }
2825
+ function armCompileConsumeTracking(handle) {
2826
+ const reg = compileRegistry.get(handle);
2827
+ if (!reg) return;
2828
+ reg.consumeTracked = true;
2829
+ reg.systemConsumed = false;
2830
+ reg.modelConsumed = false;
2831
+ reg.rawConsumed = false;
2832
+ }
2833
+ function markCompileConsumed(handle, field) {
2834
+ const reg = compileRegistry.get(handle);
2835
+ if (!reg || !reg.consumeTracked) return;
2836
+ if (field === "system") reg.systemConsumed = true;
2837
+ else if (field === "model") reg.modelConsumed = true;
2838
+ else reg.rawConsumed = true;
2839
+ }
2840
+ function maybeWarnDiscardedCompile(reg, handle) {
2841
+ if (!reg.consumeTracked) return;
2842
+ if (reg.systemConsumed || reg.modelConsumed || reg.rawConsumed) return;
2843
+ const isProd = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
2844
+ const enabled = activeConfig?.warnOnDiscardedCompile ?? !isProd;
2845
+ if (!enabled) return;
2846
+ console.warn(
2847
+ `[kgauto] compile-then-discard: compileForAISDKv6() output for handle ${handle} (archetype=${reg.archetype}, model=${reg.model}) was recorded but neither .system, .model, nor .raw was read before record(). The brain row's mutations_applied / estimated_tokens_in / system_prompt_chars describe a compile that did NOT shape the served call. Pass compiled.system + compiled.model into your generateText/streamText (or use call()). Silence via configureBrain({ warnOnDiscardedCompile: false }).`
2848
+ );
2849
+ }
919
2850
  async function record(input) {
920
2851
  const reg = compileRegistry.get(input.handle);
921
- if (reg) compileRegistry.delete(input.handle);
2852
+ if (reg) {
2853
+ maybeWarnDiscardedCompile(reg, input.handle);
2854
+ compileRegistry.delete(input.handle);
2855
+ }
922
2856
  if (!activeConfig) {
923
2857
  return;
924
2858
  }
925
2859
  const payload = buildPayload(input, reg);
926
2860
  const config = activeConfig;
927
- const fetchFn = config.fetchImpl ?? fetch;
2861
+ const fetchFn = brainWriteFetch(config);
928
2862
  const send = async () => {
2863
+ let outcomeId;
2864
+ noteSent();
929
2865
  try {
930
2866
  const res = await fetchFn(`${config.endpoint}/outcomes`, {
931
2867
  method: "POST",
932
2868
  headers: {
933
2869
  "Content-Type": "application/json",
2870
+ // alpha.20: request the inserted row back so we can JOIN advisories
2871
+ // to it via outcome_id. PostgREST returns the row when
2872
+ // `Prefer: return=representation` is set; proxies that pass the
2873
+ // header through (the recommended `const row = { ...body }` shape
2874
+ // from OutcomePayload's forward-compat rule) will surface
2875
+ // the row id. Proxies that don't (legacy / hand-rolled shapes)
2876
+ // simply produce no parseable id → secondary advisory POST is
2877
+ // skipped silently. Best-effort — primary outcome row is the
2878
+ // load-bearing write.
2879
+ Prefer: "return=representation",
934
2880
  ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
935
2881
  },
936
2882
  body: JSON.stringify(payload)
937
2883
  });
2884
+ assertNotRedirected(res, "outcomes");
2885
+ if (!res.ok) {
2886
+ const text = await res.text().catch(() => "<no body>");
2887
+ throw new Error(describeBrainWriteFailure(res.status, "outcomes", text));
2888
+ }
2889
+ noteAck();
2890
+ outcomeId = await tryExtractOutcomeId(res);
2891
+ } catch (err) {
2892
+ noteFailure(err);
2893
+ pushDeadLetter("outcomes", config.endpoint, payload, err);
2894
+ (config.onError ?? defaultOnError4)(err);
2895
+ return;
2896
+ }
2897
+ maybeAutoFlush();
2898
+ const advisories = input.advisories ?? reg?.advisoriesFromCompile;
2899
+ if (!advisories || advisories.length === 0) return;
2900
+ if (outcomeId === void 0) return;
2901
+ const advisoryPayload = advisories.map((a) => buildAdvisoryRow(outcomeId, a));
2902
+ noteSent();
2903
+ try {
2904
+ const res = await fetchFn(`${config.endpoint}/compile_outcome_advisories`, {
2905
+ method: "POST",
2906
+ headers: {
2907
+ "Content-Type": "application/json",
2908
+ Prefer: "return=minimal",
2909
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
2910
+ },
2911
+ body: JSON.stringify(advisoryPayload)
2912
+ });
2913
+ assertNotRedirected(res, "compile_outcome_advisories");
938
2914
  if (!res.ok) {
939
2915
  const text = await res.text().catch(() => "<no body>");
940
- throw new Error(`brain ${res.status}: ${text}`);
2916
+ throw new Error(`brain advisories ${res.status}: ${text}`);
941
2917
  }
2918
+ noteAck();
942
2919
  } catch (err) {
943
- (config.onError ?? defaultOnError)(err);
2920
+ noteFailure(err);
2921
+ pushDeadLetter("compile_outcome_advisories", config.endpoint, advisoryPayload, err);
2922
+ (config.onError ?? defaultOnError4)(err);
944
2923
  }
945
2924
  };
946
2925
  if (config.sync) {
@@ -949,9 +2928,14 @@ async function record(input) {
949
2928
  void send();
950
2929
  }
951
2930
  }
952
- function defaultOnError(err) {
2931
+ function defaultOnError4(err) {
953
2932
  console.warn("[kgauto] brain record failed:", err);
954
2933
  }
2934
+ function describeBrainWriteFailure(status, route, body) {
2935
+ const base = `brain ${route} ${status}: ${body}`;
2936
+ if (status !== 404 && status !== 405) return base;
2937
+ return `${base} \u2014 likely cause: the brain proxy at your KGAUTO_V2_BRAIN_URL is missing the /${route} forward route. Self-proxying consumers must implement ALL THREE sibling routes (/outcomes, /probe_outcomes, /compile_outcome_quality) \u2014 see interfaces/kgauto.md "Consumer adoption checklist" route-completeness (L-142).`;
2938
+ }
955
2939
  function buildPayload(input, reg) {
956
2940
  const compileTarget = reg?.model;
957
2941
  const actual = input.actualModel ?? compileTarget;
@@ -959,6 +2943,8 @@ function buildPayload(input, reg) {
959
2943
  const mutationsApplied = input.mutationsApplied ?? reg?.mutationsApplied ?? [];
960
2944
  const costModel = actual;
961
2945
  const costUsdActual = costModel ? computeCostUsd(costModel, input.tokensIn, input.tokensOut) : void 0;
2946
+ const fellOverFrom = input.fellOverFrom ?? requested;
2947
+ const fallbackReason = fellOverFrom ? input.fallbackReason : void 0;
962
2948
  return {
963
2949
  handle: input.handle,
964
2950
  app_id: reg?.appId,
@@ -987,17 +2973,193 @@ function buildPayload(input, reg) {
987
2973
  cache_creation_input_tokens: input.cacheCreationInputTokens,
988
2974
  cost_usd_actual: costUsdActual,
989
2975
  ttft_ms: input.ttftMs,
990
- history_cacheable_tokens: reg?.historyCacheableTokens
2976
+ history_cacheable_tokens: reg?.historyCacheableTokens,
2977
+ // alpha.57 (data-first): input wins over compile-declared; undefined
2978
+ // end-to-end -> key absent from the JSON body entirely (safe against
2979
+ // brains that haven't applied migration 033 yet).
2980
+ effort: input.effort ?? reg?.effortFromCompile,
2981
+ history_tokens_at_compile: reg?.historyTokensTotal,
2982
+ // alpha.20 E3: mirror consumer's declared tool-orchestration mode so
2983
+ // the brain can measure per-mode model perf separately (DeepSeek in
2984
+ // sequential vs parallel mode is two different stories — L-040).
2985
+ // Null when consumer hadn't adopted the constraint yet.
2986
+ tool_orchestration: reg?.toolOrchestration ?? null,
2987
+ // alpha.28 — Glass-Box renderer substrate (migration 018). All optional;
2988
+ // omitted-undefined PostgREST inserts store NULL → renderer renders "—".
2989
+ finish_reason: input.finishReason,
2990
+ total_ms: input.totalMs ?? input.latencyMs,
2991
+ tools_count: input.toolsCount ?? reg?.toolsCount,
2992
+ history_depth: input.historyDepth ?? reg?.historyDepth,
2993
+ system_prompt_chars: input.systemPromptChars ?? reg?.systemPromptChars,
2994
+ fell_over_from: fellOverFrom,
2995
+ fallback_reason: fallbackReason,
2996
+ // alpha.29 — translator activity (migration 019). Send NULL when no
2997
+ // rewrites fired so the brain's "did the translator do anything?"
2998
+ // queries can use `IS NOT NULL` cleanly.
2999
+ section_rewrites_applied: reg?.sectionRewritesApplied && reg.sectionRewritesApplied.length > 0 ? reg.sectionRewritesApplied : null
991
3000
  };
992
3001
  }
993
3002
  function computeCostUsd(modelId, tokensIn, tokensOut) {
994
3003
  if (tokensIn === 0 && tokensOut === 0) return void 0;
3004
+ const brainRow = resolvePricingAt(modelId);
3005
+ if (brainRow && (brainRow.costInputPer1m > 0 || brainRow.costOutputPer1m > 0)) {
3006
+ const inUsd2 = tokensIn / 1e6 * brainRow.costInputPer1m;
3007
+ const outUsd2 = tokensOut / 1e6 * brainRow.costOutputPer1m;
3008
+ return Math.round((inUsd2 + outUsd2) * 1e6) / 1e6;
3009
+ }
995
3010
  const profile = tryGetProfile(modelId);
996
3011
  if (!profile) return void 0;
997
3012
  const inUsd = tokensIn / 1e6 * profile.costInputPer1m;
998
3013
  const outUsd = tokensOut / 1e6 * profile.costOutputPer1m;
999
3014
  return Math.round((inUsd + outUsd) * 1e6) / 1e6;
1000
3015
  }
3016
+ async function tryExtractOutcomeId(res) {
3017
+ try {
3018
+ const ct = res.headers?.get?.("content-type") ?? "";
3019
+ if (ct && !ct.includes("application/json")) return void 0;
3020
+ if (typeof res.json !== "function") return void 0;
3021
+ const body = await res.json();
3022
+ if (Array.isArray(body) && body.length > 0) {
3023
+ const first = body[0];
3024
+ const id = first?.id;
3025
+ if (typeof id === "number") return id;
3026
+ } else if (body && typeof body === "object") {
3027
+ const id = body.id;
3028
+ if (typeof id === "number") return id;
3029
+ }
3030
+ return void 0;
3031
+ } catch {
3032
+ return void 0;
3033
+ }
3034
+ }
3035
+ function buildAdvisoryRow(outcomeId, a) {
3036
+ return {
3037
+ outcome_id: outcomeId,
3038
+ code: a.code,
3039
+ level: a.level,
3040
+ message: a.message,
3041
+ recommendation_type: a.recommendationType ?? null,
3042
+ suggestion: a.suggestion ?? null,
3043
+ docs_url: a.docsUrl ?? null
3044
+ };
3045
+ }
3046
+ async function recordOutcome(input) {
3047
+ if (!activeConfig) {
3048
+ return { ok: false, reason: "brain_not_configured" };
3049
+ }
3050
+ const config = activeConfig;
3051
+ const fetchFn = brainWriteFetch(config);
3052
+ const payload = {
3053
+ outcome_id: input.outcomeId,
3054
+ outcome: input.outcome,
3055
+ rating: input.rating ?? null,
3056
+ reason: input.reason ?? null,
3057
+ observed_confidence: input.observedConfidence ?? null
3058
+ };
3059
+ const send = async () => {
3060
+ noteSent();
3061
+ try {
3062
+ const res = await fetchFn(`${config.endpoint}/compile_outcome_quality`, {
3063
+ method: "POST",
3064
+ headers: {
3065
+ "Content-Type": "application/json",
3066
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
3067
+ },
3068
+ body: JSON.stringify(payload)
3069
+ });
3070
+ assertNotRedirected(res, "compile_outcome_quality");
3071
+ if (!res.ok) {
3072
+ const text = await res.text().catch(() => "<no body>");
3073
+ throw new Error(
3074
+ describeBrainWriteFailure(res.status, "compile_outcome_quality", text)
3075
+ );
3076
+ }
3077
+ noteAck();
3078
+ maybeAutoFlush();
3079
+ return { ok: true };
3080
+ } catch (err) {
3081
+ noteFailure(err);
3082
+ pushDeadLetter("compile_outcome_quality", config.endpoint, payload, err);
3083
+ (config.onError ?? defaultOnError4)(err);
3084
+ return { ok: false, reason: "persistence_failed" };
3085
+ }
3086
+ };
3087
+ if (config.sync) {
3088
+ return send();
3089
+ }
3090
+ void send();
3091
+ return { ok: "queued" };
3092
+ }
3093
+ function isBrainSync() {
3094
+ return activeConfig?.sync === true;
3095
+ }
3096
+ function buildShadowProbeRow(input) {
3097
+ return {
3098
+ app_id: input.appId,
3099
+ intent_archetype: input.archetype,
3100
+ family: input.family,
3101
+ candidate_model: input.candidateModel,
3102
+ current_model: input.currentModel,
3103
+ prompt_hash: input.promptHash,
3104
+ current_response: input.currentResponsePreview ?? null,
3105
+ candidate_response: input.candidateResponsePreview ?? null,
3106
+ judge_verdict: null,
3107
+ judge_score: null,
3108
+ tokens_current_in: input.tokensCurrentIn ?? null,
3109
+ tokens_current_out: input.tokensCurrentOut ?? null,
3110
+ tokens_candidate_in: input.tokensCandidateIn ?? null,
3111
+ tokens_candidate_out: input.tokensCandidateOut ?? null,
3112
+ latency_current_ms: input.latencyCurrentMs ?? null,
3113
+ latency_candidate_ms: input.latencyCandidateMs ?? null,
3114
+ // Full IR was replayed (not a truncated preview), so fidelity is 1.0 — the
3115
+ // prompt-fidelity guard never fires on these rows.
3116
+ prompt_fidelity: 1,
3117
+ replay_source: "inline-full-ir",
3118
+ // alpha — migration 029. 'completed' is the default completed-probe shape;
3119
+ // diagnostic rows (aborted/skipped/errored) pass the explicit class.
3120
+ outcome: input.outcome ?? "completed",
3121
+ // alpha.53 — migration 032. Populated only on candidate_error rows.
3122
+ // Self-proxying consumers must whitelist `error_class` (and `outcome`,
3123
+ // per the alpha.51 PB finding) or diagnostic rows degrade silently.
3124
+ error_class: input.errorClass ?? null
3125
+ };
3126
+ }
3127
+ async function recordShadowProbe(input) {
3128
+ if (!activeConfig) return;
3129
+ const config = activeConfig;
3130
+ const fetchFn = brainWriteFetch(config);
3131
+ const row = buildShadowProbeRow(input);
3132
+ const send = async () => {
3133
+ noteSent();
3134
+ try {
3135
+ const res = await fetchFn(`${config.endpoint}/probe_outcomes`, {
3136
+ method: "POST",
3137
+ headers: {
3138
+ "Content-Type": "application/json",
3139
+ Prefer: "return=minimal",
3140
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
3141
+ },
3142
+ body: JSON.stringify(row)
3143
+ });
3144
+ assertNotRedirected(res, "probe_outcomes");
3145
+ if (!res.ok) {
3146
+ const text = await res.text().catch(() => "<no body>");
3147
+ throw new Error(describeBrainWriteFailure(res.status, "probe_outcomes", text));
3148
+ }
3149
+ noteAck();
3150
+ maybeAutoFlush();
3151
+ } catch (err) {
3152
+ noteFailure(err);
3153
+ pushDeadLetter("probe_outcomes", config.endpoint, row, err);
3154
+ (config.onError ?? defaultOnError4)(err);
3155
+ }
3156
+ };
3157
+ if (config.sync) {
3158
+ await send();
3159
+ } else {
3160
+ void send();
3161
+ }
3162
+ }
1001
3163
 
1002
3164
  // src/ir.ts
1003
3165
  var CallError = class extends Error {
@@ -1011,10 +3173,297 @@ var CallError = class extends Error {
1011
3173
  this.lastStatus = lastStatus;
1012
3174
  this.lastErrorCode = lastErrorCode;
1013
3175
  }
1014
- };
3176
+ };
3177
+
3178
+ // src/streaming.ts
3179
+ var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
3180
+ async function streamAnthropic(request, apiKey, opts) {
3181
+ const { provider: _provider, ...body } = request;
3182
+ const fetchFn = opts.fetchImpl ?? fetch;
3183
+ let res;
3184
+ try {
3185
+ res = await fetchFn(ANTHROPIC_URL, {
3186
+ method: "POST",
3187
+ headers: {
3188
+ "x-api-key": apiKey,
3189
+ "anthropic-version": "2023-06-01",
3190
+ "content-type": "application/json"
3191
+ },
3192
+ body: JSON.stringify({ ...body, stream: true })
3193
+ });
3194
+ } catch (err) {
3195
+ return retryableError(0, "network_error", String(err), null);
3196
+ }
3197
+ if (!res.ok) {
3198
+ const errBody = await res.json().catch(() => ({}));
3199
+ return classifyHttpError(res.status, errBody);
3200
+ }
3201
+ let text = "";
3202
+ let inputTokens = 0;
3203
+ let outputTokens = 0;
3204
+ let cacheReadTokens;
3205
+ let cacheCreatedTokens;
3206
+ let stopReason;
3207
+ const toolBlocks = /* @__PURE__ */ new Map();
3208
+ try {
3209
+ await parseSSEStream(res, (event) => {
3210
+ if (!event.data) return;
3211
+ let payload;
3212
+ try {
3213
+ payload = JSON.parse(event.data);
3214
+ } catch {
3215
+ return;
3216
+ }
3217
+ const type = payload.type;
3218
+ if (type === "message_start") {
3219
+ const msg = payload.message;
3220
+ const usage = msg?.usage;
3221
+ if (usage) {
3222
+ inputTokens = usage.input_tokens ?? 0;
3223
+ outputTokens = usage.output_tokens ?? 0;
3224
+ if (typeof usage.cache_read_input_tokens === "number")
3225
+ cacheReadTokens = usage.cache_read_input_tokens;
3226
+ if (typeof usage.cache_creation_input_tokens === "number")
3227
+ cacheCreatedTokens = usage.cache_creation_input_tokens;
3228
+ }
3229
+ return;
3230
+ }
3231
+ if (type === "content_block_start") {
3232
+ const p = payload;
3233
+ const idx = p.index ?? 0;
3234
+ const block = p.content_block;
3235
+ if (block?.type === "tool_use" && block.id && block.name) {
3236
+ toolBlocks.set(idx, { id: block.id, name: block.name, argsJson: "" });
3237
+ }
3238
+ return;
3239
+ }
3240
+ if (type === "content_block_delta") {
3241
+ const p = payload;
3242
+ const delta = p.delta;
3243
+ if (!delta) return;
3244
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
3245
+ text += delta.text;
3246
+ opts.onChunk(delta.text);
3247
+ } else if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
3248
+ const idx = p.index ?? 0;
3249
+ const tool = toolBlocks.get(idx);
3250
+ if (tool) tool.argsJson += delta.partial_json;
3251
+ }
3252
+ return;
3253
+ }
3254
+ if (type === "message_delta") {
3255
+ const p = payload;
3256
+ if (p.delta?.stop_reason) stopReason = p.delta.stop_reason;
3257
+ if (typeof p.usage?.output_tokens === "number") outputTokens = p.usage.output_tokens;
3258
+ return;
3259
+ }
3260
+ });
3261
+ } catch (err) {
3262
+ return retryableError(0, "stream_interrupted", String(err), null);
3263
+ }
3264
+ const toolCalls = Array.from(toolBlocks.values()).map((b) => ({
3265
+ id: b.id,
3266
+ name: b.name,
3267
+ args: tryParseJson(b.argsJson) ?? {}
3268
+ }));
3269
+ const tokens = {
3270
+ input: inputTokens,
3271
+ output: outputTokens,
3272
+ total: inputTokens + outputTokens,
3273
+ cached: cacheReadTokens,
3274
+ cacheCreated: cacheCreatedTokens
3275
+ };
3276
+ const response = {
3277
+ text,
3278
+ structuredOutput: null,
3279
+ toolCalls,
3280
+ tokens,
3281
+ finishReason: stopReason,
3282
+ raw: { streamed: true, provider: "anthropic" }
3283
+ };
3284
+ return { ok: true, status: res.status, response };
3285
+ }
3286
+ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
3287
+ const { provider: _provider, ...body } = request;
3288
+ const fetchFn = opts.fetchImpl ?? fetch;
3289
+ const reqBody = {
3290
+ ...body,
3291
+ stream: true,
3292
+ stream_options: {
3293
+ ...body.stream_options ?? {},
3294
+ include_usage: true
3295
+ }
3296
+ };
3297
+ let res;
3298
+ try {
3299
+ res = await fetchFn(url, {
3300
+ method: "POST",
3301
+ headers: {
3302
+ authorization: `Bearer ${apiKey}`,
3303
+ "content-type": "application/json"
3304
+ },
3305
+ body: JSON.stringify(reqBody)
3306
+ });
3307
+ } catch (err) {
3308
+ return retryableError(0, "network_error", String(err), null);
3309
+ }
3310
+ if (!res.ok) {
3311
+ const errBody = await res.json().catch(() => ({}));
3312
+ return classifyHttpError(res.status, errBody);
3313
+ }
3314
+ let text = "";
3315
+ let finishReason;
3316
+ let inputTokens = 0;
3317
+ let outputTokens = 0;
3318
+ let totalTokens;
3319
+ let cachedTokens;
3320
+ const toolBuffers = /* @__PURE__ */ new Map();
3321
+ try {
3322
+ await parseSSEStream(res, (event) => {
3323
+ if (!event.data) return;
3324
+ if (event.data === "[DONE]") return;
3325
+ let payload;
3326
+ try {
3327
+ payload = JSON.parse(event.data);
3328
+ } catch {
3329
+ return;
3330
+ }
3331
+ const choices = payload.choices;
3332
+ if (choices && choices.length > 0) {
3333
+ const choice = choices[0];
3334
+ const deltaContent = choice.delta?.content;
3335
+ if (typeof deltaContent === "string" && deltaContent.length > 0) {
3336
+ text += deltaContent;
3337
+ opts.onChunk(deltaContent);
3338
+ }
3339
+ const toolDeltas = choice.delta?.tool_calls;
3340
+ if (toolDeltas) {
3341
+ for (const tcDelta of toolDeltas) {
3342
+ const idx = tcDelta.index ?? 0;
3343
+ let buf = toolBuffers.get(idx);
3344
+ if (!buf) {
3345
+ buf = { id: tcDelta.id ?? `tc-${idx}`, name: "", argsJson: "" };
3346
+ toolBuffers.set(idx, buf);
3347
+ }
3348
+ if (tcDelta.id) buf.id = tcDelta.id;
3349
+ if (tcDelta.function?.name) buf.name += tcDelta.function.name;
3350
+ if (tcDelta.function?.arguments) buf.argsJson += tcDelta.function.arguments;
3351
+ }
3352
+ }
3353
+ if (choice.finish_reason) finishReason = choice.finish_reason;
3354
+ }
3355
+ const usage = payload.usage;
3356
+ if (usage) {
3357
+ if (typeof usage.prompt_tokens === "number") inputTokens = usage.prompt_tokens;
3358
+ if (typeof usage.completion_tokens === "number") outputTokens = usage.completion_tokens;
3359
+ if (typeof usage.total_tokens === "number") totalTokens = usage.total_tokens;
3360
+ const details = usage.prompt_tokens_details;
3361
+ if (typeof details?.cached_tokens === "number") cachedTokens = details.cached_tokens;
3362
+ }
3363
+ });
3364
+ } catch (err) {
3365
+ return retryableError(0, "stream_interrupted", String(err), null);
3366
+ }
3367
+ const toolCalls = Array.from(toolBuffers.values()).filter((b) => b.name.length > 0).map((b) => ({
3368
+ id: b.id,
3369
+ name: b.name,
3370
+ args: tryParseJson(b.argsJson) ?? {}
3371
+ }));
3372
+ const tokens = {
3373
+ input: inputTokens,
3374
+ output: outputTokens,
3375
+ total: totalTokens ?? inputTokens + outputTokens,
3376
+ cached: cachedTokens
3377
+ };
3378
+ const response = {
3379
+ text,
3380
+ structuredOutput: null,
3381
+ toolCalls,
3382
+ tokens,
3383
+ finishReason,
3384
+ raw: { streamed: true, provider: providerLabel }
3385
+ };
3386
+ return { ok: true, status: res.status, response };
3387
+ }
3388
+ async function parseSSEStream(response, handler) {
3389
+ const body = response.body;
3390
+ if (!body) throw new Error("Response has no body for SSE parse");
3391
+ const reader = body.getReader();
3392
+ const decoder = new TextDecoder("utf-8");
3393
+ let buffer = "";
3394
+ for (; ; ) {
3395
+ const { value, done } = await reader.read();
3396
+ if (done) break;
3397
+ buffer += decoder.decode(value, { stream: true });
3398
+ let sep;
3399
+ while (sep = buffer.indexOf("\n\n"), sep !== -1) {
3400
+ const block = buffer.slice(0, sep);
3401
+ buffer = buffer.slice(sep + 2);
3402
+ const event = parseSSEBlock(block);
3403
+ if (event) handler(event);
3404
+ }
3405
+ }
3406
+ if (buffer.length > 0) {
3407
+ const event = parseSSEBlock(buffer);
3408
+ if (event) handler(event);
3409
+ }
3410
+ }
3411
+ function parseSSEBlock(block) {
3412
+ const lines = block.split(/\r?\n/);
3413
+ let eventName;
3414
+ const dataLines = [];
3415
+ for (const line of lines) {
3416
+ if (line.startsWith(":")) continue;
3417
+ if (line.startsWith("event:")) {
3418
+ eventName = line.slice(6).trim();
3419
+ } else if (line.startsWith("data:")) {
3420
+ dataLines.push(line.slice(5).trim());
3421
+ }
3422
+ }
3423
+ if (dataLines.length === 0) return void 0;
3424
+ return { event: eventName, data: dataLines.join("\n") };
3425
+ }
3426
+ function tryParseJson(s) {
3427
+ if (typeof s !== "string" || s.length === 0) return void 0;
3428
+ try {
3429
+ const parsed = JSON.parse(s);
3430
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
3431
+ } catch {
3432
+ return void 0;
3433
+ }
3434
+ }
3435
+ function classifyHttpError(status, body) {
3436
+ const message = extractErrorMessage(body) ?? `HTTP ${status}`;
3437
+ if (status === 429)
3438
+ return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
3439
+ if (status === 408)
3440
+ return { ok: false, status, errorType: "retryable", errorCode: "timeout", message, raw: body };
3441
+ if (status >= 500)
3442
+ return { ok: false, status, errorType: "retryable", errorCode: "server_error", message, raw: body };
3443
+ if (status === 404)
3444
+ return { ok: false, status, errorType: "retryable", errorCode: "model_not_found", message, raw: body };
3445
+ if (status === 401 || status === 403)
3446
+ return { ok: false, status, errorType: "terminal", errorCode: "auth", message, raw: body };
3447
+ if (status === 400)
3448
+ return { ok: false, status, errorType: "terminal", errorCode: "invalid_request", message, raw: body };
3449
+ return { ok: false, status, errorType: "terminal", errorCode: "unknown", message, raw: body };
3450
+ }
3451
+ function extractErrorMessage(body) {
3452
+ if (!body || typeof body !== "object") return void 0;
3453
+ const b = body;
3454
+ if (b.error && typeof b.error === "object") {
3455
+ const e = b.error;
3456
+ if (typeof e.message === "string") return e.message;
3457
+ }
3458
+ if (typeof b.message === "string") return b.message;
3459
+ return void 0;
3460
+ }
3461
+ function retryableError(status, code, message, raw) {
3462
+ return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
3463
+ }
1015
3464
 
1016
3465
  // src/execute.ts
1017
- var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
3466
+ var ANTHROPIC_URL2 = "https://api.anthropic.com/v1/messages";
1018
3467
  var OPENAI_URL = "https://api.openai.com/v1/chat/completions";
1019
3468
  var DEEPSEEK_URL = "https://api.deepseek.com/chat/completions";
1020
3469
  async function execute(request, opts = {}) {
@@ -1035,16 +3484,22 @@ async function execute(request, opts = {}) {
1035
3484
  }
1036
3485
  }
1037
3486
  async function executeAnthropic(request, opts) {
1038
- const apiKey = opts.apiKeys?.anthropic ?? process.env.ANTHROPIC_API_KEY;
3487
+ const apiKey = resolveProviderKey("anthropic", { apiKeys: opts.apiKeys });
1039
3488
  if (!apiKey) {
1040
3489
  return terminalError(401, "auth", "ANTHROPIC_API_KEY missing");
1041
3490
  }
3491
+ if (opts.onChunk) {
3492
+ return streamAnthropic(request, apiKey, {
3493
+ onChunk: opts.onChunk,
3494
+ fetchImpl: opts.fetchImpl
3495
+ });
3496
+ }
1042
3497
  const { provider: _provider, ...body } = request;
1043
3498
  const fetchFn = opts.fetchImpl ?? fetch;
1044
3499
  let res;
1045
3500
  let json;
1046
3501
  try {
1047
- res = await fetchFn(ANTHROPIC_URL, {
3502
+ res = await fetchFn(ANTHROPIC_URL2, {
1048
3503
  method: "POST",
1049
3504
  headers: {
1050
3505
  "x-api-key": apiKey,
@@ -1055,9 +3510,9 @@ async function executeAnthropic(request, opts) {
1055
3510
  });
1056
3511
  json = await res.json().catch(() => ({}));
1057
3512
  } catch (err) {
1058
- return retryableError(0, "network_error", String(err), null);
3513
+ return retryableError2(0, "network_error", String(err), null);
1059
3514
  }
1060
- if (!res.ok) return classifyHttpError(res.status, json);
3515
+ if (!res.ok) return classifyHttpError2(res.status, json);
1061
3516
  return { ok: true, status: res.status, response: normalizeAnthropic(json) };
1062
3517
  }
1063
3518
  function normalizeAnthropic(raw) {
@@ -1074,7 +3529,7 @@ function normalizeAnthropic(raw) {
1074
3529
  return { text, structuredOutput: null, toolCalls, tokens, finishReason: r.stop_reason, raw };
1075
3530
  }
1076
3531
  async function executeGoogle(request, opts) {
1077
- const apiKey = opts.apiKeys?.google ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
3532
+ const apiKey = resolveProviderKey("google", { apiKeys: opts.apiKeys });
1078
3533
  if (!apiKey) {
1079
3534
  return terminalError(401, "auth", "GOOGLE_API_KEY/GEMINI_API_KEY missing");
1080
3535
  }
@@ -1091,9 +3546,9 @@ async function executeGoogle(request, opts) {
1091
3546
  });
1092
3547
  json = await res.json().catch(() => ({}));
1093
3548
  } catch (err) {
1094
- return retryableError(0, "network_error", String(err), null);
3549
+ return retryableError2(0, "network_error", String(err), null);
1095
3550
  }
1096
- if (!res.ok) return classifyHttpError(res.status, json);
3551
+ if (!res.ok) return classifyHttpError2(res.status, json);
1097
3552
  return { ok: true, status: res.status, response: normalizeGoogle(json) };
1098
3553
  }
1099
3554
  function normalizeGoogle(raw) {
@@ -1116,10 +3571,16 @@ function normalizeGoogle(raw) {
1116
3571
  return { text, structuredOutput: null, toolCalls, tokens, finishReason: candidate?.finishReason, raw };
1117
3572
  }
1118
3573
  async function executeOpenAI(request, opts) {
1119
- const apiKey = opts.apiKeys?.openai ?? process.env.OPENAI_API_KEY;
3574
+ const apiKey = resolveProviderKey("openai", { apiKeys: opts.apiKeys });
1120
3575
  if (!apiKey) {
1121
3576
  return terminalError(401, "auth", "OPENAI_API_KEY missing");
1122
3577
  }
3578
+ if (opts.onChunk) {
3579
+ return streamOpenAILike(OPENAI_URL, request, apiKey, "openai", {
3580
+ onChunk: opts.onChunk,
3581
+ fetchImpl: opts.fetchImpl
3582
+ });
3583
+ }
1123
3584
  const { provider: _provider, ...body } = request;
1124
3585
  const fetchFn = opts.fetchImpl ?? fetch;
1125
3586
  let res;
@@ -1132,16 +3593,22 @@ async function executeOpenAI(request, opts) {
1132
3593
  });
1133
3594
  json = await res.json().catch(() => ({}));
1134
3595
  } catch (err) {
1135
- return retryableError(0, "network_error", String(err), null);
3596
+ return retryableError2(0, "network_error", String(err), null);
1136
3597
  }
1137
- if (!res.ok) return classifyHttpError(res.status, json);
3598
+ if (!res.ok) return classifyHttpError2(res.status, json);
1138
3599
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
1139
3600
  }
1140
3601
  async function executeDeepSeek(request, opts) {
1141
- const apiKey = opts.apiKeys?.deepseek ?? process.env.DEEPSEEK_API_KEY;
3602
+ const apiKey = resolveProviderKey("deepseek", { apiKeys: opts.apiKeys });
1142
3603
  if (!apiKey) {
1143
3604
  return terminalError(401, "auth", "DEEPSEEK_API_KEY missing");
1144
3605
  }
3606
+ if (opts.onChunk) {
3607
+ return streamOpenAILike(DEEPSEEK_URL, request, apiKey, "deepseek", {
3608
+ onChunk: opts.onChunk,
3609
+ fetchImpl: opts.fetchImpl
3610
+ });
3611
+ }
1145
3612
  const { provider: _provider, ...body } = request;
1146
3613
  const fetchFn = opts.fetchImpl ?? fetch;
1147
3614
  let res;
@@ -1154,9 +3621,9 @@ async function executeDeepSeek(request, opts) {
1154
3621
  });
1155
3622
  json = await res.json().catch(() => ({}));
1156
3623
  } catch (err) {
1157
- return retryableError(0, "network_error", String(err), null);
3624
+ return retryableError2(0, "network_error", String(err), null);
1158
3625
  }
1159
- if (!res.ok) return classifyHttpError(res.status, json);
3626
+ if (!res.ok) return classifyHttpError2(res.status, json);
1160
3627
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
1161
3628
  }
1162
3629
  function normalizeOpenAILike(raw) {
@@ -1166,7 +3633,7 @@ function normalizeOpenAILike(raw) {
1166
3633
  const toolCalls = (choice?.message?.tool_calls ?? []).filter((tc) => tc.function?.name).map((tc, i) => ({
1167
3634
  id: tc.id ?? `tc-${i}`,
1168
3635
  name: tc.function.name,
1169
- args: tryParseJson(tc.function?.arguments) ?? {}
3636
+ args: tryParseJson2(tc.function?.arguments) ?? {}
1170
3637
  }));
1171
3638
  const u = r.usage ?? {};
1172
3639
  const tokens = {
@@ -1183,8 +3650,8 @@ function applyOverrides(request, overrides) {
1183
3650
  if (!layer) return request;
1184
3651
  return { ...request, ...layer };
1185
3652
  }
1186
- function classifyHttpError(status, body) {
1187
- const message = extractErrorMessage(body) ?? `HTTP ${status}`;
3653
+ function classifyHttpError2(status, body) {
3654
+ const message = extractErrorMessage2(body) ?? `HTTP ${status}`;
1188
3655
  if (status === 429) {
1189
3656
  return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
1190
3657
  }
@@ -1205,7 +3672,7 @@ function classifyHttpError(status, body) {
1205
3672
  }
1206
3673
  return { ok: false, status, errorType: "terminal", errorCode: "unknown", message, raw: body };
1207
3674
  }
1208
- function extractErrorMessage(body) {
3675
+ function extractErrorMessage2(body) {
1209
3676
  if (!body || typeof body !== "object") return void 0;
1210
3677
  const b = body;
1211
3678
  if (b.error && typeof b.error === "object") {
@@ -1218,10 +3685,10 @@ function extractErrorMessage(body) {
1218
3685
  function terminalError(status, code, message) {
1219
3686
  return { ok: false, status, errorType: "terminal", errorCode: code, message, raw: null };
1220
3687
  }
1221
- function retryableError(status, code, message, raw) {
3688
+ function retryableError2(status, code, message, raw) {
1222
3689
  return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
1223
3690
  }
1224
- function tryParseJson(s) {
3691
+ function tryParseJson2(s) {
1225
3692
  if (typeof s !== "string" || s.length === 0) return void 0;
1226
3693
  try {
1227
3694
  const parsed = JSON.parse(s);
@@ -1233,15 +3700,148 @@ function tryParseJson(s) {
1233
3700
 
1234
3701
  // src/call.ts
1235
3702
  async function call(ir, opts = {}) {
3703
+ const traceId = generateTraceId();
3704
+ safeEmit(
3705
+ () => emitCompileStart(traceId, ir.appId, {
3706
+ appId: ir.appId,
3707
+ archetype: ir.intent.archetype,
3708
+ // alpha.41: ir.models is ChainModelEntry[] (string | { family }).
3709
+ // Glass-Box renderer carries string ids only; surface family entries
3710
+ // as their family tag prefixed so the panel still shows them in the
3711
+ // pre-resolution log.
3712
+ models: ir.models.map(
3713
+ (m) => typeof m === "string" ? m : `family:${m.family}`
3714
+ )
3715
+ })
3716
+ );
1236
3717
  const initial = compileAndRegister(ir, opts);
3718
+ safeEmit(
3719
+ () => emitCompileDone(traceId, ir.appId, {
3720
+ target: initial.target,
3721
+ provider: initial.provider,
3722
+ fallbackChain: initial.fallbackChain,
3723
+ tokensIn: initial.tokensIn,
3724
+ estimatedCostUsd: initial.estimatedCostUsd,
3725
+ mutationsApplied: initial.mutationsApplied,
3726
+ advisories: initial.advisories
3727
+ })
3728
+ );
3729
+ for (const adv of initial.advisories) {
3730
+ safeEmit(
3731
+ () => emitAdvisoryFired(traceId, ir.appId, { code: adv.code, message: adv.message })
3732
+ );
3733
+ }
1237
3734
  const start = Date.now();
1238
3735
  const attempts = [];
1239
- const targetsToTry = [initial.target, ...initial.fallbackChain];
3736
+ const rawTargets = [initial.target, ...initial.fallbackChain];
3737
+ let unreachableFiltered;
3738
+ let targetsToTry;
3739
+ if (opts.noAutoFilter) {
3740
+ targetsToTry = rawTargets;
3741
+ } else {
3742
+ const dropped = [];
3743
+ targetsToTry = [];
3744
+ for (const t of rawTargets) {
3745
+ if (isModelReachable(t, { apiKeys: opts.apiKeys })) {
3746
+ targetsToTry.push(t);
3747
+ } else {
3748
+ dropped.push(t);
3749
+ }
3750
+ }
3751
+ unreachableFiltered = dropped;
3752
+ if (targetsToTry.length === 0) {
3753
+ const latencyMs2 = Date.now() - start;
3754
+ await record({
3755
+ handle: initial.handle,
3756
+ tokensIn: 0,
3757
+ tokensOut: 0,
3758
+ latencyMs: latencyMs2,
3759
+ success: false,
3760
+ errorType: "no_reachable_models",
3761
+ promptPreview: extractPromptPreview(ir)
3762
+ });
3763
+ const noReachableAttempts = dropped.map((m) => ({
3764
+ model: m,
3765
+ status: "terminal",
3766
+ errorCode: "unreachable_provider",
3767
+ message: `No API key for ${m}'s provider \u2014 set one of PROVIDER_ENV_KEYS or pass apiKeys`
3768
+ }));
3769
+ throw new CallError(
3770
+ `call(): no reachable models in chain. Filtered: [${dropped.join(", ")}]. Add a key for one provider, or pass apiKeys.`,
3771
+ noReachableAttempts,
3772
+ void 0,
3773
+ "no_reachable_models"
3774
+ );
3775
+ }
3776
+ const archetypeName = ir.intent?.archetype;
3777
+ if (archetypeName) {
3778
+ const ensured = ensureCrossProviderTail({
3779
+ chain: targetsToTry,
3780
+ archetype: archetypeName,
3781
+ apiKeys: opts.apiKeys
3782
+ });
3783
+ if (ensured.appended) {
3784
+ targetsToTry = ensured.chain;
3785
+ }
3786
+ }
3787
+ }
3788
+ let policyBlockedFiltered;
3789
+ if (opts.policy?.blockedModels && opts.policy.blockedModels.length > 0) {
3790
+ const blocked = new Set(opts.policy.blockedModels);
3791
+ const filtered = [];
3792
+ const dropped = [];
3793
+ for (const t of targetsToTry) {
3794
+ if (blocked.has(t)) {
3795
+ dropped.push(t);
3796
+ } else {
3797
+ filtered.push(t);
3798
+ }
3799
+ }
3800
+ if (dropped.length > 0) {
3801
+ policyBlockedFiltered = dropped;
3802
+ targetsToTry = filtered;
3803
+ }
3804
+ if (targetsToTry.length === 0) {
3805
+ const latencyMs2 = Date.now() - start;
3806
+ await record({
3807
+ handle: initial.handle,
3808
+ tokensIn: 0,
3809
+ tokensOut: 0,
3810
+ latencyMs: latencyMs2,
3811
+ success: false,
3812
+ errorType: "all_blocked_by_policy",
3813
+ promptPreview: extractPromptPreview(ir)
3814
+ });
3815
+ const blockedAttempts = dropped.map((m) => ({
3816
+ model: m,
3817
+ status: "terminal",
3818
+ errorCode: "blocked_by_policy",
3819
+ message: `Skipped \u2014 model ${m} is in CompilePolicy.blockedModels`
3820
+ }));
3821
+ throw new CallError(
3822
+ `call(): all chain targets blocked by CompilePolicy.blockedModels: [${dropped.join(", ")}]`,
3823
+ blockedAttempts,
3824
+ void 0,
3825
+ "all_blocked_by_policy"
3826
+ );
3827
+ }
3828
+ }
1240
3829
  let activeCompile = initial;
1241
3830
  let lastErr;
3831
+ const failedProviders = /* @__PURE__ */ new Set();
1242
3832
  for (let i = 0; i < targetsToTry.length; i++) {
1243
3833
  const targetModel = targetsToTry[i];
1244
- if (i > 0) {
3834
+ const targetProfile = tryGetProfile(targetModel);
3835
+ if (targetProfile && failedProviders.has(targetProfile.provider) && !opts.noFallback) {
3836
+ attempts.push({
3837
+ model: targetModel,
3838
+ status: "terminal",
3839
+ errorCode: "auth_inferred",
3840
+ message: `Skipped \u2014 provider ${targetProfile.provider} returned 401/403 earlier in this call; same key inferred to fail`
3841
+ });
3842
+ continue;
3843
+ }
3844
+ if (targetModel !== initial.target) {
1245
3845
  try {
1246
3846
  activeCompile = compileAndRegister(
1247
3847
  {
@@ -1261,49 +3861,117 @@ async function call(ir, opts = {}) {
1261
3861
  continue;
1262
3862
  }
1263
3863
  }
3864
+ safeEmit(
3865
+ () => emitExecuteAttempt(traceId, ir.appId, { model: targetModel, attemptIndex: i })
3866
+ );
3867
+ const targetSupportsStreaming = targetProfile?.streaming === true;
3868
+ const streamingOnChunk = opts.onChunk && !opts.noStream && targetSupportsStreaming ? opts.onChunk : void 0;
1264
3869
  const exec = await execute(activeCompile.request, {
1265
3870
  apiKeys: opts.apiKeys,
1266
3871
  fetchImpl: opts.fetchImpl,
1267
- providerOverrides: opts.providerOverrides
3872
+ providerOverrides: opts.providerOverrides,
3873
+ onChunk: streamingOnChunk
1268
3874
  });
1269
- if (exec.ok) {
3875
+ const validated = exec.ok ? validateStructuredContract(exec, ir) : exec;
3876
+ if (validated.ok) {
1270
3877
  attempts.push({ model: targetModel, status: "success" });
1271
3878
  const latencyMs2 = Date.now() - start;
1272
- const responseWithStructured = withStructuredOutput(exec.response, ir);
3879
+ safeEmit(
3880
+ () => emitExecuteSuccess(traceId, ir.appId, {
3881
+ model: targetModel,
3882
+ tokensIn: validated.response.tokens.input,
3883
+ tokensOut: validated.response.tokens.output,
3884
+ latencyMs: latencyMs2
3885
+ })
3886
+ );
3887
+ const fellOver = targetModel !== initial.target;
3888
+ const fallbackReason = fellOver ? normalizeFallbackReason(attempts) : void 0;
1273
3889
  await record({
1274
3890
  handle: initial.handle,
1275
- tokensIn: responseWithStructured.tokens.input,
1276
- tokensOut: responseWithStructured.tokens.output,
3891
+ tokensIn: validated.response.tokens.input,
3892
+ tokensOut: validated.response.tokens.output,
1277
3893
  latencyMs: latencyMs2,
1278
3894
  success: true,
1279
- emptyResponse: responseWithStructured.tokens.output === 0,
1280
- toolsCalled: responseWithStructured.toolCalls.map((tc) => tc.name),
3895
+ emptyResponse: validated.response.tokens.output === 0,
3896
+ toolsCalled: validated.response.toolCalls.map((tc) => tc.name),
1281
3897
  actualModel: targetModel !== initial.target ? targetModel : void 0,
1282
3898
  mutationsApplied: targetModel !== initial.target ? activeCompile.mutationsApplied.map((m) => m.id) : void 0,
1283
3899
  promptPreview: extractPromptPreview(ir),
1284
- responsePreview: responseWithStructured.text.slice(0, 200),
1285
- cacheReadInputTokens: responseWithStructured.tokens.cached,
1286
- cacheCreationInputTokens: responseWithStructured.tokens.cacheCreated
3900
+ responsePreview: validated.response.text.slice(0, 200),
3901
+ cacheReadInputTokens: validated.response.tokens.cached,
3902
+ cacheCreationInputTokens: validated.response.tokens.cacheCreated,
3903
+ // alpha.28 — Glass-Box renderer substrate (migration 018). call()
3904
+ // owns the lifecycle so it has direct visibility into finishReason
3905
+ // (from the normalized provider response), totalMs (mirrors latencyMs
3906
+ // for non-streaming; future streaming variant may diverge), and the
3907
+ // fell-over-from / fallback-reason pair (already computed above for
3908
+ // the CallResult return shape).
3909
+ finishReason: validated.response.finishReason,
3910
+ totalMs: latencyMs2,
3911
+ fellOverFrom: fellOver ? initial.target : void 0,
3912
+ fallbackReason
1287
3913
  });
3914
+ if (fellOver) {
3915
+ const firstFailed = attempts.find((a) => a.status !== "success");
3916
+ if (firstFailed) {
3917
+ safeEmit(
3918
+ () => emitFallbackWalked(traceId, ir.appId, {
3919
+ from: initial.target,
3920
+ to: targetModel,
3921
+ reason: fallbackReason ?? "unknown",
3922
+ attempt: firstFailed
3923
+ })
3924
+ );
3925
+ }
3926
+ }
3927
+ if (opts.shadowProbe) {
3928
+ const probe = runShadowProbe({
3929
+ ir,
3930
+ opts,
3931
+ servedModel: targetModel,
3932
+ servedResponse: validated.response,
3933
+ servedLatencyMs: latencyMs2
3934
+ });
3935
+ if (isBrainSync()) {
3936
+ await probe;
3937
+ } else {
3938
+ void probe;
3939
+ }
3940
+ }
1288
3941
  return {
1289
3942
  handle: initial.handle,
1290
3943
  actualModel: targetModel,
1291
3944
  requestedModel: initial.target,
1292
3945
  provider: activeCompile.provider,
1293
- response: responseWithStructured,
3946
+ response: validated.response,
1294
3947
  latencyMs: latencyMs2,
1295
3948
  mutationsApplied: activeCompile.mutationsApplied,
1296
- attempts
3949
+ attempts,
3950
+ servedBy: targetModel,
3951
+ fellOverFrom: fellOver ? initial.target : void 0,
3952
+ fallbackReason,
3953
+ unreachableFiltered,
3954
+ policyBlockedFiltered,
3955
+ traceId,
3956
+ // alpha.44: surface the served compile's advisories so call()
3957
+ // consumers (agent paths) can logAdvisories() like compile()
3958
+ // consumers (chat/intake) do. activeCompile, not initial — parity
3959
+ // with mutationsApplied above on fallback. Closes IC's side-finding.
3960
+ advisories: activeCompile.advisories
1297
3961
  };
1298
3962
  }
1299
3963
  attempts.push({
1300
3964
  model: targetModel,
1301
- status: exec.errorType,
1302
- errorCode: exec.errorCode,
1303
- message: exec.message
3965
+ status: validated.errorType,
3966
+ errorCode: validated.errorCode,
3967
+ message: validated.message
1304
3968
  });
1305
- lastErr = exec;
1306
- if (exec.errorType === "terminal" || opts.noFallback) {
3969
+ lastErr = validated;
3970
+ if (validated.errorType === "terminal" || opts.noFallback) {
3971
+ if (validated.errorCode === "auth" && !opts.noFallback && activeCompile.provider) {
3972
+ failedProviders.add(activeCompile.provider);
3973
+ continue;
3974
+ }
1307
3975
  break;
1308
3976
  }
1309
3977
  }
@@ -1317,8 +3985,10 @@ async function call(ir, opts = {}) {
1317
3985
  errorType: lastErr?.errorCode,
1318
3986
  promptPreview: extractPromptPreview(ir)
1319
3987
  });
3988
+ const filteredNote = unreachableFiltered && unreachableFiltered.length > 0 ? ` (also auto-filtered: [${unreachableFiltered.join(", ")}] \u2014 no API key)` : "";
3989
+ const blockedNote = policyBlockedFiltered && policyBlockedFiltered.length > 0 ? ` (also policy-blocked: [${policyBlockedFiltered.join(", ")}])` : "";
1320
3990
  throw new CallError(
1321
- `call(): all attempts failed${lastErr ? ` \u2014 ${lastErr.errorCode}: ${lastErr.message}` : ""}`,
3991
+ `call(): all attempts failed${lastErr ? ` \u2014 ${lastErr.errorCode}: ${lastErr.message}` : ""}${filteredNote}${blockedNote}`,
1322
3992
  attempts,
1323
3993
  lastErr?.status,
1324
3994
  lastErr?.errorCode
@@ -1340,20 +4010,371 @@ function extractPromptPreview(ir) {
1340
4010
  if (lastHist) return lastHist.slice(0, 200);
1341
4011
  return void 0;
1342
4012
  }
1343
- function withStructuredOutput(response, ir) {
1344
- if (!ir.constraints?.structuredOutput) return response;
1345
- if (!response.text) return response;
4013
+ function shouldSampleProbe(sampleRate, rng = Math.random) {
4014
+ if (!(sampleRate > 0)) return false;
4015
+ if (sampleRate >= 1) return true;
4016
+ return rng() < sampleRate;
4017
+ }
4018
+ function normalizeProbeCandidates(candidates) {
4019
+ const arr = Array.isArray(candidates) ? candidates : [candidates];
4020
+ return [...new Set(arr.filter((c) => typeof c === "string" && c.length > 0))];
4021
+ }
4022
+ function hashForProbe(s) {
4023
+ let h = 0;
4024
+ for (let i = 0; i < s.length; i++) h = (h << 5) - h + s.charCodeAt(i) | 0;
4025
+ return `h${(h >>> 0).toString(16)}`;
4026
+ }
4027
+ var DEFAULT_PROBE_MAX_LATENCY_MS = 15e3;
4028
+ function isSlowTierCandidate(candidate) {
4029
+ const profile = tryGetProfile(candidate);
4030
+ if (!profile) return false;
4031
+ return latencyTierOf(profile) === "slow";
4032
+ }
4033
+ async function runProbeCandidates(args) {
4034
+ try {
4035
+ if (!shouldSampleProbe(args.cfg.sampleRate ?? 0.05)) return;
4036
+ const candidates = normalizeProbeCandidates(args.cfg.candidates);
4037
+ const promptHash = hashForProbe(args.ir.currentTurn?.content ?? "");
4038
+ const budgetMs = args.cfg.maxLatencyMs ?? DEFAULT_PROBE_MAX_LATENCY_MS;
4039
+ const deadline = args.sync ? Date.now() + budgetMs : Number.POSITIVE_INFINITY;
4040
+ const skipSlow = args.sync && (args.cfg.skipSlowTierInSync ?? true);
4041
+ const recordDiagnostic = async (candidate, outcome, detail) => {
4042
+ await recordShadowProbe({
4043
+ appId: args.ir.appId,
4044
+ archetype: args.ir.intent.archetype,
4045
+ family: deriveFamilyFromModelId(candidate) ?? "unknown",
4046
+ candidateModel: candidate,
4047
+ currentModel: args.servedModel,
4048
+ promptHash,
4049
+ // Served leg only — the candidate never produced a comparable response.
4050
+ currentResponsePreview: args.servedResponse.text.slice(0, 2e3),
4051
+ tokensCurrentIn: args.servedResponse.tokens.input,
4052
+ tokensCurrentOut: args.servedResponse.tokens.output,
4053
+ latencyCurrentMs: args.servedLatencyMs,
4054
+ outcome,
4055
+ ...detail?.errorClass ? { errorClass: detail.errorClass } : {},
4056
+ ...detail?.latencyCandidateMs !== void 0 ? { latencyCandidateMs: detail.latencyCandidateMs } : {}
4057
+ });
4058
+ };
4059
+ for (const candidate of candidates) {
4060
+ if (candidate === args.servedModel) continue;
4061
+ if (deriveFamilyFromModelId(candidate) === "claude-opus") continue;
4062
+ if (!isModelReachable(candidate, { apiKeys: args.exec.apiKeys })) continue;
4063
+ if (skipSlow && isSlowTierCandidate(candidate)) {
4064
+ try {
4065
+ await recordDiagnostic(candidate, "skipped_slow_tier_sync");
4066
+ } catch {
4067
+ }
4068
+ continue;
4069
+ }
4070
+ if (args.sync && Date.now() >= deadline) {
4071
+ try {
4072
+ await recordDiagnostic(candidate, "aborted_latency_budget");
4073
+ } catch {
4074
+ }
4075
+ break;
4076
+ }
4077
+ try {
4078
+ const candCompile = compileAndRegister(
4079
+ {
4080
+ ...args.ir,
4081
+ models: args.ir.models.includes(candidate) ? args.ir.models : [candidate, ...args.ir.models],
4082
+ constraints: { ...args.ir.constraints ?? {}, forceModel: candidate }
4083
+ },
4084
+ args.exec
4085
+ );
4086
+ const candStart = Date.now();
4087
+ let timer;
4088
+ const execPromise = execute(candCompile.request, {
4089
+ apiKeys: args.exec.apiKeys,
4090
+ fetchImpl: args.exec.fetchImpl,
4091
+ providerOverrides: args.exec.providerOverrides
4092
+ }).then((exec2) => ({ kind: "exec", exec: exec2 })).catch(() => ({ kind: "failed" }));
4093
+ let raced;
4094
+ if (args.sync) {
4095
+ const remaining = Math.max(0, deadline - Date.now());
4096
+ const timeoutPromise = new Promise((resolve) => {
4097
+ timer = setTimeout(() => resolve({ kind: "timeout" }), remaining);
4098
+ });
4099
+ raced = await Promise.race([execPromise, timeoutPromise]);
4100
+ if (timer) clearTimeout(timer);
4101
+ } else {
4102
+ raced = await execPromise;
4103
+ }
4104
+ if (raced.kind === "timeout") {
4105
+ try {
4106
+ await recordDiagnostic(candidate, "aborted_latency_budget");
4107
+ } catch {
4108
+ }
4109
+ break;
4110
+ }
4111
+ if (raced.kind === "failed") {
4112
+ try {
4113
+ await recordDiagnostic(candidate, "candidate_error", {
4114
+ errorClass: "execute_rejected",
4115
+ latencyCandidateMs: Date.now() - candStart
4116
+ });
4117
+ } catch {
4118
+ }
4119
+ continue;
4120
+ }
4121
+ const exec = raced.exec;
4122
+ const candidateLatencyMs = Date.now() - candStart;
4123
+ if (!exec.ok) {
4124
+ try {
4125
+ await recordDiagnostic(candidate, "candidate_error", {
4126
+ errorClass: exec.errorCode,
4127
+ latencyCandidateMs: candidateLatencyMs
4128
+ });
4129
+ } catch {
4130
+ }
4131
+ continue;
4132
+ }
4133
+ await recordShadowProbe({
4134
+ appId: args.ir.appId,
4135
+ archetype: args.ir.intent.archetype,
4136
+ family: deriveFamilyFromModelId(candidate) ?? candCompile.provider,
4137
+ candidateModel: candidate,
4138
+ currentModel: args.servedModel,
4139
+ promptHash,
4140
+ currentResponsePreview: args.servedResponse.text.slice(0, 2e3),
4141
+ candidateResponsePreview: exec.response.text.slice(0, 2e3),
4142
+ tokensCurrentIn: args.servedResponse.tokens.input,
4143
+ tokensCurrentOut: args.servedResponse.tokens.output,
4144
+ tokensCandidateIn: exec.response.tokens.input,
4145
+ tokensCandidateOut: exec.response.tokens.output,
4146
+ // alpha.46 — speed is the third swap-decision axis. Served latency
4147
+ // mirrors the user's actual wait; candidate latency is measured only
4148
+ // here and stored nowhere else (irrecoverable if not captured now).
4149
+ latencyCurrentMs: args.servedLatencyMs,
4150
+ latencyCandidateMs: candidateLatencyMs,
4151
+ outcome: "completed"
4152
+ });
4153
+ } catch {
4154
+ }
4155
+ }
4156
+ } catch {
4157
+ }
4158
+ }
4159
+ async function runShadowProbe(args) {
4160
+ const cfg = args.opts.shadowProbe;
4161
+ if (!cfg) return;
4162
+ await runProbeCandidates({
4163
+ ir: args.ir,
4164
+ cfg,
4165
+ servedModel: args.servedModel,
4166
+ servedResponse: args.servedResponse,
4167
+ servedLatencyMs: args.servedLatencyMs,
4168
+ // CallOptions is a structural superset of ProbeExecOpts.
4169
+ exec: args.opts,
4170
+ // alpha — the call()-inline path is on the user's critical path exactly when
4171
+ // BrainConfig.sync is set (call() awaits the probe before returning). That's
4172
+ // the only case where the latency budget + slow-tier skip must fire.
4173
+ sync: isBrainSync()
4174
+ });
4175
+ }
4176
+ async function probeShadow(ir, opts) {
4177
+ try {
4178
+ const servedResponse = {
4179
+ text: opts.served.responseText,
4180
+ structuredOutput: null,
4181
+ toolCalls: [],
4182
+ tokens: {
4183
+ input: opts.served.tokensIn,
4184
+ output: opts.served.tokensOut,
4185
+ total: opts.served.tokensIn + opts.served.tokensOut
4186
+ },
4187
+ raw: null
4188
+ };
4189
+ await runProbeCandidates({
4190
+ ir,
4191
+ cfg: {
4192
+ candidates: opts.candidates,
4193
+ sampleRate: opts.sampleRate,
4194
+ judge: opts.judge ?? "off"
4195
+ },
4196
+ servedModel: opts.served.model,
4197
+ servedResponse,
4198
+ servedLatencyMs: opts.served.latencyMs,
4199
+ exec: {
4200
+ policy: opts.policy,
4201
+ toolRelevanceThreshold: opts.toolRelevanceThreshold,
4202
+ compressHistoryAfter: opts.compressHistoryAfter,
4203
+ apiKeys: opts.apiKeys,
4204
+ fetchImpl: opts.fetchImpl,
4205
+ providerOverrides: opts.providerOverrides
4206
+ },
4207
+ // alpha — probeShadow() is ALWAYS off the user-facing critical path: the
4208
+ // consumer fires it from `onFinish` wrapped in `after()`/`waitUntil()`,
4209
+ // after the response is flushed. So the sync-mode latency budget +
4210
+ // slow-tier skip are no-ops here — slow candidates run to completion (this
4211
+ // is in fact the right place to evaluate a slow reasoner). The
4212
+ // maxLatencyMs/skipSlowTierInSync knobs aren't even exposed on
4213
+ // ProbeShadowOptions for that reason.
4214
+ sync: false
4215
+ });
4216
+ } catch {
4217
+ }
4218
+ }
4219
+ function validateStructuredContract(exec, ir) {
4220
+ if (!ir.constraints?.structuredOutput) {
4221
+ return { ok: true, response: exec.response };
4222
+ }
4223
+ const finish = (exec.response.finishReason ?? "").toLowerCase();
4224
+ if (finish === "max_tokens" || finish === "length") {
4225
+ return {
4226
+ ok: false,
4227
+ status: exec.status,
4228
+ errorType: "retryable",
4229
+ errorCode: "max_tokens_on_structured_output",
4230
+ message: `Provider returned finishReason="${exec.response.finishReason}" on a structured-output call \u2014 output truncated mid-token, JSON cannot be valid`,
4231
+ raw: exec.response.raw
4232
+ };
4233
+ }
4234
+ if (!exec.response.text) {
4235
+ return { ok: true, response: exec.response };
4236
+ }
1346
4237
  try {
1347
- const parsed = JSON.parse(response.text);
1348
- return { ...response, structuredOutput: parsed };
4238
+ const parsed = JSON.parse(exec.response.text);
4239
+ return { ok: true, response: { ...exec.response, structuredOutput: parsed } };
1349
4240
  } catch (err) {
1350
4241
  return {
1351
- ...response,
1352
- structuredOutput: null,
1353
- parseError: err instanceof Error ? err.message : String(err)
4242
+ ok: false,
4243
+ status: exec.status,
4244
+ errorType: "retryable",
4245
+ errorCode: "structured_output_parse_failed",
4246
+ message: err instanceof Error ? err.message : String(err),
4247
+ raw: exec.response.raw
1354
4248
  };
1355
4249
  }
1356
4250
  }
4251
+ function normalizeFallbackReason(attempts) {
4252
+ const first = attempts.find((a) => a.status !== "success");
4253
+ if (!first) return void 0;
4254
+ const code = first.errorCode ?? "";
4255
+ if (code === "rate_limit_429" || code === "rate_limit") return "rate_limit";
4256
+ if (code === "max_tokens_on_structured_output" || code === "structured_output_parse_failed") {
4257
+ return "cliff";
4258
+ }
4259
+ if (code === "cost_cap_exceeded") return "cost_cap";
4260
+ if (code === "auth" || code === "auth_inferred") return "provider_auth_failed";
4261
+ return "provider_error";
4262
+ }
4263
+ function generateTraceId() {
4264
+ try {
4265
+ const g = globalThis;
4266
+ if (g.crypto && typeof g.crypto.randomUUID === "function") {
4267
+ return g.crypto.randomUUID();
4268
+ }
4269
+ } catch {
4270
+ }
4271
+ const hex = (n) => Math.floor(Math.random() * Math.pow(16, n)).toString(16).padStart(n, "0");
4272
+ return `${hex(8)}-${hex(4)}-${hex(4)}-${hex(4)}-${hex(12)}`;
4273
+ }
4274
+ function safeEmit(fn) {
4275
+ try {
4276
+ fn();
4277
+ } catch {
4278
+ }
4279
+ }
4280
+
4281
+ // src/streamtext-helpers.ts
4282
+ function attachCacheControlToStreamTextInput(result, convertedMessages) {
4283
+ const messages = convertedMessages.map((m) => ({ ...m }));
4284
+ const markIdx = result.diagnostics.historyCacheMarkIndex;
4285
+ if (result.provider === "anthropic" && typeof markIdx === "number" && markIdx >= 0 && markIdx < messages.length) {
4286
+ const target = messages[markIdx];
4287
+ if (target) {
4288
+ messages[markIdx] = {
4289
+ role: target.role,
4290
+ content: target.content,
4291
+ providerOptions: {
4292
+ ...target.providerOptions ?? {},
4293
+ anthropic: {
4294
+ ...target.providerOptions?.anthropic ?? {},
4295
+ cacheControl: { type: "ephemeral" }
4296
+ }
4297
+ }
4298
+ };
4299
+ }
4300
+ }
4301
+ const systemMessages = result.systemMessages;
4302
+ const hasStructuredSystemCacheControl = result.provider === "anthropic" && systemMessages.some(
4303
+ (m) => m.providerOptions?.anthropic?.cacheControl !== void 0
4304
+ );
4305
+ const system = hasStructuredSystemCacheControl ? systemMessages : systemMessages.map((m) => m.content).join("\n\n");
4306
+ return { system, messages };
4307
+ }
4308
+
4309
+ // src/compile-aisdk.ts
4310
+ function compileForAISDKv6(ir, opts) {
4311
+ const result = compile(ir, opts);
4312
+ registerCompile(ir.appId, ir.intent.archetype, ir, result);
4313
+ armCompileConsumeTracking(result.handle);
4314
+ const systemMessages = result.systemMessages;
4315
+ const hasStructuredSystemCacheControl = result.provider === "anthropic" && systemMessages.some(
4316
+ (m) => m.providerOptions?.anthropic?.cacheControl !== void 0
4317
+ );
4318
+ const systemValue = hasStructuredSystemCacheControl ? systemMessages : systemMessages.map((m) => m.content).join("\n\n");
4319
+ const keptToolNames = extractKeptToolNames(result);
4320
+ const providerOptions = result.provider === "anthropic" && result.diagnostics.cacheableTokens > 0 ? { anthropic: { cacheControl: { type: "ephemeral" } } } : void 0;
4321
+ const handle = result.handle;
4322
+ const diagnostics = {
4323
+ ...result.diagnostics,
4324
+ targetProvider: result.provider,
4325
+ estimatedCostUsd: result.estimatedCostUsd,
4326
+ fallbackChain: result.fallbackChain
4327
+ };
4328
+ return {
4329
+ handle,
4330
+ // Consume-tracking getters: reading any of these flags the compile as
4331
+ // "used", silencing the discard guard at record() time.
4332
+ get model() {
4333
+ markCompileConsumed(handle, "model");
4334
+ return result.target;
4335
+ },
4336
+ get system() {
4337
+ markCompileConsumed(handle, "system");
4338
+ return systemValue;
4339
+ },
4340
+ get raw() {
4341
+ markCompileConsumed(handle, "raw");
4342
+ return result;
4343
+ },
4344
+ keptToolNames,
4345
+ ...providerOptions ? { providerOptions } : {},
4346
+ // Provider-gated (alpha.55, IC finding): the marker is an Anthropic
4347
+ // concept — Gemini/OpenAI/DeepSeek cache prefixes implicitly, and a
4348
+ // consumer attaching Anthropic providerOptions at this index on a
4349
+ // non-Anthropic call would ship a no-op-at-best marker. The ungated value
4350
+ // stays on diagnostics.historyCacheMarkIndex for observability.
4351
+ ...result.provider === "anthropic" && result.diagnostics.historyCacheMarkIndex !== void 0 ? { historyCacheMarkIndex: result.diagnostics.historyCacheMarkIndex } : {},
4352
+ mutationsApplied: result.mutationsApplied.map((m) => m.id),
4353
+ advisories: result.advisories ?? [],
4354
+ diagnostics,
4355
+ ir
4356
+ };
4357
+ }
4358
+ function extractKeptToolNames(result) {
4359
+ const names = /* @__PURE__ */ new Set();
4360
+ const tools = result.request.tools;
4361
+ if (!Array.isArray(tools)) return names;
4362
+ if (result.provider === "google") {
4363
+ for (const td of tools) {
4364
+ for (const fn of td.functionDeclarations ?? []) if (fn.name) names.add(fn.name);
4365
+ }
4366
+ } else if (result.provider === "openai") {
4367
+ for (const t of tools) {
4368
+ const n = t.function?.name;
4369
+ if (n) names.add(n);
4370
+ }
4371
+ } else {
4372
+ for (const t of tools) {
4373
+ if (t.name) names.add(t.name);
4374
+ }
4375
+ }
4376
+ return names;
4377
+ }
1357
4378
 
1358
4379
  // src/oracle.ts
1359
4380
  var DEFAULT_DIMENSIONS = ["correctness", "completeness", "conciseness", "format"];
@@ -1443,6 +4464,338 @@ function clamp(n) {
1443
4464
  return Math.max(0, Math.min(1, n));
1444
4465
  }
1445
4466
 
4467
+ // src/advisories-api.ts
4468
+ var SEVERITY_SET = /* @__PURE__ */ new Set(["info", "warn", "critical"]);
4469
+ var STATUS_SET = /* @__PURE__ */ new Set(["open", "snoozed", "resolved"]);
4470
+ var RESOLUTION_SOURCE_SET = /* @__PURE__ */ new Set([
4471
+ "auto",
4472
+ "consumer-marked",
4473
+ "declined"
4474
+ ]);
4475
+ function asString2(v) {
4476
+ return typeof v === "string" && v.length > 0 ? v : void 0;
4477
+ }
4478
+ function asSeverity(v) {
4479
+ if (typeof v === "string" && SEVERITY_SET.has(v)) {
4480
+ return v;
4481
+ }
4482
+ return "info";
4483
+ }
4484
+ function asStatus(v) {
4485
+ if (typeof v === "string" && STATUS_SET.has(v)) {
4486
+ return v;
4487
+ }
4488
+ return "open";
4489
+ }
4490
+ function asResolutionSource(v) {
4491
+ if (typeof v === "string" && RESOLUTION_SOURCE_SET.has(v)) {
4492
+ return v;
4493
+ }
4494
+ return void 0;
4495
+ }
4496
+ function rowToAdvisory(row) {
4497
+ const archetype = asString2(row.applies_to_archetype);
4498
+ const model = asString2(row.applies_to_model);
4499
+ const docsLink = asString2(row.docs_url);
4500
+ const suggestion = asString2(row.suggestion);
4501
+ let suggestedFix = null;
4502
+ if (docsLink || suggestion) {
4503
+ suggestedFix = { type: "manual" };
4504
+ if (docsLink) suggestedFix.docsLink = docsLink;
4505
+ if (suggestion) suggestedFix.before = suggestion;
4506
+ }
4507
+ const out = {
4508
+ id: typeof row.id === "string" ? row.id : "",
4509
+ rule: typeof row.rule === "string" ? row.rule : "",
4510
+ severity: asSeverity(row.severity),
4511
+ openedAt: typeof row.opened_at === "string" ? row.opened_at : "",
4512
+ lastObservedAt: typeof row.last_observed_at === "string" ? row.last_observed_at : "",
4513
+ observationCount: typeof row.observation_count === "number" ? row.observation_count : 0,
4514
+ appliesTo: {
4515
+ ...archetype ? { archetype } : {},
4516
+ ...model ? { model } : {}
4517
+ },
4518
+ message: typeof row.message === "string" ? row.message : "",
4519
+ suggestedFix,
4520
+ autoApplicable: false,
4521
+ // reserved — alpha.30+
4522
+ status: asStatus(row.status)
4523
+ };
4524
+ const resolvedAt = asString2(row.resolved_at);
4525
+ if (resolvedAt) out.resolvedAt = resolvedAt;
4526
+ const resolutionSource = asResolutionSource(row.resolution_source);
4527
+ if (resolutionSource) out.resolutionSource = resolutionSource;
4528
+ const resolutionNote = asString2(row.resolution_note);
4529
+ if (resolutionNote) out.resolutionNote = resolutionNote;
4530
+ return out;
4531
+ }
4532
+ function resolveFetch(injected) {
4533
+ return injected ?? ((...args) => globalThis.fetch(...args));
4534
+ }
4535
+ function normalizeEndpoint2(endpoint) {
4536
+ return endpoint.replace(/\/+$/, "");
4537
+ }
4538
+ async function getActionableAdvisories(opts) {
4539
+ const {
4540
+ appId,
4541
+ severity,
4542
+ status,
4543
+ brainEndpoint,
4544
+ brainJwt,
4545
+ brainAnonKey,
4546
+ fetch: injectedFetch
4547
+ } = opts;
4548
+ if (!appId) {
4549
+ throw new Error("getActionableAdvisories: appId is required");
4550
+ }
4551
+ const doFetch = resolveFetch(injectedFetch);
4552
+ const base = normalizeEndpoint2(brainEndpoint);
4553
+ const qs = new URLSearchParams();
4554
+ qs.set("app_id", `eq.${appId}`);
4555
+ if (severity) qs.set("severity", `eq.${severity}`);
4556
+ const effectiveStatus = status ?? "open";
4557
+ if (effectiveStatus !== "all") {
4558
+ qs.set("status", `eq.${effectiveStatus}`);
4559
+ }
4560
+ qs.set("order", "last_observed_at.desc");
4561
+ const url = `${base}/rest/v1/actionable_advisories_v?${qs.toString()}`;
4562
+ let res;
4563
+ try {
4564
+ res = await doFetch(url, {
4565
+ method: "GET",
4566
+ headers: {
4567
+ Authorization: `Bearer ${brainJwt}`,
4568
+ apikey: brainAnonKey,
4569
+ Accept: "application/json"
4570
+ }
4571
+ });
4572
+ } catch (err) {
4573
+ const msg = err instanceof Error ? err.message : String(err);
4574
+ throw new Error(`getActionableAdvisories: network error: ${msg}`);
4575
+ }
4576
+ if (res.status === 401 || res.status === 403) {
4577
+ throw new Error("getActionableAdvisories: brain auth misconfig");
4578
+ }
4579
+ if (res.status >= 500) {
4580
+ throw new Error(`getActionableAdvisories: brain unavailable (${res.status})`);
4581
+ }
4582
+ if (!res.ok) {
4583
+ throw new Error(`getActionableAdvisories: bad request (${res.status})`);
4584
+ }
4585
+ let rows;
4586
+ try {
4587
+ rows = await res.json();
4588
+ } catch {
4589
+ throw new Error("getActionableAdvisories: malformed brain response");
4590
+ }
4591
+ if (!Array.isArray(rows)) {
4592
+ throw new Error("getActionableAdvisories: expected array from brain");
4593
+ }
4594
+ const out = [];
4595
+ for (const raw of rows) {
4596
+ if (raw && typeof raw === "object") {
4597
+ out.push(rowToAdvisory(raw));
4598
+ }
4599
+ }
4600
+ return out;
4601
+ }
4602
+ async function markAdvisoryResolved(opts) {
4603
+ const {
4604
+ id,
4605
+ resolutionNote,
4606
+ brainEndpoint,
4607
+ brainJwt,
4608
+ brainAnonKey,
4609
+ fetch: injectedFetch
4610
+ } = opts;
4611
+ if (!id) {
4612
+ return { ok: false, reason: "id_required" };
4613
+ }
4614
+ const doFetch = resolveFetch(injectedFetch);
4615
+ const base = normalizeEndpoint2(brainEndpoint);
4616
+ const lookupUrl = `${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=app_id,rule`;
4617
+ let lookupRes;
4618
+ try {
4619
+ lookupRes = await doFetch(lookupUrl, {
4620
+ method: "GET",
4621
+ headers: {
4622
+ Authorization: `Bearer ${brainJwt}`,
4623
+ apikey: brainAnonKey,
4624
+ Accept: "application/json"
4625
+ }
4626
+ });
4627
+ } catch (err) {
4628
+ const msg = err instanceof Error ? err.message : String(err);
4629
+ return { ok: false, reason: `network_error:${msg}` };
4630
+ }
4631
+ if (lookupRes.status === 401 || lookupRes.status === 403) {
4632
+ return { ok: false, reason: "brain_auth_misconfig" };
4633
+ }
4634
+ if (lookupRes.status >= 500) {
4635
+ return { ok: false, reason: "brain_unavailable" };
4636
+ }
4637
+ if (!lookupRes.ok) {
4638
+ return { ok: false, reason: `brain_lookup_failed:${lookupRes.status}` };
4639
+ }
4640
+ let lookupRows;
4641
+ try {
4642
+ lookupRows = await lookupRes.json();
4643
+ } catch {
4644
+ return { ok: false, reason: "brain_lookup_malformed" };
4645
+ }
4646
+ if (!Array.isArray(lookupRows) || lookupRows.length === 0) {
4647
+ return { ok: false, reason: "advisory_not_found" };
4648
+ }
4649
+ const tuple = lookupRows[0];
4650
+ const appId = typeof tuple.app_id === "string" ? tuple.app_id : "";
4651
+ const code = typeof tuple.rule === "string" ? tuple.rule : "";
4652
+ if (!appId || !code) {
4653
+ return { ok: false, reason: "advisory_tuple_invalid" };
4654
+ }
4655
+ const outcomesUrl = `${base}/rest/v1/compile_outcomes?app_id=eq.${encodeURIComponent(appId)}&select=id`;
4656
+ let outcomesRes;
4657
+ try {
4658
+ outcomesRes = await doFetch(outcomesUrl, {
4659
+ method: "GET",
4660
+ headers: {
4661
+ Authorization: `Bearer ${brainJwt}`,
4662
+ apikey: brainAnonKey,
4663
+ Accept: "application/json"
4664
+ }
4665
+ });
4666
+ } catch (err) {
4667
+ const msg = err instanceof Error ? err.message : String(err);
4668
+ return { ok: false, reason: `network_error:${msg}` };
4669
+ }
4670
+ if (outcomesRes.status === 401 || outcomesRes.status === 403) {
4671
+ return { ok: false, reason: "brain_auth_misconfig" };
4672
+ }
4673
+ if (outcomesRes.status >= 500) {
4674
+ return { ok: false, reason: "brain_unavailable" };
4675
+ }
4676
+ if (!outcomesRes.ok) {
4677
+ return { ok: false, reason: `brain_lookup_failed:${outcomesRes.status}` };
4678
+ }
4679
+ let outcomeRows;
4680
+ try {
4681
+ outcomeRows = await outcomesRes.json();
4682
+ } catch {
4683
+ return { ok: false, reason: "brain_lookup_malformed" };
4684
+ }
4685
+ if (!Array.isArray(outcomeRows)) {
4686
+ return { ok: false, reason: "brain_lookup_malformed" };
4687
+ }
4688
+ const outcomeIds = [];
4689
+ for (const row of outcomeRows) {
4690
+ if (row && typeof row === "object") {
4691
+ const idVal = row.id;
4692
+ if (typeof idVal === "number" && Number.isFinite(idVal)) {
4693
+ outcomeIds.push(idVal);
4694
+ }
4695
+ }
4696
+ }
4697
+ if (outcomeIds.length === 0) {
4698
+ return { ok: true };
4699
+ }
4700
+ const inList = outcomeIds.join(",");
4701
+ const patchUrl = `${base}/rest/v1/compile_outcome_advisories?outcome_id=in.(${inList})&code=eq.${encodeURIComponent(code)}&resolved_at=is.null`;
4702
+ const patchBody = {
4703
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
4704
+ resolution_source: "consumer-marked"
4705
+ };
4706
+ if (resolutionNote !== void 0) {
4707
+ patchBody.resolution_note = resolutionNote;
4708
+ }
4709
+ let patchRes;
4710
+ try {
4711
+ patchRes = await doFetch(patchUrl, {
4712
+ method: "PATCH",
4713
+ headers: {
4714
+ Authorization: `Bearer ${brainJwt}`,
4715
+ apikey: brainAnonKey,
4716
+ "Content-Type": "application/json",
4717
+ Accept: "application/json",
4718
+ // PostgREST default is no return; we don't need the row back.
4719
+ Prefer: "return=minimal"
4720
+ },
4721
+ body: JSON.stringify(patchBody)
4722
+ });
4723
+ } catch (err) {
4724
+ const msg = err instanceof Error ? err.message : String(err);
4725
+ return { ok: false, reason: `network_error:${msg}` };
4726
+ }
4727
+ if (patchRes.status === 401 || patchRes.status === 403) {
4728
+ return { ok: false, reason: "brain_auth_misconfig" };
4729
+ }
4730
+ if (patchRes.status >= 500) {
4731
+ return { ok: false, reason: "brain_unavailable" };
4732
+ }
4733
+ if (!patchRes.ok) {
4734
+ return { ok: false, reason: `patch_failed:${patchRes.status}` };
4735
+ }
4736
+ return { ok: true };
4737
+ }
4738
+ async function markExclusionFindingHandled(opts) {
4739
+ const {
4740
+ appId,
4741
+ archetype,
4742
+ excludedModel,
4743
+ resolution,
4744
+ resolutionNote,
4745
+ brainEndpoint,
4746
+ brainJwt,
4747
+ brainAnonKey,
4748
+ fetch: injectedFetch
4749
+ } = opts;
4750
+ if (!appId) return { ok: false, reason: "app_id_required" };
4751
+ if (!archetype) return { ok: false, reason: "archetype_required" };
4752
+ if (!excludedModel) {
4753
+ return { ok: false, reason: "excluded_model_required" };
4754
+ }
4755
+ if (resolution !== "consumer-marked" && resolution !== "declined" && resolution !== "probed-unblock" && resolution !== "probed-stay-excluded") {
4756
+ return { ok: false, reason: "resolution_invalid" };
4757
+ }
4758
+ const doFetch = resolveFetch(injectedFetch);
4759
+ const base = normalizeEndpoint2(brainEndpoint);
4760
+ 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`;
4761
+ const patchBody = {
4762
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
4763
+ resolution_source: resolution
4764
+ };
4765
+ if (resolutionNote !== void 0) {
4766
+ patchBody.resolution_note = resolutionNote;
4767
+ }
4768
+ let res;
4769
+ try {
4770
+ res = await doFetch(url, {
4771
+ method: "PATCH",
4772
+ headers: {
4773
+ Authorization: `Bearer ${brainJwt}`,
4774
+ apikey: brainAnonKey,
4775
+ "Content-Type": "application/json",
4776
+ Accept: "application/json",
4777
+ // PATCH may match zero rows when already resolved — return=minimal
4778
+ // keeps the response body empty so the success path is uniform.
4779
+ Prefer: "return=minimal"
4780
+ },
4781
+ body: JSON.stringify(patchBody)
4782
+ });
4783
+ } catch (err) {
4784
+ const msg = err instanceof Error ? err.message : String(err);
4785
+ return { ok: false, reason: `network_error:${msg}` };
4786
+ }
4787
+ if (res.status === 401 || res.status === 403) {
4788
+ return { ok: false, reason: "brain_auth_misconfig" };
4789
+ }
4790
+ if (res.status >= 500) {
4791
+ return { ok: false, reason: "brain_unavailable" };
4792
+ }
4793
+ if (!res.ok) {
4794
+ return { ok: false, reason: `patch_failed:${res.status}` };
4795
+ }
4796
+ return { ok: true };
4797
+ }
4798
+
1446
4799
  // src/index.ts
1447
4800
  function compile2(ir, opts) {
1448
4801
  const result = compile(ir, opts);
@@ -1450,29 +4803,92 @@ function compile2(ir, opts) {
1450
4803
  return result;
1451
4804
  }
1452
4805
  export {
4806
+ ABSOLUTE_FLOOR,
1453
4807
  ALIASES,
1454
4808
  ALL_ARCHETYPES,
4809
+ ARCHETYPE_FAMILY_FITS,
4810
+ ARCHETYPE_FLOOR_DEFAULT,
1455
4811
  CallError,
4812
+ DEFAULT_FINDINGS_ENDPOINT,
1456
4813
  DIALECT_VERSION,
4814
+ FamilyResolutionError,
1457
4815
  INTENT_ARCHETYPES,
4816
+ LATENCY_TIER_MS,
4817
+ LIBRARY_VERSION,
4818
+ MEASURED_GROUNDING_MIN_N,
4819
+ PRODUCER_OWNED_RULE_CODES,
4820
+ PROVIDER_ENV_KEYS,
4821
+ RULE_SEQUENTIAL_TOOL_CLIFF,
4822
+ TRANSLATOR_FLOOR,
1458
4823
  allProfiles,
4824
+ applyArchetypeConvention,
4825
+ applySectionRewrites,
4826
+ attachCacheControlToStreamTextInput,
4827
+ brainHealth,
1459
4828
  bucketContext,
1460
4829
  bucketHistory,
1461
4830
  bucketToolCount,
1462
4831
  buildLLMJudge,
4832
+ buildShadowProbeRow,
1463
4833
  call,
1464
4834
  clearBrain,
1465
4835
  compile2 as compile,
4836
+ compileForAISDKv6,
1466
4837
  configureBrain,
1467
4838
  countTokens,
4839
+ createBrainForwardRoutes,
4840
+ createKeyHealthRoute,
4841
+ deriveFamilyFromModelId,
4842
+ deriveOwnership,
1468
4843
  execute,
4844
+ findBetterFit,
4845
+ flushBrainDeadLetter,
4846
+ getActionableAdvisories,
4847
+ getAllStarterChains,
4848
+ getAllStarterChainsWithGrounding,
4849
+ getArchetypePerfScore,
4850
+ getDefaultFallbackChain,
4851
+ getDefaultFallbackChainWithGrounding,
4852
+ getModelCompatibility,
4853
+ getPerAxisMetrics,
1469
4854
  getProfile,
4855
+ getReachabilityDiagnostic,
4856
+ getRecommendedPrimary,
4857
+ getSequentialStarterChain,
4858
+ getSequentialStarterChainWithGrounding,
4859
+ getStaleExclusionFindings,
4860
+ getStarterChain,
4861
+ getStarterChainWithGrounding,
1470
4862
  hashShape,
1471
4863
  isArchetype,
4864
+ isBrainQueryActiveFor,
4865
+ isBrainSync,
4866
+ isExclusionFindingsBrainActive,
4867
+ isModelReachable,
4868
+ isProviderReachable,
4869
+ latencyTierOf,
1472
4870
  learningKey,
4871
+ loadAliasesFromBrain,
4872
+ loadArchetypePerfFromBrain,
4873
+ loadArchetypePerfNFromBrain,
4874
+ loadChainsFromBrain,
4875
+ loadModelsFromBrain,
4876
+ loadPricingFromBrain,
4877
+ markAdvisoryResolved,
4878
+ markExclusionFindingHandled,
4879
+ markPromoteReadyHandled,
4880
+ peekBrainDeadLetter,
4881
+ probeShadow,
4882
+ profileToRow,
1473
4883
  profilesByProvider,
4884
+ readBrainReadEnv,
1474
4885
  record,
4886
+ recordOutcome,
4887
+ recordShadowProbe,
1475
4888
  resetTokenizer,
4889
+ resolveConventionsForProfile,
4890
+ resolvePricingAt,
4891
+ resolveProviderKey,
1476
4892
  runAdvisor,
1477
4893
  setTokenizer,
1478
4894
  tryGetProfile