@warmdrift/kgauto-compiler 2.0.0-alpha.76 → 2.0.0-alpha.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-46U2NVOL.mjs → chunk-FT2FN6ZP.mjs} +13 -1
- package/dist/{chunk-WZZCW6NA.mjs → chunk-QVD2QWST.mjs} +1 -1
- package/dist/{chunk-N36LE3MK.mjs → chunk-VVRDFE6T.mjs} +16 -0
- package/dist/glassbox/index.d.mts +3 -3
- package/dist/glassbox/index.d.ts +3 -3
- package/dist/glassbox-routes/format.d.mts +2 -2
- package/dist/glassbox-routes/format.d.ts +2 -2
- package/dist/glassbox-routes/index.d.mts +4 -4
- package/dist/glassbox-routes/index.d.ts +4 -4
- package/dist/glassbox-routes/index.js +17 -0
- package/dist/glassbox-routes/index.mjs +3 -2
- package/dist/glassbox-routes/react/index.d.mts +2 -2
- package/dist/glassbox-routes/react/index.d.ts +2 -2
- package/dist/index.d.mts +152 -25
- package/dist/index.d.ts +152 -25
- package/dist/index.js +1353 -1037
- package/dist/index.mjs +1323 -1039
- package/dist/{ir-CZukZvDn.d.mts → ir-BFwWhj2s.d.mts} +67 -7
- package/dist/{ir-B0v2f9NY.d.ts → ir-DZKS1tI7.d.ts} +67 -7
- package/dist/key-health.js +1 -1
- package/dist/key-health.mjs +1 -1
- package/dist/profiles.d.mts +12 -2
- package/dist/profiles.d.ts +12 -2
- package/dist/profiles.js +16 -0
- package/dist/profiles.mjs +1 -1
- package/dist/{types-D-PuKCWk.d.ts → types-B4kz3Vs0.d.ts} +1 -1
- package/dist/{types-Ba68lIs1.d.ts → types-D_fLt_Xv.d.ts} +2 -9
- package/dist/{types-BgJ5iQB_.d.mts → types-DpcAMmk-.d.mts} +1 -1
- package/dist/{types-BOTLfJOp.d.mts → types-hjzSWxtv.d.mts} +2 -9
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -16,10 +16,11 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
LIBRARY_VERSION,
|
|
18
18
|
createKeyHealthRoute
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-QVD2QWST.mjs";
|
|
20
20
|
import {
|
|
21
21
|
ABSOLUTE_FLOOR,
|
|
22
22
|
ARCHETYPE_FLOOR_DEFAULT,
|
|
23
|
+
BRAIN_READ_ENV_NAMES,
|
|
23
24
|
PROVIDER_ENV_KEYS,
|
|
24
25
|
configureBrainQuery,
|
|
25
26
|
createBrainQueryCache,
|
|
@@ -42,7 +43,7 @@ import {
|
|
|
42
43
|
loadChainsFromBrain,
|
|
43
44
|
readBrainReadEnv,
|
|
44
45
|
resolveProviderKey
|
|
45
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-FT2FN6ZP.mjs";
|
|
46
47
|
import {
|
|
47
48
|
ALIASES,
|
|
48
49
|
LATENCY_TIER_MS,
|
|
@@ -53,7 +54,7 @@ import {
|
|
|
53
54
|
latencyTierOf,
|
|
54
55
|
profilesByProvider,
|
|
55
56
|
tryGetProfile
|
|
56
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-VVRDFE6T.mjs";
|
|
57
58
|
import {
|
|
58
59
|
emitAdvisoryFired,
|
|
59
60
|
emitCompileDone,
|
|
@@ -820,11 +821,18 @@ function passScoreTargets(ir, opts) {
|
|
|
820
821
|
}
|
|
821
822
|
}
|
|
822
823
|
let qualityGatePenalty = 0;
|
|
824
|
+
let structuredCliffGate;
|
|
823
825
|
if (constraints.structuredOutput) {
|
|
824
826
|
const schemaWeak = effectiveConventions(profile).some(
|
|
825
827
|
(c) => c.archetype === ir.intent.archetype && c.structuredOutputHint === "avoid"
|
|
826
828
|
);
|
|
827
829
|
if (schemaWeak) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
830
|
+
if (!schemaWeak) {
|
|
831
|
+
structuredCliffGate = profile.cliffs.find(
|
|
832
|
+
(c) => c.action === "quality_gate_structured" && (!c.whenIntent || c.whenIntent === ir.intent.archetype) && c.metric === "input_tokens" && opts.estimatedInputTokens >= c.threshold
|
|
833
|
+
);
|
|
834
|
+
if (structuredCliffGate) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
835
|
+
}
|
|
828
836
|
}
|
|
829
837
|
const measuredGate = opts.measuredFailureGates?.get(modelId);
|
|
830
838
|
if (measuredGate) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
@@ -889,6 +897,16 @@ function passScoreTargets(ir, opts) {
|
|
|
889
897
|
rankAfter: rank,
|
|
890
898
|
description: `Model ${modelId} gated below the quality floor for archetype '${ir.intent.archetype}' by MEASURED evidence from this app's own outcomes \u2014 ${measuredGate.nFail} of ${measuredGate.n} attempts failed on the quality axis in the trailing window (${pct(measuredGate.rate)}; 95% lower bound ${pct(measuredGate.lowerBound)} > 50%). Rank ${rankBefore.toFixed(2)} \u2192 ${rank.toFixed(2)} (\u2212${qualityGatePenalty.toFixed(2)}). Down-ranked out of leadership; retained as graceful fallback only. The gate is derived, not stored \u2014 it lifts on its own once the failures age out of the window.`
|
|
891
899
|
});
|
|
900
|
+
} else if (structuredCliffGate) {
|
|
901
|
+
policyMutations.push({
|
|
902
|
+
id: `quality-gate-structured-cliff-${modelId}`,
|
|
903
|
+
source: "quality_gate",
|
|
904
|
+
passName: "score_targets",
|
|
905
|
+
rankDelta: -qualityGatePenalty,
|
|
906
|
+
rankBefore,
|
|
907
|
+
rankAfter: rank,
|
|
908
|
+
description: `Model ${modelId} gated below the quality floor for archetype '${ir.intent.archetype}' \u2014 declared structuredOutput + input_tokens \u2265 ${structuredCliffGate.threshold} trips a measured cliff: ${structuredCliffGate.reason} Rank ${rankBefore.toFixed(2)} \u2192 ${rank.toFixed(2)} (\u2212${qualityGatePenalty.toFixed(2)}). Down-ranked out of leadership; retained as graceful fallback only. Bundled knowledge \u2014 active on cold isolates with no brain.`
|
|
909
|
+
});
|
|
892
910
|
} else {
|
|
893
911
|
policyMutations.push({
|
|
894
912
|
id: `quality-gate-structured-${modelId}`,
|
|
@@ -1572,13 +1590,8 @@ function getArchetypePerfScore(modelId, archetype) {
|
|
|
1572
1590
|
return { score, n, grounding };
|
|
1573
1591
|
}
|
|
1574
1592
|
|
|
1575
|
-
// src/
|
|
1576
|
-
function
|
|
1577
|
-
if (!x || typeof x !== "object") return false;
|
|
1578
|
-
const r = x;
|
|
1579
|
-
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";
|
|
1580
|
-
}
|
|
1581
|
-
function coerceNumber(v) {
|
|
1593
|
+
// src/measured-failure-brain.ts
|
|
1594
|
+
function coerceCount(v) {
|
|
1582
1595
|
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
1583
1596
|
if (typeof v === "string") {
|
|
1584
1597
|
const n = Number(v);
|
|
@@ -1586,58 +1599,144 @@ function coerceNumber(v) {
|
|
|
1586
1599
|
}
|
|
1587
1600
|
return null;
|
|
1588
1601
|
}
|
|
1589
|
-
function
|
|
1602
|
+
function isRawFailureRow(x) {
|
|
1603
|
+
if (!x || typeof x !== "object") return false;
|
|
1604
|
+
const r = x;
|
|
1605
|
+
return typeof r.intent_archetype === "string" && typeof r.model === "string" && (typeof r.n === "number" || typeof r.n === "string");
|
|
1606
|
+
}
|
|
1607
|
+
function mapRows(rows) {
|
|
1590
1608
|
const out = [];
|
|
1591
1609
|
for (const row of rows) {
|
|
1592
|
-
if (!
|
|
1593
|
-
const
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
if (sampleN === null || passRate === null || avgScore === null) continue;
|
|
1610
|
+
if (!isRawFailureRow(row)) continue;
|
|
1611
|
+
const n = coerceCount(row.n);
|
|
1612
|
+
const nFail = coerceCount(row.n_fail) ?? 0;
|
|
1613
|
+
if (n === null || n <= 0) continue;
|
|
1597
1614
|
out.push({
|
|
1598
1615
|
archetype: row.intent_archetype,
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
sampleN,
|
|
1603
|
-
judgePassRate: passRate,
|
|
1604
|
-
judgeAvgScore: avgScore,
|
|
1605
|
-
costDeltaPct: coerceNumber(row.cost_delta_pct),
|
|
1606
|
-
detectedAt: row.detected_at
|
|
1616
|
+
model: row.model,
|
|
1617
|
+
n,
|
|
1618
|
+
nFail
|
|
1607
1619
|
});
|
|
1608
1620
|
}
|
|
1609
1621
|
return out;
|
|
1610
1622
|
}
|
|
1623
|
+
var MEASURED_FAILURE_CFG = {
|
|
1624
|
+
/**
|
|
1625
|
+
* Hard minimum attempts before ANY gate may be created. Guards against
|
|
1626
|
+
* pathological tiny samples that the confidence bound alone would let
|
|
1627
|
+
* through in edge cases. At 5-for-5 the bound clears the threshold; at
|
|
1628
|
+
* 3-for-3 it does not, which is the behaviour we want (three failures is
|
|
1629
|
+
* a bad day, five in a row is a pattern).
|
|
1630
|
+
*/
|
|
1631
|
+
minSample: 5,
|
|
1632
|
+
/**
|
|
1633
|
+
* Gate when we are 95% confident the model fails MORE OFTEN THAN IT
|
|
1634
|
+
* SUCCEEDS on this surface. Deliberately unarguable rather than tuned —
|
|
1635
|
+
* a model that probably fails the majority of the time has no business
|
|
1636
|
+
* leading a surface, whatever its declared scores say.
|
|
1637
|
+
*/
|
|
1638
|
+
lowerBoundThreshold: 0.5,
|
|
1639
|
+
/** 95% one-sided-ish confidence (standard two-sided z at α=0.05). */
|
|
1640
|
+
z: 1.96,
|
|
1641
|
+
/** Must match the view's window. Documented here for the advisory text. */
|
|
1642
|
+
windowDays: 28
|
|
1643
|
+
};
|
|
1644
|
+
function wilsonLowerBound(failures, n, z = MEASURED_FAILURE_CFG.z) {
|
|
1645
|
+
if (n <= 0) return 0;
|
|
1646
|
+
const p = failures / n;
|
|
1647
|
+
const z2 = z * z;
|
|
1648
|
+
const denom = 1 + z2 / n;
|
|
1649
|
+
const centre = p + z2 / (2 * n);
|
|
1650
|
+
const margin = z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
|
|
1651
|
+
const lower2 = (centre - margin) / denom;
|
|
1652
|
+
return lower2 < 0 ? 0 : lower2;
|
|
1653
|
+
}
|
|
1654
|
+
function mapMeasuredFailureRows(rows) {
|
|
1655
|
+
return mapRows(rows);
|
|
1656
|
+
}
|
|
1657
|
+
function judgeMeasuredFailure(row, cfg = MEASURED_FAILURE_CFG) {
|
|
1658
|
+
if (!row) return void 0;
|
|
1659
|
+
const normalized = "nFail" in row && typeof row.n === "number" ? row : mapRows([row])[0];
|
|
1660
|
+
if (!normalized || normalized.n < cfg.minSample) return void 0;
|
|
1661
|
+
const lowerBound = wilsonLowerBound(normalized.nFail, normalized.n, cfg.z);
|
|
1662
|
+
return {
|
|
1663
|
+
gated: lowerBound > cfg.lowerBoundThreshold,
|
|
1664
|
+
rate: normalized.nFail / normalized.n,
|
|
1665
|
+
lowerBound,
|
|
1666
|
+
n: normalized.n,
|
|
1667
|
+
nFail: normalized.nFail
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1611
1670
|
var snapshots2 = /* @__PURE__ */ new Map();
|
|
1612
1671
|
var runtime2;
|
|
1613
1672
|
var warnedOnce2 = false;
|
|
1614
|
-
|
|
1673
|
+
var DEFAULT_MEASURED_FAILURE_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/measured-failure";
|
|
1674
|
+
function isMeasuredFailureGateEnabledFromEnv(envSource) {
|
|
1675
|
+
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
1676
|
+
const raw = (env.KGAUTO_MEASURED_FAILURE_GATE ?? "").trim().toLowerCase();
|
|
1677
|
+
return !(raw === "0" || raw === "false");
|
|
1678
|
+
}
|
|
1679
|
+
function configureMeasuredFailureBrain(rt) {
|
|
1680
|
+
runtime2 = rt;
|
|
1681
|
+
snapshots2.clear();
|
|
1682
|
+
warnedOnce2 = false;
|
|
1683
|
+
}
|
|
1684
|
+
function isMeasuredFailureBrainActive() {
|
|
1615
1685
|
return runtime2 !== void 0;
|
|
1616
1686
|
}
|
|
1617
|
-
function
|
|
1687
|
+
function prefetchMeasuredFailure(appId) {
|
|
1618
1688
|
const rt = runtime2;
|
|
1619
|
-
if (!rt) return
|
|
1620
|
-
|
|
1621
|
-
if (!
|
|
1689
|
+
if (!rt || !appId) return void 0;
|
|
1690
|
+
let snap = snapshots2.get(appId);
|
|
1691
|
+
if (!snap) {
|
|
1692
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
1693
|
+
snapshots2.set(appId, snap);
|
|
1694
|
+
}
|
|
1695
|
+
if (snap.expiresAt > Date.now()) return void 0;
|
|
1696
|
+
const inflight = pendingRefreshes2.get(appId);
|
|
1697
|
+
if (inflight) return inflight;
|
|
1698
|
+
if (snap.refreshing) return void 0;
|
|
1699
|
+
snap.refreshing = true;
|
|
1700
|
+
void asyncRefresh2(rt, appId);
|
|
1701
|
+
return pendingRefreshes2.get(appId);
|
|
1702
|
+
}
|
|
1703
|
+
async function awaitMeasuredFailureReady(appId, timeoutMs) {
|
|
1704
|
+
if (!runtime2 || !appId) return;
|
|
1705
|
+
const pending = prefetchMeasuredFailure(appId) ?? pendingRefreshes2.get(appId);
|
|
1706
|
+
if (!(timeoutMs > 0)) return;
|
|
1707
|
+
if (!pending) return;
|
|
1708
|
+
let timer;
|
|
1709
|
+
try {
|
|
1710
|
+
await Promise.race([
|
|
1711
|
+
pending,
|
|
1712
|
+
new Promise((resolve) => {
|
|
1713
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
1714
|
+
})
|
|
1715
|
+
]);
|
|
1716
|
+
} catch {
|
|
1717
|
+
} finally {
|
|
1718
|
+
if (timer) clearTimeout(timer);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
function getMeasuredFailureVerdict(opts) {
|
|
1722
|
+
const rt = runtime2;
|
|
1723
|
+
if (!rt) return void 0;
|
|
1724
|
+
const { appId, archetype, model } = opts;
|
|
1725
|
+
if (!appId || !archetype || !model) return void 0;
|
|
1622
1726
|
let snap = snapshots2.get(appId);
|
|
1623
1727
|
if (!snap) {
|
|
1624
1728
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
1625
1729
|
snapshots2.set(appId, snap);
|
|
1626
1730
|
}
|
|
1627
1731
|
const now = Date.now();
|
|
1628
|
-
|
|
1629
|
-
if (stale && !snap.refreshing) {
|
|
1732
|
+
if (snap.expiresAt <= now && !snap.refreshing) {
|
|
1630
1733
|
snap.refreshing = true;
|
|
1631
1734
|
void asyncRefresh2(rt, appId);
|
|
1632
1735
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
if (opts.family) {
|
|
1638
|
-
rows = rows.filter((f) => f.family === opts.family);
|
|
1639
|
-
}
|
|
1640
|
-
return rows;
|
|
1736
|
+
const row = snap.data.find(
|
|
1737
|
+
(r) => r.archetype === archetype && r.model === model
|
|
1738
|
+
);
|
|
1739
|
+
return judgeMeasuredFailure(row);
|
|
1641
1740
|
}
|
|
1642
1741
|
var pendingRefreshes2 = /* @__PURE__ */ new Map();
|
|
1643
1742
|
async function asyncRefresh2(rt, appId) {
|
|
@@ -1661,12 +1760,11 @@ async function doRefresh2(rt, appId) {
|
|
|
1661
1760
|
try {
|
|
1662
1761
|
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
1663
1762
|
if (!res.ok) {
|
|
1664
|
-
throw new Error(`
|
|
1763
|
+
throw new Error(`measured-failure ${res.status}: ${res.statusText}`);
|
|
1665
1764
|
}
|
|
1666
1765
|
const body = await res.json();
|
|
1667
1766
|
if (runtime2 !== rt) return;
|
|
1668
|
-
|
|
1669
|
-
snap.data = rows;
|
|
1767
|
+
snap.data = Array.isArray(body) ? mapRows(body) : [];
|
|
1670
1768
|
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
1671
1769
|
snap.refreshing = false;
|
|
1672
1770
|
} catch (err) {
|
|
@@ -1681,193 +1779,84 @@ async function doRefresh2(rt, appId) {
|
|
|
1681
1779
|
}
|
|
1682
1780
|
function defaultOnError2(err) {
|
|
1683
1781
|
console.warn(
|
|
1684
|
-
"[kgauto]
|
|
1782
|
+
"[kgauto] measured-failure fetch failed (gate inactive until next refresh):",
|
|
1685
1783
|
err
|
|
1686
1784
|
);
|
|
1687
1785
|
}
|
|
1688
|
-
function
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1786
|
+
function _testResetMeasuredFailure() {
|
|
1787
|
+
runtime2 = void 0;
|
|
1788
|
+
snapshots2.clear();
|
|
1789
|
+
pendingRefreshes2 = /* @__PURE__ */ new Map();
|
|
1790
|
+
warnedOnce2 = false;
|
|
1693
1791
|
}
|
|
1694
|
-
async function
|
|
1695
|
-
const
|
|
1696
|
-
|
|
1697
|
-
archetype,
|
|
1698
|
-
family,
|
|
1699
|
-
resolution,
|
|
1700
|
-
resolutionNote,
|
|
1701
|
-
brainEndpoint,
|
|
1702
|
-
brainJwt,
|
|
1703
|
-
brainAnonKey,
|
|
1704
|
-
fetch: injectedFetch
|
|
1705
|
-
} = opts;
|
|
1706
|
-
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
1707
|
-
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
1708
|
-
if (!family) return { ok: false, reason: "family_required" };
|
|
1709
|
-
if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
|
|
1710
|
-
return { ok: false, reason: "resolution_invalid" };
|
|
1711
|
-
}
|
|
1712
|
-
const doFetch = resolveFetchImpl(injectedFetch);
|
|
1713
|
-
const base = normalizeEndpoint(brainEndpoint);
|
|
1714
|
-
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`;
|
|
1715
|
-
const patchBody = {
|
|
1716
|
-
resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1717
|
-
resolution
|
|
1718
|
-
};
|
|
1719
|
-
if (resolutionNote !== void 0) {
|
|
1720
|
-
patchBody.resolution_note = resolutionNote;
|
|
1721
|
-
}
|
|
1722
|
-
let res;
|
|
1723
|
-
try {
|
|
1724
|
-
res = await doFetch(url, {
|
|
1725
|
-
method: "PATCH",
|
|
1726
|
-
headers: {
|
|
1727
|
-
Authorization: `Bearer ${brainJwt}`,
|
|
1728
|
-
apikey: brainAnonKey,
|
|
1729
|
-
"Content-Type": "application/json",
|
|
1730
|
-
Accept: "application/json",
|
|
1731
|
-
Prefer: "return=minimal"
|
|
1732
|
-
},
|
|
1733
|
-
body: JSON.stringify(patchBody)
|
|
1734
|
-
});
|
|
1735
|
-
} catch (err) {
|
|
1736
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1737
|
-
return { ok: false, reason: `network_error:${msg}` };
|
|
1738
|
-
}
|
|
1739
|
-
if (res.status === 401 || res.status === 403) {
|
|
1740
|
-
return { ok: false, reason: "brain_auth_misconfig" };
|
|
1741
|
-
}
|
|
1742
|
-
if (res.status >= 500) {
|
|
1743
|
-
return { ok: false, reason: "brain_unavailable" };
|
|
1744
|
-
}
|
|
1745
|
-
if (!res.ok) {
|
|
1746
|
-
return { ok: false, reason: `patch_failed:${res.status}` };
|
|
1747
|
-
}
|
|
1748
|
-
return { ok: true };
|
|
1792
|
+
async function _testWaitForMeasuredFailureRefresh() {
|
|
1793
|
+
const pending = Array.from(pendingRefreshes2.values());
|
|
1794
|
+
if (pending.length > 0) await Promise.all(pending);
|
|
1749
1795
|
}
|
|
1750
1796
|
|
|
1751
|
-
// src/
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
function shouldFirePromoteReady(finding, resolvedPrimary) {
|
|
1757
|
-
if (finding.currentModel !== resolvedPrimary) return false;
|
|
1758
|
-
if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
|
|
1759
|
-
if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
|
|
1760
|
-
return true;
|
|
1797
|
+
// src/promotions-brain.ts
|
|
1798
|
+
function isRawPromotionRow(x) {
|
|
1799
|
+
if (!x || typeof x !== "object") return false;
|
|
1800
|
+
const r = x;
|
|
1801
|
+
return (typeof r.id === "number" || typeof r.id === "string") && typeof r.intent_archetype === "string" && typeof r.promoted_model === "string" && typeof r.incumbent_model === "string";
|
|
1761
1802
|
}
|
|
1762
|
-
function
|
|
1763
|
-
if (
|
|
1764
|
-
if (
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
|
|
1769
|
-
if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
|
|
1770
|
-
if (modelId.startsWith("deepseek-")) return "deepseek-chat";
|
|
1771
|
-
if (modelId.startsWith("gpt-")) return "openai-gpt";
|
|
1803
|
+
function coerceId(v) {
|
|
1804
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
1805
|
+
if (typeof v === "string") {
|
|
1806
|
+
const n = Number(v);
|
|
1807
|
+
return Number.isFinite(n) ? n : null;
|
|
1808
|
+
}
|
|
1772
1809
|
return null;
|
|
1773
1810
|
}
|
|
1774
|
-
function
|
|
1775
|
-
if (!isPromoteReadyBrainActive()) return [];
|
|
1776
|
-
if (!ctx.appId) return [];
|
|
1777
|
-
if (!ctx.resolvedPrimary) return [];
|
|
1778
|
-
const family = deriveFamilyLocal(ctx.resolvedPrimary);
|
|
1779
|
-
if (!family) return [];
|
|
1780
|
-
const findings = loadPromoteReadyFindings({
|
|
1781
|
-
appId: ctx.appId,
|
|
1782
|
-
archetype: ctx.archetype,
|
|
1783
|
-
family
|
|
1784
|
-
});
|
|
1785
|
-
if (findings.length === 0) return [];
|
|
1786
|
-
const qualifying = findings.filter(
|
|
1787
|
-
(f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
|
|
1788
|
-
);
|
|
1789
|
-
if (qualifying.length === 0) return [];
|
|
1790
|
-
qualifying.sort((a, b) => {
|
|
1791
|
-
if (a.judgeAvgScore !== b.judgeAvgScore) {
|
|
1792
|
-
return b.judgeAvgScore - a.judgeAvgScore;
|
|
1793
|
-
}
|
|
1794
|
-
return b.judgePassRate - a.judgePassRate;
|
|
1795
|
-
});
|
|
1796
|
-
const top = qualifying[0];
|
|
1797
|
-
const pctPass = Math.round(top.judgePassRate * 100);
|
|
1798
|
-
const score = top.judgeAvgScore.toFixed(2);
|
|
1799
|
-
let costClause = "";
|
|
1800
|
-
if (top.costDeltaPct !== null) {
|
|
1801
|
-
const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
|
|
1802
|
-
const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
|
|
1803
|
-
costClause = `, cost ${magnitude}% ${sign}`;
|
|
1804
|
-
}
|
|
1805
|
-
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.`;
|
|
1806
|
-
return [
|
|
1807
|
-
{
|
|
1808
|
-
level: "info",
|
|
1809
|
-
code: "promote-ready",
|
|
1810
|
-
message,
|
|
1811
|
-
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.`,
|
|
1812
|
-
// alpha.36 architectural field — not a no-ai-needed case.
|
|
1813
|
-
recommendedArchitecture: void 0,
|
|
1814
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
1815
|
-
}
|
|
1816
|
-
];
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
// src/advisor-rules/consumer-on-stale-model.ts
|
|
1820
|
-
function isStaleStatus(v) {
|
|
1821
|
-
return v === "legacy" || v === "deprecated";
|
|
1822
|
-
}
|
|
1823
|
-
function asString(v) {
|
|
1824
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1825
|
-
}
|
|
1826
|
-
function mapRowsToFindings3(rows) {
|
|
1811
|
+
function mapRowsToPromotions(rows) {
|
|
1827
1812
|
const out = [];
|
|
1828
|
-
for (const
|
|
1829
|
-
if (!
|
|
1830
|
-
const
|
|
1831
|
-
|
|
1832
|
-
const
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
};
|
|
1850
|
-
const suggestion = asString(r.suggestion);
|
|
1851
|
-
if (suggestion) row.suggestion = suggestion;
|
|
1852
|
-
if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
|
|
1853
|
-
row.observationCount = r.observation_count;
|
|
1854
|
-
}
|
|
1855
|
-
out.push(row);
|
|
1813
|
+
for (const row of rows) {
|
|
1814
|
+
if (!isRawPromotionRow(row)) continue;
|
|
1815
|
+
const id = coerceId(row.id);
|
|
1816
|
+
if (id === null) continue;
|
|
1817
|
+
const mode = row.mode === "strategy" ? "strategy" : row.mode === "downswap" || row.mode === void 0 ? "downswap" : null;
|
|
1818
|
+
if (mode === null) continue;
|
|
1819
|
+
out.push({
|
|
1820
|
+
id,
|
|
1821
|
+
archetype: row.intent_archetype,
|
|
1822
|
+
mode,
|
|
1823
|
+
strategy: typeof row.strategy === "string" ? row.strategy : null,
|
|
1824
|
+
promotedModel: row.promoted_model,
|
|
1825
|
+
incumbentModel: row.incumbent_model,
|
|
1826
|
+
evalRunId: coerceId(row.eval_run_id ?? null),
|
|
1827
|
+
suppressQualityGate: row.suppress_quality_gate === true,
|
|
1828
|
+
promotedAt: typeof row.promoted_at === "string" ? row.promoted_at : "",
|
|
1829
|
+
// Pre-.78 endpoints serve no status column and only active rows —
|
|
1830
|
+
// defaulting to 'active' is exact, not optimistic.
|
|
1831
|
+
status: row.status === "rolled_back" ? "rolled_back" : "active",
|
|
1832
|
+
...typeof row.rolled_back_at === "string" ? { rolledBackAt: row.rolled_back_at } : {},
|
|
1833
|
+
...typeof row.rollback_class === "string" ? { rollbackClass: row.rollback_class } : {}
|
|
1834
|
+
});
|
|
1856
1835
|
}
|
|
1857
1836
|
return out;
|
|
1858
1837
|
}
|
|
1859
1838
|
var snapshots3 = /* @__PURE__ */ new Map();
|
|
1860
1839
|
var runtime3;
|
|
1861
1840
|
var warnedOnce3 = false;
|
|
1862
|
-
var
|
|
1863
|
-
function
|
|
1841
|
+
var DEFAULT_PROMOTIONS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/promotions";
|
|
1842
|
+
function isAutoPromoteEnabledFromEnv(envSource) {
|
|
1843
|
+
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
1844
|
+
const raw = (env.KGAUTO_AUTO_PROMOTE ?? "").trim().toLowerCase();
|
|
1845
|
+
return raw === "1" || raw === "true";
|
|
1846
|
+
}
|
|
1847
|
+
function configurePromotionsBrain(rt) {
|
|
1848
|
+
runtime3 = rt;
|
|
1849
|
+
snapshots3.clear();
|
|
1850
|
+
warnedOnce3 = false;
|
|
1851
|
+
}
|
|
1852
|
+
function isPromotionsBrainActive() {
|
|
1864
1853
|
return runtime3 !== void 0;
|
|
1865
1854
|
}
|
|
1866
|
-
function
|
|
1855
|
+
function getApplicablePromotion(opts) {
|
|
1867
1856
|
const rt = runtime3;
|
|
1868
|
-
if (!rt) return
|
|
1857
|
+
if (!rt) return void 0;
|
|
1869
1858
|
const appId = opts.appId;
|
|
1870
|
-
if (!appId) return
|
|
1859
|
+
if (!appId || !opts.archetype || !opts.mode) return void 0;
|
|
1871
1860
|
let snap = snapshots3.get(appId);
|
|
1872
1861
|
if (!snap) {
|
|
1873
1862
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
@@ -1879,11 +1868,31 @@ function getStaleModelFindings(opts) {
|
|
|
1879
1868
|
snap.refreshing = true;
|
|
1880
1869
|
void asyncRefresh3(rt, appId);
|
|
1881
1870
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1871
|
+
return snap.data.find(
|
|
1872
|
+
(p) => p.status === "active" && p.archetype === opts.archetype && p.mode === opts.mode
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
var ROLLBACK_SUPPRESSION_WINDOW_DAYS = 28;
|
|
1876
|
+
function getRecentRollback(opts) {
|
|
1877
|
+
const rt = runtime3;
|
|
1878
|
+
if (!rt) return void 0;
|
|
1879
|
+
if (!opts.appId || !opts.archetype || !opts.model) return void 0;
|
|
1880
|
+
let snap = snapshots3.get(opts.appId);
|
|
1881
|
+
if (!snap) {
|
|
1882
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
1883
|
+
snapshots3.set(opts.appId, snap);
|
|
1884
1884
|
}
|
|
1885
|
-
|
|
1885
|
+
const now = opts.nowMs ?? Date.now();
|
|
1886
|
+
if (snap.expiresAt <= now && !snap.refreshing) {
|
|
1887
|
+
snap.refreshing = true;
|
|
1888
|
+
void asyncRefresh3(rt, opts.appId);
|
|
1889
|
+
}
|
|
1890
|
+
const windowMs = (opts.windowDays ?? ROLLBACK_SUPPRESSION_WINDOW_DAYS) * 864e5;
|
|
1891
|
+
return snap.data.find(
|
|
1892
|
+
(p) => p.status === "rolled_back" && p.archetype === opts.archetype && p.promotedModel === opts.model && typeof p.rolledBackAt === "string" && now - Date.parse(p.rolledBackAt) <= windowMs
|
|
1893
|
+
);
|
|
1886
1894
|
}
|
|
1895
|
+
var pendingRefreshes3 = /* @__PURE__ */ new Map();
|
|
1887
1896
|
async function asyncRefresh3(rt, appId) {
|
|
1888
1897
|
const promise = doRefresh3(rt, appId);
|
|
1889
1898
|
pendingRefreshes3.set(appId, promise);
|
|
@@ -1896,7 +1905,7 @@ async function asyncRefresh3(rt, appId) {
|
|
|
1896
1905
|
}
|
|
1897
1906
|
}
|
|
1898
1907
|
async function doRefresh3(rt, appId) {
|
|
1899
|
-
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
1908
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}&with_rollbacks=1`;
|
|
1900
1909
|
let snap = snapshots3.get(appId);
|
|
1901
1910
|
if (!snap) {
|
|
1902
1911
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
@@ -1905,11 +1914,11 @@ async function doRefresh3(rt, appId) {
|
|
|
1905
1914
|
try {
|
|
1906
1915
|
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
1907
1916
|
if (!res.ok) {
|
|
1908
|
-
throw new Error(`
|
|
1917
|
+
throw new Error(`promotions ${res.status}: ${res.statusText}`);
|
|
1909
1918
|
}
|
|
1910
1919
|
const body = await res.json();
|
|
1911
1920
|
if (runtime3 !== rt) return;
|
|
1912
|
-
const rows = Array.isArray(body) ?
|
|
1921
|
+
const rows = Array.isArray(body) ? mapRowsToPromotions(body) : [];
|
|
1913
1922
|
snap.data = rows;
|
|
1914
1923
|
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
1915
1924
|
snap.refreshing = false;
|
|
@@ -1925,847 +1934,905 @@ async function doRefresh3(rt, appId) {
|
|
|
1925
1934
|
}
|
|
1926
1935
|
function defaultOnError3(err) {
|
|
1927
1936
|
console.warn(
|
|
1928
|
-
"[kgauto]
|
|
1937
|
+
"[kgauto] promotions fetch failed (promotion boost inactive until next refresh):",
|
|
1929
1938
|
err
|
|
1930
1939
|
);
|
|
1931
1940
|
}
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
if (
|
|
1941
|
-
const ranked = [...findings].sort((a, b) => {
|
|
1942
|
-
if (a.staleStatus !== b.staleStatus) {
|
|
1943
|
-
return a.staleStatus === "deprecated" ? -1 : 1;
|
|
1944
|
-
}
|
|
1945
|
-
return a.staleModel.localeCompare(b.staleModel);
|
|
1946
|
-
});
|
|
1947
|
-
const top = ranked[0];
|
|
1948
|
-
const extraCount = findings.length - 1;
|
|
1949
|
-
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
1950
|
-
return [
|
|
1951
|
-
{
|
|
1952
|
-
level: "warn",
|
|
1953
|
-
code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
|
|
1954
|
-
message: `${top.message}${extraNote}`,
|
|
1955
|
-
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}.`,
|
|
1956
|
-
recommendationType: "model-swap",
|
|
1957
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
1958
|
-
}
|
|
1959
|
-
];
|
|
1941
|
+
function _testResetPromotions() {
|
|
1942
|
+
runtime3 = void 0;
|
|
1943
|
+
snapshots3.clear();
|
|
1944
|
+
pendingRefreshes3 = /* @__PURE__ */ new Map();
|
|
1945
|
+
warnedOnce3 = false;
|
|
1946
|
+
}
|
|
1947
|
+
async function _testWaitForPromotionsRefresh() {
|
|
1948
|
+
const pending = Array.from(pendingRefreshes3.values());
|
|
1949
|
+
if (pending.length > 0) await Promise.all(pending);
|
|
1960
1950
|
}
|
|
1961
1951
|
|
|
1962
|
-
// src/
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
{
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
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.",
|
|
1974
|
-
costGuidance: "comparable or cheaper at current pricing"
|
|
1975
|
-
}
|
|
1976
|
-
]);
|
|
1977
|
-
function findBetterFit(archetype, currentFamily) {
|
|
1978
|
-
for (const fit of ARCHETYPE_FAMILY_FITS) {
|
|
1979
|
-
if (fit.archetype !== archetype) continue;
|
|
1980
|
-
if (fit.betterFitFamily === currentFamily) return null;
|
|
1981
|
-
return fit;
|
|
1952
|
+
// src/promote-ready-brain.ts
|
|
1953
|
+
function isRawPromoteReadyRow(x) {
|
|
1954
|
+
if (!x || typeof x !== "object") return false;
|
|
1955
|
+
const r = x;
|
|
1956
|
+
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";
|
|
1957
|
+
}
|
|
1958
|
+
function coerceNumber(v) {
|
|
1959
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
1960
|
+
if (typeof v === "string") {
|
|
1961
|
+
const n = Number(v);
|
|
1962
|
+
return Number.isFinite(n) ? n : null;
|
|
1982
1963
|
}
|
|
1983
1964
|
return null;
|
|
1984
1965
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
const
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
if (
|
|
1993
|
-
|
|
1966
|
+
function mapRowsToFindings2(rows) {
|
|
1967
|
+
const out = [];
|
|
1968
|
+
for (const row of rows) {
|
|
1969
|
+
if (!isRawPromoteReadyRow(row)) continue;
|
|
1970
|
+
const sampleN = coerceNumber(row.sample_n);
|
|
1971
|
+
const passRate = coerceNumber(row.judge_pass_rate);
|
|
1972
|
+
const avgScore = coerceNumber(row.judge_avg_score);
|
|
1973
|
+
if (sampleN === null || passRate === null || avgScore === null) continue;
|
|
1974
|
+
out.push({
|
|
1975
|
+
archetype: row.intent_archetype,
|
|
1976
|
+
family: row.family,
|
|
1977
|
+
candidateModel: row.candidate_model,
|
|
1978
|
+
currentModel: row.current_model,
|
|
1979
|
+
sampleN,
|
|
1980
|
+
judgePassRate: passRate,
|
|
1981
|
+
judgeAvgScore: avgScore,
|
|
1982
|
+
costDeltaPct: coerceNumber(row.cost_delta_pct),
|
|
1983
|
+
detectedAt: row.detected_at
|
|
1984
|
+
});
|
|
1994
1985
|
}
|
|
1995
|
-
return
|
|
1986
|
+
return out;
|
|
1996
1987
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
if (profile.status !== "current") continue;
|
|
2003
|
-
if (profile.active === false) continue;
|
|
2004
|
-
candidates.push(profile.id);
|
|
2005
|
-
if (candidates.length >= 3) break;
|
|
2006
|
-
}
|
|
2007
|
-
return candidates;
|
|
1988
|
+
var snapshots4 = /* @__PURE__ */ new Map();
|
|
1989
|
+
var runtime4;
|
|
1990
|
+
var warnedOnce4 = false;
|
|
1991
|
+
function isPromoteReadyBrainActive() {
|
|
1992
|
+
return runtime4 !== void 0;
|
|
2008
1993
|
}
|
|
2009
|
-
function
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
if (!
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
1994
|
+
function loadPromoteReadyFindings(opts) {
|
|
1995
|
+
const rt = runtime4;
|
|
1996
|
+
if (!rt) return [];
|
|
1997
|
+
const appId = opts.appId;
|
|
1998
|
+
if (!appId) return [];
|
|
1999
|
+
let snap = snapshots4.get(appId);
|
|
2000
|
+
if (!snap) {
|
|
2001
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2002
|
+
snapshots4.set(appId, snap);
|
|
2003
|
+
}
|
|
2004
|
+
const now = Date.now();
|
|
2005
|
+
const stale = snap.expiresAt <= now;
|
|
2006
|
+
if (stale && !snap.refreshing) {
|
|
2007
|
+
snap.refreshing = true;
|
|
2008
|
+
void asyncRefresh4(rt, appId);
|
|
2009
|
+
}
|
|
2010
|
+
let rows = snap.data;
|
|
2011
|
+
if (opts.archetype) {
|
|
2012
|
+
rows = rows.filter((f) => f.archetype === opts.archetype);
|
|
2013
|
+
}
|
|
2014
|
+
if (opts.family) {
|
|
2015
|
+
rows = rows.filter((f) => f.family === opts.family);
|
|
2016
|
+
}
|
|
2017
|
+
return rows;
|
|
2018
|
+
}
|
|
2019
|
+
var pendingRefreshes4 = /* @__PURE__ */ new Map();
|
|
2020
|
+
async function asyncRefresh4(rt, appId) {
|
|
2021
|
+
const promise = doRefresh4(rt, appId);
|
|
2022
|
+
pendingRefreshes4.set(appId, promise);
|
|
2023
|
+
try {
|
|
2024
|
+
await promise;
|
|
2025
|
+
} finally {
|
|
2026
|
+
if (pendingRefreshes4.get(appId) === promise) {
|
|
2027
|
+
pendingRefreshes4.delete(appId);
|
|
2029
2028
|
}
|
|
2030
|
-
|
|
2029
|
+
}
|
|
2031
2030
|
}
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2031
|
+
async function doRefresh4(rt, appId) {
|
|
2032
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
2033
|
+
let snap = snapshots4.get(appId);
|
|
2034
|
+
if (!snap) {
|
|
2035
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2036
|
+
snapshots4.set(appId, snap);
|
|
2037
|
+
}
|
|
2038
|
+
try {
|
|
2039
|
+
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
2040
|
+
if (!res.ok) {
|
|
2041
|
+
throw new Error(`promote-ready ${res.status}: ${res.statusText}`);
|
|
2042
|
+
}
|
|
2043
|
+
const body = await res.json();
|
|
2044
|
+
if (runtime4 !== rt) return;
|
|
2045
|
+
const rows = Array.isArray(body) ? mapRowsToFindings2(body) : [];
|
|
2046
|
+
snap.data = rows;
|
|
2047
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
2048
|
+
snap.refreshing = false;
|
|
2049
|
+
} catch (err) {
|
|
2050
|
+
if (runtime4 !== rt) return;
|
|
2051
|
+
snap.refreshing = false;
|
|
2052
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
2053
|
+
if (!warnedOnce4) {
|
|
2054
|
+
warnedOnce4 = true;
|
|
2055
|
+
(rt.onError ?? defaultOnError4)(err);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2043
2058
|
}
|
|
2044
|
-
function
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2059
|
+
function defaultOnError4(err) {
|
|
2060
|
+
console.warn(
|
|
2061
|
+
"[kgauto] promote-ready fetch failed (using empty fallback):",
|
|
2062
|
+
err
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
function resolveFetchImpl(injected) {
|
|
2066
|
+
return injected ?? ((...args) => globalThis.fetch(...args));
|
|
2067
|
+
}
|
|
2068
|
+
function normalizeEndpoint(endpoint) {
|
|
2069
|
+
return endpoint.replace(/\/+$/, "");
|
|
2070
|
+
}
|
|
2071
|
+
async function markPromoteReadyHandled(opts) {
|
|
2072
|
+
const {
|
|
2073
|
+
appId,
|
|
2074
|
+
archetype,
|
|
2075
|
+
family,
|
|
2076
|
+
resolution,
|
|
2077
|
+
resolutionNote,
|
|
2078
|
+
brainEndpoint,
|
|
2079
|
+
brainJwt,
|
|
2080
|
+
brainAnonKey,
|
|
2081
|
+
fetch: injectedFetch
|
|
2082
|
+
} = opts;
|
|
2083
|
+
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
2084
|
+
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
2085
|
+
if (!family) return { ok: false, reason: "family_required" };
|
|
2086
|
+
if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
|
|
2087
|
+
return { ok: false, reason: "resolution_invalid" };
|
|
2055
2088
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2089
|
+
const doFetch = resolveFetchImpl(injectedFetch);
|
|
2090
|
+
const base = normalizeEndpoint(brainEndpoint);
|
|
2091
|
+
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`;
|
|
2092
|
+
const patchBody = {
|
|
2093
|
+
resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2094
|
+
resolution
|
|
2095
|
+
};
|
|
2096
|
+
if (resolutionNote !== void 0) {
|
|
2097
|
+
patchBody.resolution_note = resolutionNote;
|
|
2058
2098
|
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2099
|
+
let res;
|
|
2100
|
+
try {
|
|
2101
|
+
res = await doFetch(url, {
|
|
2102
|
+
method: "PATCH",
|
|
2103
|
+
headers: {
|
|
2104
|
+
Authorization: `Bearer ${brainJwt}`,
|
|
2105
|
+
apikey: brainAnonKey,
|
|
2106
|
+
"Content-Type": "application/json",
|
|
2107
|
+
Accept: "application/json",
|
|
2108
|
+
Prefer: "return=minimal"
|
|
2109
|
+
},
|
|
2110
|
+
body: JSON.stringify(patchBody)
|
|
2111
|
+
});
|
|
2112
|
+
} catch (err) {
|
|
2113
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2114
|
+
return { ok: false, reason: `network_error:${msg}` };
|
|
2061
2115
|
}
|
|
2062
|
-
if (
|
|
2063
|
-
|
|
2064
|
-
...advisorRulePromoteReady({
|
|
2065
|
-
appId: ir.appId,
|
|
2066
|
-
archetype: ir.intent.archetype,
|
|
2067
|
-
resolvedPrimary: profile.id
|
|
2068
|
-
})
|
|
2069
|
-
);
|
|
2070
|
-
out.push(...advisorRuleConsumerOnStaleModel(ir));
|
|
2116
|
+
if (res.status === 401 || res.status === 403) {
|
|
2117
|
+
return { ok: false, reason: "brain_auth_misconfig" };
|
|
2071
2118
|
}
|
|
2072
|
-
if (
|
|
2073
|
-
|
|
2074
|
-
...advisorRuleCrossFamilyFit({
|
|
2075
|
-
archetype: ir.intent.archetype,
|
|
2076
|
-
resolvedPrimary: profile.id
|
|
2077
|
-
})
|
|
2078
|
-
);
|
|
2119
|
+
if (res.status >= 500) {
|
|
2120
|
+
return { ok: false, reason: "brain_unavailable" };
|
|
2079
2121
|
}
|
|
2080
|
-
|
|
2081
|
-
}
|
|
2082
|
-
function translatorClearedToolCallCliff(phase2) {
|
|
2083
|
-
const rewrites = phase2?.sectionRewritesApplied;
|
|
2084
|
-
if (!rewrites || rewrites.length === 0) return false;
|
|
2085
|
-
for (const rw of rewrites) {
|
|
2086
|
-
if (rw.kind === "tool_call_contract") return true;
|
|
2122
|
+
if (!res.ok) {
|
|
2123
|
+
return { ok: false, reason: `patch_failed:${res.status}` };
|
|
2087
2124
|
}
|
|
2088
|
-
return
|
|
2125
|
+
return { ok: true };
|
|
2089
2126
|
}
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2127
|
+
|
|
2128
|
+
// src/advisor-rules/promote-ready.ts
|
|
2129
|
+
var PROMOTE_READY_THRESHOLDS = {
|
|
2130
|
+
minPassRate: 0.8,
|
|
2131
|
+
minAvgScore: 4
|
|
2132
|
+
};
|
|
2133
|
+
function shouldFirePromoteReady(finding, resolvedPrimary) {
|
|
2134
|
+
if (finding.currentModel !== resolvedPrimary) return false;
|
|
2135
|
+
if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
|
|
2136
|
+
if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
|
|
2137
|
+
return true;
|
|
2138
|
+
}
|
|
2139
|
+
function deriveFamilyLocal(modelId) {
|
|
2140
|
+
if (modelId.startsWith("claude-opus-")) return "claude-opus";
|
|
2141
|
+
if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
|
|
2142
|
+
if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
|
|
2143
|
+
if (/^gemini-.*-flash-lite/.test(modelId)) return "gemini-flash-lite";
|
|
2144
|
+
if (/^gemini-.*-flash/.test(modelId)) return "gemini-flash";
|
|
2145
|
+
if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
|
|
2146
|
+
if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
|
|
2147
|
+
if (modelId.startsWith("deepseek-")) return "deepseek-chat";
|
|
2148
|
+
if (modelId.startsWith("gpt-")) return "openai-gpt";
|
|
2149
|
+
return null;
|
|
2150
|
+
}
|
|
2151
|
+
function advisorRulePromoteReady(ctx) {
|
|
2152
|
+
if (!isPromoteReadyBrainActive()) return [];
|
|
2153
|
+
if (!ctx.appId) return [];
|
|
2154
|
+
if (!ctx.resolvedPrimary) return [];
|
|
2155
|
+
const family = deriveFamilyLocal(ctx.resolvedPrimary);
|
|
2156
|
+
if (!family) return [];
|
|
2157
|
+
const findings = loadPromoteReadyFindings({
|
|
2158
|
+
appId: ctx.appId,
|
|
2159
|
+
archetype: ctx.archetype,
|
|
2160
|
+
family
|
|
2161
|
+
});
|
|
2162
|
+
if (findings.length === 0) return [];
|
|
2163
|
+
const qualifying = findings.filter(
|
|
2164
|
+
(f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
|
|
2165
|
+
);
|
|
2166
|
+
if (qualifying.length === 0) return [];
|
|
2167
|
+
qualifying.sort((a, b) => {
|
|
2168
|
+
if (a.judgeAvgScore !== b.judgeAvgScore) {
|
|
2169
|
+
return b.judgeAvgScore - a.judgeAvgScore;
|
|
2170
|
+
}
|
|
2171
|
+
return b.judgePassRate - a.judgePassRate;
|
|
2172
|
+
});
|
|
2173
|
+
const top = qualifying[0];
|
|
2174
|
+
const pctPass = Math.round(top.judgePassRate * 100);
|
|
2175
|
+
const score = top.judgeAvgScore.toFixed(2);
|
|
2176
|
+
let costClause = "";
|
|
2177
|
+
if (top.costDeltaPct !== null) {
|
|
2178
|
+
const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
|
|
2179
|
+
const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
|
|
2180
|
+
costClause = `, cost ${magnitude}% ${sign}`;
|
|
2181
|
+
}
|
|
2182
|
+
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.`;
|
|
2096
2183
|
return [
|
|
2097
2184
|
{
|
|
2098
|
-
level: "
|
|
2099
|
-
code: "
|
|
2100
|
-
message
|
|
2101
|
-
suggestion:
|
|
2185
|
+
level: "info",
|
|
2186
|
+
code: "promote-ready",
|
|
2187
|
+
message,
|
|
2188
|
+
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.`,
|
|
2189
|
+
// alpha.36 architectural field — not a no-ai-needed case.
|
|
2190
|
+
recommendedArchitecture: void 0,
|
|
2102
2191
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2103
2192
|
}
|
|
2104
2193
|
];
|
|
2105
2194
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
if (!only || only.text.length <= 1e3) return [];
|
|
2111
|
-
return [
|
|
2112
|
-
{
|
|
2113
|
-
level: "info",
|
|
2114
|
-
code: "single-chunk-system",
|
|
2115
|
-
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.`,
|
|
2116
|
-
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.",
|
|
2117
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2118
|
-
}
|
|
2119
|
-
];
|
|
2195
|
+
|
|
2196
|
+
// src/advisor-rules/consumer-on-stale-model.ts
|
|
2197
|
+
function isStaleStatus(v) {
|
|
2198
|
+
return v === "legacy" || v === "deprecated";
|
|
2120
2199
|
}
|
|
2121
|
-
function
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
"
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
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.`,
|
|
2138
|
-
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.",
|
|
2139
|
-
docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
|
|
2200
|
+
function asString(v) {
|
|
2201
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
2202
|
+
}
|
|
2203
|
+
function mapRowsToFindings3(rows) {
|
|
2204
|
+
const out = [];
|
|
2205
|
+
for (const raw of rows) {
|
|
2206
|
+
if (!raw || typeof raw !== "object") continue;
|
|
2207
|
+
const r = raw;
|
|
2208
|
+
const archetype = asString(r.intent_archetype) ?? asString(r.applies_to_archetype);
|
|
2209
|
+
const staleModel = asString(r.stale_model) ?? asString(r.applies_to_model);
|
|
2210
|
+
const staleProvider = asString(r.stale_provider);
|
|
2211
|
+
const recommendedModel = asString(r.recommended_model);
|
|
2212
|
+
const family = asString(r.family);
|
|
2213
|
+
const message = asString(r.message);
|
|
2214
|
+
if (!archetype || !staleModel || !recommendedModel || !family || !message) {
|
|
2215
|
+
continue;
|
|
2140
2216
|
}
|
|
2141
|
-
|
|
2217
|
+
if (!isStaleStatus(r.stale_status)) continue;
|
|
2218
|
+
const row = {
|
|
2219
|
+
archetype,
|
|
2220
|
+
staleModel,
|
|
2221
|
+
staleProvider: staleProvider ?? "unknown",
|
|
2222
|
+
staleStatus: r.stale_status,
|
|
2223
|
+
recommendedModel,
|
|
2224
|
+
family,
|
|
2225
|
+
message
|
|
2226
|
+
};
|
|
2227
|
+
const suggestion = asString(r.suggestion);
|
|
2228
|
+
if (suggestion) row.suggestion = suggestion;
|
|
2229
|
+
if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
|
|
2230
|
+
row.observationCount = r.observation_count;
|
|
2231
|
+
}
|
|
2232
|
+
out.push(row);
|
|
2233
|
+
}
|
|
2234
|
+
return out;
|
|
2142
2235
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2236
|
+
var snapshots5 = /* @__PURE__ */ new Map();
|
|
2237
|
+
var runtime5;
|
|
2238
|
+
var warnedOnce5 = false;
|
|
2239
|
+
var pendingRefreshes5 = /* @__PURE__ */ new Map();
|
|
2240
|
+
function isStaleModelFindingsBrainActive() {
|
|
2241
|
+
return runtime5 !== void 0;
|
|
2242
|
+
}
|
|
2243
|
+
function getStaleModelFindings(opts) {
|
|
2244
|
+
const rt = runtime5;
|
|
2245
|
+
if (!rt) return [];
|
|
2246
|
+
const appId = opts.appId;
|
|
2247
|
+
if (!appId) return [];
|
|
2248
|
+
let snap = snapshots5.get(appId);
|
|
2249
|
+
if (!snap) {
|
|
2250
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2251
|
+
snapshots5.set(appId, snap);
|
|
2148
2252
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2253
|
+
const now = Date.now();
|
|
2254
|
+
const stale = snap.expiresAt <= now;
|
|
2255
|
+
if (stale && !snap.refreshing) {
|
|
2256
|
+
snap.refreshing = true;
|
|
2257
|
+
void asyncRefresh5(rt, appId);
|
|
2258
|
+
}
|
|
2259
|
+
if (opts.archetype) {
|
|
2260
|
+
return snap.data.filter((f) => f.archetype === opts.archetype);
|
|
2261
|
+
}
|
|
2262
|
+
return snap.data;
|
|
2158
2263
|
}
|
|
2159
|
-
function
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
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.`,
|
|
2168
|
-
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.",
|
|
2169
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
|
|
2264
|
+
async function asyncRefresh5(rt, appId) {
|
|
2265
|
+
const promise = doRefresh5(rt, appId);
|
|
2266
|
+
pendingRefreshes5.set(appId, promise);
|
|
2267
|
+
try {
|
|
2268
|
+
await promise;
|
|
2269
|
+
} finally {
|
|
2270
|
+
if (pendingRefreshes5.get(appId) === promise) {
|
|
2271
|
+
pendingRefreshes5.delete(appId);
|
|
2170
2272
|
}
|
|
2171
|
-
|
|
2273
|
+
}
|
|
2172
2274
|
}
|
|
2173
|
-
function
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
if (!chosenHasRoomToGrow) return [];
|
|
2180
|
-
let bestAlt = null;
|
|
2181
|
-
for (const altId of phase2.fallbackChain) {
|
|
2182
|
-
const altProfile = phase2.profileResolver(altId);
|
|
2183
|
-
if (!altProfile) continue;
|
|
2184
|
-
if (altProfile.id === profile.id) continue;
|
|
2185
|
-
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
2186
|
-
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
2187
|
-
if (altScore.score < chosenScore.score) continue;
|
|
2188
|
-
if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
|
|
2189
|
-
if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
|
|
2190
|
-
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
2191
|
-
}
|
|
2275
|
+
async function doRefresh5(rt, appId) {
|
|
2276
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
2277
|
+
let snap = snapshots5.get(appId);
|
|
2278
|
+
if (!snap) {
|
|
2279
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2280
|
+
snapshots5.set(appId, snap);
|
|
2192
2281
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
|
|
2198
|
-
return [
|
|
2199
|
-
{
|
|
2200
|
-
level: "warn",
|
|
2201
|
-
code: "cost-mismatched-archetype",
|
|
2202
|
-
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.`,
|
|
2203
|
-
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.`,
|
|
2204
|
-
recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
|
|
2205
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2282
|
+
try {
|
|
2283
|
+
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
2284
|
+
if (!res.ok) {
|
|
2285
|
+
throw new Error(`stale-model findings ${res.status}: ${res.statusText}`);
|
|
2206
2286
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
recommendationType: "prompt-fix",
|
|
2221
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2287
|
+
const body = await res.json();
|
|
2288
|
+
if (runtime5 !== rt) return;
|
|
2289
|
+
const rows = Array.isArray(body) ? mapRowsToFindings3(body) : [];
|
|
2290
|
+
snap.data = rows;
|
|
2291
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
2292
|
+
snap.refreshing = false;
|
|
2293
|
+
} catch (err) {
|
|
2294
|
+
if (runtime5 !== rt) return;
|
|
2295
|
+
snap.refreshing = false;
|
|
2296
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
2297
|
+
if (!warnedOnce5) {
|
|
2298
|
+
warnedOnce5 = true;
|
|
2299
|
+
(rt.onError ?? defaultOnError5)(err);
|
|
2222
2300
|
}
|
|
2223
|
-
|
|
2301
|
+
}
|
|
2224
2302
|
}
|
|
2225
|
-
function
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
if (
|
|
2242
|
-
|
|
2303
|
+
function defaultOnError5(err) {
|
|
2304
|
+
console.warn(
|
|
2305
|
+
"[kgauto] stale-model findings fetch failed (using empty fallback):",
|
|
2306
|
+
err
|
|
2307
|
+
);
|
|
2308
|
+
}
|
|
2309
|
+
var CONSUMER_ON_STALE_MODEL_RULE_CODE = "consumer-on-stale-model";
|
|
2310
|
+
function advisorRuleConsumerOnStaleModel(ir) {
|
|
2311
|
+
if (!isStaleModelFindingsBrainActive()) return [];
|
|
2312
|
+
if (!ir.appId) return [];
|
|
2313
|
+
const findings = getStaleModelFindings({
|
|
2314
|
+
appId: ir.appId,
|
|
2315
|
+
archetype: ir.intent.archetype
|
|
2316
|
+
});
|
|
2317
|
+
if (findings.length === 0) return [];
|
|
2318
|
+
const ranked = [...findings].sort((a, b) => {
|
|
2319
|
+
if (a.staleStatus !== b.staleStatus) {
|
|
2320
|
+
return a.staleStatus === "deprecated" ? -1 : 1;
|
|
2243
2321
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
const
|
|
2322
|
+
return a.staleModel.localeCompare(b.staleModel);
|
|
2323
|
+
});
|
|
2324
|
+
const top = ranked[0];
|
|
2325
|
+
const extraCount = findings.length - 1;
|
|
2326
|
+
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
2247
2327
|
return [
|
|
2248
2328
|
{
|
|
2249
2329
|
level: "warn",
|
|
2250
|
-
code:
|
|
2251
|
-
message:
|
|
2252
|
-
suggestion: `
|
|
2253
|
-
recommendationType: "
|
|
2330
|
+
code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
|
|
2331
|
+
message: `${top.message}${extraNote}`,
|
|
2332
|
+
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}.`,
|
|
2333
|
+
recommendationType: "model-swap",
|
|
2254
2334
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2255
2335
|
}
|
|
2256
2336
|
];
|
|
2257
2337
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2338
|
+
|
|
2339
|
+
// src/archetype-fits.ts
|
|
2340
|
+
var ARCHETYPE_FAMILY_FITS = Object.freeze([
|
|
2341
|
+
{
|
|
2342
|
+
archetype: "plan",
|
|
2343
|
+
betterFitFamily: "deepseek-reasoner",
|
|
2344
|
+
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).",
|
|
2345
|
+
costGuidance: "substantially cheaper at current pricing (deepseek-v4-pro promo: ~7-17x cheaper than sonnet)"
|
|
2346
|
+
},
|
|
2347
|
+
{
|
|
2348
|
+
archetype: "critique",
|
|
2349
|
+
betterFitFamily: "deepseek-reasoner",
|
|
2350
|
+
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.",
|
|
2351
|
+
costGuidance: "comparable or cheaper at current pricing"
|
|
2352
|
+
}
|
|
2353
|
+
]);
|
|
2354
|
+
function findBetterFit(archetype, currentFamily) {
|
|
2355
|
+
for (const fit of ARCHETYPE_FAMILY_FITS) {
|
|
2356
|
+
if (fit.archetype !== archetype) continue;
|
|
2357
|
+
if (fit.betterFitFamily === currentFamily) return null;
|
|
2358
|
+
return fit;
|
|
2359
|
+
}
|
|
2360
|
+
return null;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
// src/advisor-rules/cross-family-fit.ts
|
|
2364
|
+
function familyHasCurrentActiveModel(family) {
|
|
2365
|
+
for (const profile of allProfiles()) {
|
|
2366
|
+
const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
|
|
2367
|
+
if (profileFamily !== family) continue;
|
|
2368
|
+
if (profile.status !== "current") continue;
|
|
2369
|
+
if (profile.active === false) continue;
|
|
2370
|
+
return true;
|
|
2371
|
+
}
|
|
2372
|
+
return false;
|
|
2373
|
+
}
|
|
2374
|
+
function listCandidatesInFamily(family) {
|
|
2375
|
+
const candidates = [];
|
|
2376
|
+
for (const profile of allProfiles()) {
|
|
2377
|
+
const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
|
|
2378
|
+
if (profileFamily !== family) continue;
|
|
2379
|
+
if (profile.status !== "current") continue;
|
|
2380
|
+
if (profile.active === false) continue;
|
|
2381
|
+
candidates.push(profile.id);
|
|
2382
|
+
if (candidates.length >= 3) break;
|
|
2276
2383
|
}
|
|
2384
|
+
return candidates;
|
|
2385
|
+
}
|
|
2386
|
+
function advisorRuleCrossFamilyFit(ctx) {
|
|
2387
|
+
if (!ctx.resolvedPrimary) return [];
|
|
2388
|
+
const currentFamily = deriveFamilyFromModelId(ctx.resolvedPrimary);
|
|
2389
|
+
if (!currentFamily) return [];
|
|
2390
|
+
const fit = findBetterFit(ctx.archetype, currentFamily);
|
|
2391
|
+
if (!fit) return [];
|
|
2392
|
+
if (!familyHasCurrentActiveModel(fit.betterFitFamily)) return [];
|
|
2393
|
+
const candidates = listCandidatesInFamily(fit.betterFitFamily);
|
|
2394
|
+
if (candidates.length === 0) return [];
|
|
2395
|
+
const candidateStr = candidates.join(", ");
|
|
2396
|
+
const message = `Your ${currentFamily} call on ${ctx.archetype} could shift to ${fit.betterFitFamily} \u2014 typically better quality + ${fit.costGuidance}. Suggested candidates: ${candidateStr}.`;
|
|
2277
2397
|
return [
|
|
2278
2398
|
{
|
|
2279
|
-
level: "
|
|
2280
|
-
code: "
|
|
2281
|
-
|
|
2282
|
-
|
|
2399
|
+
level: "info",
|
|
2400
|
+
code: "cross-family-fit-candidate",
|
|
2401
|
+
ownership: "consumer-actionable",
|
|
2402
|
+
message,
|
|
2403
|
+
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.`,
|
|
2283
2404
|
recommendationType: "model-swap",
|
|
2284
2405
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2285
2406
|
}
|
|
2286
2407
|
];
|
|
2287
2408
|
}
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2409
|
+
|
|
2410
|
+
// src/advisor.ts
|
|
2411
|
+
var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
|
|
2412
|
+
var TIER_DOWN_COST_RATIO = 0.5;
|
|
2413
|
+
var COST_MISMATCHED_CHOSEN_SCORE_CEILING = 7;
|
|
2414
|
+
var PRODUCER_OWNED_RULE_CODES = Object.freeze(
|
|
2415
|
+
/* @__PURE__ */ new Set(["model-stale-evidence", "promote-ready"])
|
|
2416
|
+
);
|
|
2417
|
+
function deriveOwnership(code, selfDeclared) {
|
|
2418
|
+
if (selfDeclared) return selfDeclared;
|
|
2419
|
+
return PRODUCER_OWNED_RULE_CODES.has(code) ? "producer-owned" : "consumer-actionable";
|
|
2420
|
+
}
|
|
2421
|
+
function runAdvisor(ir, result, profile, policy, phase2) {
|
|
2422
|
+
const out = [];
|
|
2423
|
+
out.push(...detectCachingOff(ir, profile));
|
|
2424
|
+
out.push(...detectSingleChunkSystem(ir, profile));
|
|
2425
|
+
out.push(...detectToolBloat(ir, result));
|
|
2426
|
+
out.push(...detectHistoryUncached(ir, profile));
|
|
2427
|
+
out.push(...detectSingleModelArray(ir, policy));
|
|
2428
|
+
if (policy?.posture !== "locked") {
|
|
2429
|
+
out.push(...detectCostMismatchedArchetype(ir, profile, phase2));
|
|
2430
|
+
out.push(...detectModelStaleEvidence(ir, profile));
|
|
2431
|
+
out.push(...detectTierDown(ir, profile, phase2));
|
|
2432
|
+
}
|
|
2433
|
+
if (!translatorClearedToolCallCliff(phase2)) {
|
|
2434
|
+
out.push(...detectArchetypePerfFloorBreach(ir, profile));
|
|
2435
|
+
}
|
|
2436
|
+
if (policy?.posture !== "locked") {
|
|
2437
|
+
out.push(...detectStaleExclusionCandidate(ir));
|
|
2438
|
+
}
|
|
2439
|
+
if (policy?.posture !== "locked" && ir.appId) {
|
|
2440
|
+
out.push(
|
|
2441
|
+
...advisorRulePromoteReady({
|
|
2442
|
+
appId: ir.appId,
|
|
2443
|
+
archetype: ir.intent.archetype,
|
|
2444
|
+
resolvedPrimary: profile.id
|
|
2445
|
+
})
|
|
2446
|
+
);
|
|
2447
|
+
out.push(...advisorRuleConsumerOnStaleModel(ir));
|
|
2448
|
+
}
|
|
2449
|
+
if (policy?.posture !== "locked") {
|
|
2450
|
+
out.push(
|
|
2451
|
+
...advisorRuleCrossFamilyFit({
|
|
2452
|
+
archetype: ir.intent.archetype,
|
|
2453
|
+
resolvedPrimary: profile.id
|
|
2454
|
+
})
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
return out;
|
|
2458
|
+
}
|
|
2459
|
+
function translatorClearedToolCallCliff(phase2) {
|
|
2460
|
+
const rewrites = phase2?.sectionRewritesApplied;
|
|
2461
|
+
if (!rewrites || rewrites.length === 0) return false;
|
|
2462
|
+
for (const rw of rewrites) {
|
|
2463
|
+
if (rw.kind === "tool_call_contract") return true;
|
|
2464
|
+
}
|
|
2465
|
+
return false;
|
|
2466
|
+
}
|
|
2467
|
+
function detectCachingOff(ir, profile) {
|
|
2468
|
+
if (profile.provider !== "anthropic") return [];
|
|
2469
|
+
const totalChars = ir.sections.reduce((s, sec) => s + sec.text.length, 0);
|
|
2470
|
+
if (totalChars < 2e3) return [];
|
|
2471
|
+
const anyCacheable = ir.sections.some((s) => s.cacheable === true);
|
|
2472
|
+
if (anyCacheable) return [];
|
|
2305
2473
|
return [
|
|
2306
2474
|
{
|
|
2307
|
-
level: "
|
|
2308
|
-
code: "
|
|
2309
|
-
message:
|
|
2310
|
-
suggestion:
|
|
2311
|
-
recommendationType: "tier-down",
|
|
2475
|
+
level: "warn",
|
|
2476
|
+
code: "caching-off-on-claude",
|
|
2477
|
+
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.`,
|
|
2478
|
+
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.",
|
|
2312
2479
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2313
2480
|
}
|
|
2314
2481
|
];
|
|
2315
2482
|
}
|
|
2316
|
-
function
|
|
2317
|
-
if (
|
|
2318
|
-
if (
|
|
2319
|
-
|
|
2483
|
+
function detectSingleChunkSystem(ir, profile) {
|
|
2484
|
+
if (profile.provider !== "anthropic") return [];
|
|
2485
|
+
if (ir.sections.length !== 1) return [];
|
|
2486
|
+
const only = ir.sections[0];
|
|
2487
|
+
if (!only || only.text.length <= 1e3) return [];
|
|
2488
|
+
return [
|
|
2489
|
+
{
|
|
2490
|
+
level: "info",
|
|
2491
|
+
code: "single-chunk-system",
|
|
2492
|
+
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.`,
|
|
2493
|
+
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. NOTE: the lowering pass HOISTS cacheable sections ahead of dynamic ones on the Anthropic wire (prefix caching requires it) \u2014 if your prompt has a protected ordering (e.g. a voice/persona block that must precede boilerplate), splitting will reorder the compiled output; declining this advisory is then correct. Also: a cacheable block under ~1024 tokens gets NO cache_control marker (provider minimum), so marking small sections is inert, not harmful.",
|
|
2494
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2495
|
+
}
|
|
2496
|
+
];
|
|
2320
2497
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2341
|
-
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
2342
|
-
4. Label each claim: mark it observed, inferred, or assumed.
|
|
2343
|
-
5. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
2344
|
-
var RULE_DISCIPLINE_GATES_V1_STRUCTURED = "discipline-gates-v1-structured";
|
|
2345
|
-
var DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2346
|
-
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2347
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2348
|
-
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
2349
|
-
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
2350
|
-
var DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2351
|
-
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2352
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2353
|
-
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
2354
|
-
var DISCIPLINE_ELIGIBLE_ARCHETYPES = /* @__PURE__ */ new Set([
|
|
2355
|
-
"hunt",
|
|
2356
|
-
"summarize",
|
|
2357
|
-
"plan",
|
|
2358
|
-
"critique",
|
|
2359
|
-
"judge"
|
|
2360
|
-
]);
|
|
2361
|
-
function matchRule(kind, profile, archetype, ctx) {
|
|
2362
|
-
if (kind === "discipline_contract") {
|
|
2363
|
-
if (!DISCIPLINE_ELIGIBLE_ARCHETYPES.has(archetype)) return null;
|
|
2364
|
-
if (ctx.outputMode !== "text") {
|
|
2365
|
-
return {
|
|
2366
|
-
id: RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
2367
|
-
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS : DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS
|
|
2368
|
-
};
|
|
2498
|
+
function detectToolBloat(ir, result) {
|
|
2499
|
+
const SHORT_OUTPUT = /* @__PURE__ */ new Set([
|
|
2500
|
+
"classify",
|
|
2501
|
+
"extract",
|
|
2502
|
+
"summarize",
|
|
2503
|
+
"transform",
|
|
2504
|
+
"critique"
|
|
2505
|
+
]);
|
|
2506
|
+
if (!ir.tools || ir.tools.length === 0) return [];
|
|
2507
|
+
const toolsKept = result.diagnostics.toolsKept;
|
|
2508
|
+
if (toolsKept <= 10) return [];
|
|
2509
|
+
if (!SHORT_OUTPUT.has(ir.intent.archetype)) return [];
|
|
2510
|
+
return [
|
|
2511
|
+
{
|
|
2512
|
+
level: "warn",
|
|
2513
|
+
code: "tool-bloat",
|
|
2514
|
+
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.`,
|
|
2515
|
+
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.",
|
|
2516
|
+
docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
|
|
2369
2517
|
}
|
|
2370
|
-
|
|
2371
|
-
id: RULE_DISCIPLINE_GATES_V1,
|
|
2372
|
-
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_WITH_TOOLS : DISCIPLINE_GATES_V1_NO_TOOLS
|
|
2373
|
-
};
|
|
2374
|
-
}
|
|
2375
|
-
if (kind === "tool_call_contract") {
|
|
2376
|
-
if (!profile.archetypePerf) return null;
|
|
2377
|
-
const archetypeScore = profile.archetypePerf[archetype];
|
|
2378
|
-
if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
|
|
2379
|
-
return null;
|
|
2380
|
-
}
|
|
2381
|
-
return {
|
|
2382
|
-
id: RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
2383
|
-
preamble: SEQUENTIAL_TOOL_PREAMBLE,
|
|
2384
|
-
wireOverrides: { parallelToolCalls: false }
|
|
2385
|
-
};
|
|
2386
|
-
}
|
|
2387
|
-
if (kind === "narration_contract") {
|
|
2388
|
-
if (profile.provider === "anthropic") {
|
|
2389
|
-
return {
|
|
2390
|
-
id: RULE_NARRATION_DRIFT_ANTHROPIC,
|
|
2391
|
-
preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
|
|
2392
|
-
};
|
|
2393
|
-
}
|
|
2394
|
-
if (profile.provider === "deepseek") {
|
|
2395
|
-
return {
|
|
2396
|
-
id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
|
|
2397
|
-
preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
return null;
|
|
2401
|
-
}
|
|
2402
|
-
return null;
|
|
2403
|
-
}
|
|
2404
|
-
function applySectionRewrites(args) {
|
|
2405
|
-
const { ir, profile, archetype } = args;
|
|
2406
|
-
if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
|
|
2407
|
-
return { rewrittenIR: ir, rewrites: [] };
|
|
2408
|
-
}
|
|
2409
|
-
const outputMode = args.outputMode ?? resolveOutputMode({
|
|
2410
|
-
declared: ir.constraints?.outputMode,
|
|
2411
|
-
structuredOutput: ir.constraints?.structuredOutput,
|
|
2412
|
-
toolCount: ir.tools?.length ?? 0
|
|
2413
|
-
});
|
|
2414
|
-
const hasTools = (ir.tools?.length ?? 0) > 0;
|
|
2415
|
-
const ctx = { outputMode, hasTools };
|
|
2416
|
-
const rewrites = [];
|
|
2417
|
-
const newSections = ir.sections.map((section) => {
|
|
2418
|
-
if (!section.kind || section.kind === "arbitrary") return section;
|
|
2419
|
-
const rule = matchRule(section.kind, profile, archetype, ctx);
|
|
2420
|
-
if (!rule) return section;
|
|
2421
|
-
const originalText = section.text;
|
|
2422
|
-
const transformedText = `${rule.preamble}
|
|
2423
|
-
|
|
2424
|
-
${originalText}`;
|
|
2425
|
-
rewrites.push({
|
|
2426
|
-
sectionId: section.id,
|
|
2427
|
-
kind: section.kind,
|
|
2428
|
-
rule: rule.id,
|
|
2429
|
-
originalText,
|
|
2430
|
-
transformedText,
|
|
2431
|
-
...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
|
|
2432
|
-
});
|
|
2433
|
-
return { ...section, text: transformedText };
|
|
2434
|
-
});
|
|
2435
|
-
if (rewrites.length === 0) {
|
|
2436
|
-
return { rewrittenIR: ir, rewrites: [] };
|
|
2437
|
-
}
|
|
2438
|
-
const rewrittenIR = { ...ir, sections: newSections };
|
|
2439
|
-
return { rewrittenIR, rewrites };
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
// src/promotions-brain.ts
|
|
2443
|
-
function isRawPromotionRow(x) {
|
|
2444
|
-
if (!x || typeof x !== "object") return false;
|
|
2445
|
-
const r = x;
|
|
2446
|
-
return (typeof r.id === "number" || typeof r.id === "string") && typeof r.intent_archetype === "string" && typeof r.promoted_model === "string" && typeof r.incumbent_model === "string";
|
|
2447
|
-
}
|
|
2448
|
-
function coerceId(v) {
|
|
2449
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
2450
|
-
if (typeof v === "string") {
|
|
2451
|
-
const n = Number(v);
|
|
2452
|
-
return Number.isFinite(n) ? n : null;
|
|
2453
|
-
}
|
|
2454
|
-
return null;
|
|
2455
|
-
}
|
|
2456
|
-
function mapRowsToPromotions(rows) {
|
|
2457
|
-
const out = [];
|
|
2458
|
-
for (const row of rows) {
|
|
2459
|
-
if (!isRawPromotionRow(row)) continue;
|
|
2460
|
-
const id = coerceId(row.id);
|
|
2461
|
-
if (id === null) continue;
|
|
2462
|
-
const mode = row.mode === "strategy" ? "strategy" : row.mode === "downswap" || row.mode === void 0 ? "downswap" : null;
|
|
2463
|
-
if (mode === null) continue;
|
|
2464
|
-
out.push({
|
|
2465
|
-
id,
|
|
2466
|
-
archetype: row.intent_archetype,
|
|
2467
|
-
mode,
|
|
2468
|
-
strategy: typeof row.strategy === "string" ? row.strategy : null,
|
|
2469
|
-
promotedModel: row.promoted_model,
|
|
2470
|
-
incumbentModel: row.incumbent_model,
|
|
2471
|
-
evalRunId: coerceId(row.eval_run_id ?? null),
|
|
2472
|
-
suppressQualityGate: row.suppress_quality_gate === true,
|
|
2473
|
-
promotedAt: typeof row.promoted_at === "string" ? row.promoted_at : ""
|
|
2474
|
-
});
|
|
2475
|
-
}
|
|
2476
|
-
return out;
|
|
2477
|
-
}
|
|
2478
|
-
var snapshots4 = /* @__PURE__ */ new Map();
|
|
2479
|
-
var runtime4;
|
|
2480
|
-
var warnedOnce4 = false;
|
|
2481
|
-
var DEFAULT_PROMOTIONS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/promotions";
|
|
2482
|
-
function isAutoPromoteEnabledFromEnv(envSource) {
|
|
2483
|
-
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
2484
|
-
const raw = (env.KGAUTO_AUTO_PROMOTE ?? "").trim().toLowerCase();
|
|
2485
|
-
return raw === "1" || raw === "true";
|
|
2486
|
-
}
|
|
2487
|
-
function configurePromotionsBrain(rt) {
|
|
2488
|
-
runtime4 = rt;
|
|
2489
|
-
snapshots4.clear();
|
|
2490
|
-
warnedOnce4 = false;
|
|
2491
|
-
}
|
|
2492
|
-
function isPromotionsBrainActive() {
|
|
2493
|
-
return runtime4 !== void 0;
|
|
2518
|
+
];
|
|
2494
2519
|
}
|
|
2495
|
-
function
|
|
2496
|
-
|
|
2497
|
-
if (!
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
let snap = snapshots4.get(appId);
|
|
2501
|
-
if (!snap) {
|
|
2502
|
-
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2503
|
-
snapshots4.set(appId, snap);
|
|
2504
|
-
}
|
|
2505
|
-
const now = Date.now();
|
|
2506
|
-
const stale = snap.expiresAt <= now;
|
|
2507
|
-
if (stale && !snap.refreshing) {
|
|
2508
|
-
snap.refreshing = true;
|
|
2509
|
-
void asyncRefresh4(rt, appId);
|
|
2520
|
+
function detectHistoryUncached(ir, profile) {
|
|
2521
|
+
if (profile.provider !== "anthropic") return [];
|
|
2522
|
+
if (!ir.history || ir.history.length < 2) return [];
|
|
2523
|
+
if (ir.historyCachePolicy && ir.historyCachePolicy.strategy !== "none") {
|
|
2524
|
+
return [];
|
|
2510
2525
|
}
|
|
2511
|
-
return
|
|
2512
|
-
|
|
2513
|
-
|
|
2526
|
+
return [
|
|
2527
|
+
{
|
|
2528
|
+
level: "warn",
|
|
2529
|
+
code: "history-uncached-on-claude",
|
|
2530
|
+
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.`,
|
|
2531
|
+
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.",
|
|
2532
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2533
|
+
}
|
|
2534
|
+
];
|
|
2514
2535
|
}
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2536
|
+
function detectSingleModelArray(ir, policy) {
|
|
2537
|
+
if (ir.models.length !== 1) return [];
|
|
2538
|
+
if (policy?.posture === "locked") return [];
|
|
2539
|
+
const only = ir.models[0];
|
|
2540
|
+
return [
|
|
2541
|
+
{
|
|
2542
|
+
level: "warn",
|
|
2543
|
+
code: "single-model-array",
|
|
2544
|
+
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.`,
|
|
2545
|
+
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.",
|
|
2546
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
|
|
2524
2547
|
}
|
|
2525
|
-
|
|
2548
|
+
];
|
|
2526
2549
|
}
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2550
|
+
function detectCostMismatchedArchetype(ir, profile, phase2) {
|
|
2551
|
+
if (!phase2 || phase2.fallbackChain.length === 0) return [];
|
|
2552
|
+
if (!phase2.profileResolver) return [];
|
|
2553
|
+
const archetype = ir.intent.archetype;
|
|
2554
|
+
const chosenScore = getArchetypePerfScore(profile.id, archetype);
|
|
2555
|
+
const chosenHasRoomToGrow = chosenScore.grounding === "judgment" || chosenScore.score < COST_MISMATCHED_CHOSEN_SCORE_CEILING;
|
|
2556
|
+
if (!chosenHasRoomToGrow) return [];
|
|
2557
|
+
let bestAlt = null;
|
|
2558
|
+
for (const altId of phase2.fallbackChain) {
|
|
2559
|
+
const altProfile = phase2.profileResolver(altId);
|
|
2560
|
+
if (!altProfile) continue;
|
|
2561
|
+
if (altProfile.id === profile.id) continue;
|
|
2562
|
+
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
2563
|
+
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
2564
|
+
if (altScore.score < chosenScore.score) continue;
|
|
2565
|
+
if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
|
|
2566
|
+
if (getMeasuredFailureVerdict({
|
|
2567
|
+
appId: ir.appId,
|
|
2568
|
+
archetype,
|
|
2569
|
+
model: altProfile.id
|
|
2570
|
+
})?.gated === true) {
|
|
2571
|
+
continue;
|
|
2538
2572
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2573
|
+
if (ir.constraints?.structuredOutput && effectiveConventions(altProfile).some(
|
|
2574
|
+
(c) => c.archetype === archetype && c.structuredOutputHint === "avoid"
|
|
2575
|
+
)) {
|
|
2576
|
+
continue;
|
|
2577
|
+
}
|
|
2578
|
+
if (getRecentRollback({
|
|
2579
|
+
appId: ir.appId,
|
|
2580
|
+
archetype,
|
|
2581
|
+
model: altProfile.id
|
|
2582
|
+
}) !== void 0) {
|
|
2583
|
+
continue;
|
|
2584
|
+
}
|
|
2585
|
+
if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
|
|
2586
|
+
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
2552
2587
|
}
|
|
2553
2588
|
}
|
|
2589
|
+
if (!bestAlt) return [];
|
|
2590
|
+
const tierDownWouldFire = bestAlt.score.grounding === "measured" && bestAlt.profile.costInputPer1m <= profile.costInputPer1m * TIER_DOWN_COST_RATIO;
|
|
2591
|
+
if (tierDownWouldFire) return [];
|
|
2592
|
+
const chosenGrounding = chosenScore.grounding === "judgment" ? `archetypePerf.${archetype}=judgment` : `archetypePerf.${archetype}=${chosenScore.score}`;
|
|
2593
|
+
const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
|
|
2594
|
+
return [
|
|
2595
|
+
{
|
|
2596
|
+
level: "warn",
|
|
2597
|
+
code: "cost-mismatched-archetype",
|
|
2598
|
+
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.`,
|
|
2599
|
+
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.`,
|
|
2600
|
+
recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
|
|
2601
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2602
|
+
}
|
|
2603
|
+
];
|
|
2554
2604
|
}
|
|
2555
|
-
function
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
);
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
}
|
|
2571
|
-
|
|
2572
|
-
// src/measured-failure-brain.ts
|
|
2573
|
-
function coerceCount(v) {
|
|
2574
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
2575
|
-
if (typeof v === "string") {
|
|
2576
|
-
const n = Number(v);
|
|
2577
|
-
return Number.isFinite(n) ? n : null;
|
|
2578
|
-
}
|
|
2579
|
-
return null;
|
|
2580
|
-
}
|
|
2581
|
-
function isRawFailureRow(x) {
|
|
2582
|
-
if (!x || typeof x !== "object") return false;
|
|
2583
|
-
const r = x;
|
|
2584
|
-
return typeof r.intent_archetype === "string" && typeof r.model === "string" && (typeof r.n === "number" || typeof r.n === "string");
|
|
2605
|
+
function detectModelStaleEvidence(ir, profile) {
|
|
2606
|
+
if (!isBrainQueryActiveFor("kgauto_archetype_perf")) return [];
|
|
2607
|
+
const archetype = ir.intent.archetype;
|
|
2608
|
+
const chosen = getArchetypePerfScore(profile.id, archetype);
|
|
2609
|
+
if (chosen.grounding !== "judgment") return [];
|
|
2610
|
+
return [
|
|
2611
|
+
{
|
|
2612
|
+
level: "info",
|
|
2613
|
+
code: "model-stale-evidence",
|
|
2614
|
+
message: `Model-stale-evidence: target=${profile.id} archetype=${archetype} is judgment-grounded (n=${chosen.n}, cross-app 90d window) despite brain-query mode being active. Fewer than 10 outcomes back this (model, archetype) tuple across ALL consumers \u2014 routing decisions remain pre-measured for this slot.`,
|
|
2615
|
+
suggestion: "Verify that `record()` is being called on every call() outcome. Counts are cross-app (migration 050 view): once ANY consumers accumulate n>=10 rows on this tuple, the score promotes from judgment to measured automatically within the 5-min SWR window. (Before alpha.78 this promotion was advertised but had no implementing mechanism \u2014 n was never populated; if this advisory has been firing for weeks at n=0 despite real traffic, bump to >=alpha.78 and it will clear on its own.)",
|
|
2616
|
+
recommendationType: "prompt-fix",
|
|
2617
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2618
|
+
}
|
|
2619
|
+
];
|
|
2585
2620
|
}
|
|
2586
|
-
function
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2621
|
+
function detectTierDown(ir, profile, phase2) {
|
|
2622
|
+
if (!phase2 || phase2.fallbackChain.length === 0) return [];
|
|
2623
|
+
if (!phase2.profileResolver) return [];
|
|
2624
|
+
const archetype = ir.intent.archetype;
|
|
2625
|
+
const chosenScore = getArchetypePerfScore(profile.id, archetype);
|
|
2626
|
+
const chosenCost = profile.costInputPer1m;
|
|
2627
|
+
let bestAlt = null;
|
|
2628
|
+
for (const altId of phase2.fallbackChain) {
|
|
2629
|
+
const altProfile = phase2.profileResolver(altId);
|
|
2630
|
+
if (!altProfile) continue;
|
|
2631
|
+
if (altProfile.id === profile.id) continue;
|
|
2632
|
+
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
2633
|
+
if (altScore.grounding !== "measured") continue;
|
|
2634
|
+
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
2635
|
+
if (altScore.score < chosenScore.score) continue;
|
|
2636
|
+
if (altProfile.costInputPer1m > chosenCost * TIER_DOWN_COST_RATIO) continue;
|
|
2637
|
+
if (!bestAlt || altProfile.costInputPer1m < bestAlt.profile.costInputPer1m || altProfile.costInputPer1m === bestAlt.profile.costInputPer1m && altScore.score > bestAlt.score.score) {
|
|
2638
|
+
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
2639
|
+
}
|
|
2599
2640
|
}
|
|
2600
|
-
return
|
|
2601
|
-
}
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
* Gate when we are 95% confident the model fails MORE OFTEN THAN IT
|
|
2613
|
-
* SUCCEEDS on this surface. Deliberately unarguable rather than tuned —
|
|
2614
|
-
* a model that probably fails the majority of the time has no business
|
|
2615
|
-
* leading a surface, whatever its declared scores say.
|
|
2616
|
-
*/
|
|
2617
|
-
lowerBoundThreshold: 0.5,
|
|
2618
|
-
/** 95% one-sided-ish confidence (standard two-sided z at α=0.05). */
|
|
2619
|
-
z: 1.96,
|
|
2620
|
-
/** Must match the view's window. Documented here for the advisory text. */
|
|
2621
|
-
windowDays: 28
|
|
2622
|
-
};
|
|
2623
|
-
function wilsonLowerBound(failures, n, z = MEASURED_FAILURE_CFG.z) {
|
|
2624
|
-
if (n <= 0) return 0;
|
|
2625
|
-
const p = failures / n;
|
|
2626
|
-
const z2 = z * z;
|
|
2627
|
-
const denom = 1 + z2 / n;
|
|
2628
|
-
const centre = p + z2 / (2 * n);
|
|
2629
|
-
const margin = z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
|
|
2630
|
-
const lower2 = (centre - margin) / denom;
|
|
2631
|
-
return lower2 < 0 ? 0 : lower2;
|
|
2632
|
-
}
|
|
2633
|
-
function judgeMeasuredFailure(row, cfg = MEASURED_FAILURE_CFG) {
|
|
2634
|
-
if (!row || row.n < cfg.minSample) return void 0;
|
|
2635
|
-
const lowerBound = wilsonLowerBound(row.nFail, row.n, cfg.z);
|
|
2636
|
-
return {
|
|
2637
|
-
gated: lowerBound > cfg.lowerBoundThreshold,
|
|
2638
|
-
rate: row.nFail / row.n,
|
|
2639
|
-
lowerBound,
|
|
2640
|
-
n: row.n,
|
|
2641
|
-
nFail: row.nFail
|
|
2642
|
-
};
|
|
2643
|
-
}
|
|
2644
|
-
var snapshots5 = /* @__PURE__ */ new Map();
|
|
2645
|
-
var runtime5;
|
|
2646
|
-
var warnedOnce5 = false;
|
|
2647
|
-
var DEFAULT_MEASURED_FAILURE_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/measured-failure";
|
|
2648
|
-
function isMeasuredFailureGateEnabledFromEnv(envSource) {
|
|
2649
|
-
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
2650
|
-
const raw = (env.KGAUTO_MEASURED_FAILURE_GATE ?? "").trim().toLowerCase();
|
|
2651
|
-
return !(raw === "0" || raw === "false");
|
|
2652
|
-
}
|
|
2653
|
-
function configureMeasuredFailureBrain(rt) {
|
|
2654
|
-
runtime5 = rt;
|
|
2655
|
-
snapshots5.clear();
|
|
2656
|
-
warnedOnce5 = false;
|
|
2657
|
-
}
|
|
2658
|
-
function isMeasuredFailureBrainActive() {
|
|
2659
|
-
return runtime5 !== void 0;
|
|
2641
|
+
if (!bestAlt) return [];
|
|
2642
|
+
const chosenDesc = chosenScore.grounding === "measured" ? `archetypePerf.${archetype}=${chosenScore.score} (measured, n=${chosenScore.n})` : `archetypePerf.${archetype}=${chosenScore.score} (${chosenScore.grounding})`;
|
|
2643
|
+
return [
|
|
2644
|
+
{
|
|
2645
|
+
level: "warn",
|
|
2646
|
+
code: "tier-down",
|
|
2647
|
+
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.`,
|
|
2648
|
+
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.`,
|
|
2649
|
+
recommendationType: "tier-down",
|
|
2650
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2651
|
+
}
|
|
2652
|
+
];
|
|
2660
2653
|
}
|
|
2661
|
-
function
|
|
2662
|
-
const
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2654
|
+
function detectArchetypePerfFloorBreach(ir, profile) {
|
|
2655
|
+
const compat = getModelCompatibility(profile.id, {
|
|
2656
|
+
archetype: ir.intent.archetype,
|
|
2657
|
+
toolOrchestration: ir.constraints?.toolOrchestration
|
|
2658
|
+
});
|
|
2659
|
+
if (compat.status === "compatible") return [];
|
|
2660
|
+
if (compat.status === "requires-adapter") {
|
|
2661
|
+
return [
|
|
2662
|
+
{
|
|
2663
|
+
level: "warn",
|
|
2664
|
+
code: "archetype-perf-floor-breach",
|
|
2665
|
+
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}`,
|
|
2666
|
+
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.`,
|
|
2667
|
+
recommendationType: "prompt-fix",
|
|
2668
|
+
suggestedAdaptation: compat.adapter,
|
|
2669
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2670
|
+
}
|
|
2671
|
+
];
|
|
2668
2672
|
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2673
|
+
return [
|
|
2674
|
+
{
|
|
2675
|
+
level: "critical",
|
|
2676
|
+
code: "archetype-perf-floor-breach",
|
|
2677
|
+
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}`,
|
|
2678
|
+
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.`,
|
|
2679
|
+
recommendationType: "model-swap",
|
|
2680
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2681
|
+
}
|
|
2682
|
+
];
|
|
2676
2683
|
}
|
|
2677
|
-
|
|
2678
|
-
if (!
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
}
|
|
2684
|
+
function detectStaleExclusionCandidate(ir) {
|
|
2685
|
+
if (!isExclusionFindingsBrainActive()) return [];
|
|
2686
|
+
if (!ir.appId) return [];
|
|
2687
|
+
const findings = getStaleExclusionFindings({
|
|
2688
|
+
appId: ir.appId,
|
|
2689
|
+
archetype: ir.intent.archetype
|
|
2690
|
+
});
|
|
2691
|
+
if (findings.length === 0) return [];
|
|
2692
|
+
const ranked = [...findings].sort((a, b) => {
|
|
2693
|
+
const sa = a.estimatedSavingsUsd30d ?? -Infinity;
|
|
2694
|
+
const sb = b.estimatedSavingsUsd30d ?? -Infinity;
|
|
2695
|
+
if (sa !== sb) return sb - sa;
|
|
2696
|
+
return confidenceRank(b.confidence) - confidenceRank(a.confidence);
|
|
2697
|
+
});
|
|
2698
|
+
const top = ranked[0];
|
|
2699
|
+
const extraCount = findings.length - 1;
|
|
2700
|
+
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more excluded model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
2701
|
+
return [
|
|
2702
|
+
{
|
|
2703
|
+
level: "info",
|
|
2704
|
+
code: "stale-exclusion-candidate",
|
|
2705
|
+
message: `${top.message}${extraNote}`,
|
|
2706
|
+
suggestion: top.suggestion,
|
|
2707
|
+
recommendationType: "tier-down",
|
|
2708
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2709
|
+
}
|
|
2710
|
+
];
|
|
2694
2711
|
}
|
|
2695
|
-
function
|
|
2696
|
-
|
|
2697
|
-
if (
|
|
2698
|
-
|
|
2699
|
-
if (!appId || !archetype || !model) return void 0;
|
|
2700
|
-
let snap = snapshots5.get(appId);
|
|
2701
|
-
if (!snap) {
|
|
2702
|
-
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2703
|
-
snapshots5.set(appId, snap);
|
|
2704
|
-
}
|
|
2705
|
-
const now = Date.now();
|
|
2706
|
-
if (snap.expiresAt <= now && !snap.refreshing) {
|
|
2707
|
-
snap.refreshing = true;
|
|
2708
|
-
void asyncRefresh5(rt, appId);
|
|
2709
|
-
}
|
|
2710
|
-
const row = snap.data.find(
|
|
2711
|
-
(r) => r.archetype === archetype && r.model === model
|
|
2712
|
-
);
|
|
2713
|
-
return judgeMeasuredFailure(row);
|
|
2712
|
+
function confidenceRank(c) {
|
|
2713
|
+
if (c === "high") return 3;
|
|
2714
|
+
if (c === "medium") return 2;
|
|
2715
|
+
return 1;
|
|
2714
2716
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2717
|
+
|
|
2718
|
+
// src/translator.ts
|
|
2719
|
+
var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
|
|
2720
|
+
var RULE_SEQUENTIAL_TOOL_CLIFF = "sequential-tool-cliff-below-floor";
|
|
2721
|
+
var RULE_NARRATION_DRIFT_ANTHROPIC = "narration-drift-anthropic";
|
|
2722
|
+
var RULE_NARRATION_THINKING_LEAK_DEEPSEEK = "narration-thinking-leak-deepseek";
|
|
2723
|
+
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.";
|
|
2724
|
+
var NARRATION_DRIFT_ANTHROPIC_PREAMBLE = "Output ONLY the requested content. Do not narrate your thought process. Each line \u2264 12 words.";
|
|
2725
|
+
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.";
|
|
2726
|
+
var RULE_DISCIPLINE_GATES_V1 = "discipline-gates-v1";
|
|
2727
|
+
var DISCIPLINE_GATES_V1_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2728
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2729
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2730
|
+
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
2731
|
+
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
2732
|
+
5. Label each claim: mark it observed, inferred, or assumed.
|
|
2733
|
+
6. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
2734
|
+
var DISCIPLINE_GATES_V1_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2735
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2736
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2737
|
+
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
2738
|
+
4. Label each claim: mark it observed, inferred, or assumed.
|
|
2739
|
+
5. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
2740
|
+
var RULE_DISCIPLINE_GATES_V1_STRUCTURED = "discipline-gates-v1-structured";
|
|
2741
|
+
var DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2742
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2743
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2744
|
+
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
2745
|
+
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
2746
|
+
var DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2747
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2748
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2749
|
+
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
2750
|
+
var DISCIPLINE_ELIGIBLE_ARCHETYPES = /* @__PURE__ */ new Set([
|
|
2751
|
+
"hunt",
|
|
2752
|
+
"summarize",
|
|
2753
|
+
"plan",
|
|
2754
|
+
"critique",
|
|
2755
|
+
"judge"
|
|
2756
|
+
]);
|
|
2757
|
+
function matchRule(kind, profile, archetype, ctx) {
|
|
2758
|
+
if (kind === "discipline_contract") {
|
|
2759
|
+
if (!DISCIPLINE_ELIGIBLE_ARCHETYPES.has(archetype)) return null;
|
|
2760
|
+
if (ctx.outputMode !== "text") {
|
|
2761
|
+
return {
|
|
2762
|
+
id: RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
2763
|
+
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS : DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS
|
|
2764
|
+
};
|
|
2724
2765
|
}
|
|
2766
|
+
return {
|
|
2767
|
+
id: RULE_DISCIPLINE_GATES_V1,
|
|
2768
|
+
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_WITH_TOOLS : DISCIPLINE_GATES_V1_NO_TOOLS
|
|
2769
|
+
};
|
|
2725
2770
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2771
|
+
if (kind === "tool_call_contract") {
|
|
2772
|
+
if (!profile.archetypePerf) return null;
|
|
2773
|
+
const archetypeScore = profile.archetypePerf[archetype];
|
|
2774
|
+
if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
|
|
2775
|
+
return null;
|
|
2776
|
+
}
|
|
2777
|
+
return {
|
|
2778
|
+
id: RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
2779
|
+
preamble: SEQUENTIAL_TOOL_PREAMBLE,
|
|
2780
|
+
wireOverrides: { parallelToolCalls: false }
|
|
2781
|
+
};
|
|
2733
2782
|
}
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2783
|
+
if (kind === "narration_contract") {
|
|
2784
|
+
if (profile.provider === "anthropic") {
|
|
2785
|
+
return {
|
|
2786
|
+
id: RULE_NARRATION_DRIFT_ANTHROPIC,
|
|
2787
|
+
preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
|
|
2788
|
+
};
|
|
2738
2789
|
}
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
} catch (err) {
|
|
2745
|
-
if (runtime5 !== rt) return;
|
|
2746
|
-
snap.refreshing = false;
|
|
2747
|
-
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
2748
|
-
if (!warnedOnce5) {
|
|
2749
|
-
warnedOnce5 = true;
|
|
2750
|
-
(rt.onError ?? defaultOnError5)(err);
|
|
2790
|
+
if (profile.provider === "deepseek") {
|
|
2791
|
+
return {
|
|
2792
|
+
id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
|
|
2793
|
+
preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
|
|
2794
|
+
};
|
|
2751
2795
|
}
|
|
2796
|
+
return null;
|
|
2752
2797
|
}
|
|
2798
|
+
return null;
|
|
2753
2799
|
}
|
|
2754
|
-
function
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
}
|
|
2766
|
-
|
|
2767
|
-
const
|
|
2768
|
-
|
|
2800
|
+
function applySectionRewrites(args) {
|
|
2801
|
+
const { ir, profile, archetype } = args;
|
|
2802
|
+
if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
|
|
2803
|
+
return { rewrittenIR: ir, rewrites: [] };
|
|
2804
|
+
}
|
|
2805
|
+
const outputMode = args.outputMode ?? resolveOutputMode({
|
|
2806
|
+
declared: ir.constraints?.outputMode,
|
|
2807
|
+
structuredOutput: ir.constraints?.structuredOutput,
|
|
2808
|
+
toolCount: ir.tools?.length ?? 0
|
|
2809
|
+
});
|
|
2810
|
+
const hasTools = (ir.tools?.length ?? 0) > 0;
|
|
2811
|
+
const ctx = { outputMode, hasTools };
|
|
2812
|
+
const rewrites = [];
|
|
2813
|
+
const newSections = ir.sections.map((section) => {
|
|
2814
|
+
if (!section.kind || section.kind === "arbitrary") return section;
|
|
2815
|
+
const rule = matchRule(section.kind, profile, archetype, ctx);
|
|
2816
|
+
if (!rule) return section;
|
|
2817
|
+
const originalText = section.text;
|
|
2818
|
+
const transformedText = `${rule.preamble}
|
|
2819
|
+
|
|
2820
|
+
${originalText}`;
|
|
2821
|
+
rewrites.push({
|
|
2822
|
+
sectionId: section.id,
|
|
2823
|
+
kind: section.kind,
|
|
2824
|
+
rule: rule.id,
|
|
2825
|
+
originalText,
|
|
2826
|
+
transformedText,
|
|
2827
|
+
...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
|
|
2828
|
+
});
|
|
2829
|
+
return { ...section, text: transformedText };
|
|
2830
|
+
});
|
|
2831
|
+
if (rewrites.length === 0) {
|
|
2832
|
+
return { rewrittenIR: ir, rewrites: [] };
|
|
2833
|
+
}
|
|
2834
|
+
const rewrittenIR = { ...ir, sections: newSections };
|
|
2835
|
+
return { rewrittenIR, rewrites };
|
|
2769
2836
|
}
|
|
2770
2837
|
|
|
2771
2838
|
// src/compile.ts
|
|
@@ -3895,6 +3962,17 @@ function parseJsonLoose(raw) {
|
|
|
3895
3962
|
var FAILED = /* @__PURE__ */ Symbol("parse-failed");
|
|
3896
3963
|
|
|
3897
3964
|
// src/ir.ts
|
|
3965
|
+
function mutationId(m) {
|
|
3966
|
+
return typeof m === "string" ? m : m.id;
|
|
3967
|
+
}
|
|
3968
|
+
function hasMutation(list, idOrPrefix) {
|
|
3969
|
+
if (!Array.isArray(list)) return false;
|
|
3970
|
+
const prefix = idOrPrefix.endsWith("*") ? idOrPrefix.slice(0, -1) : void 0;
|
|
3971
|
+
return list.some((m) => {
|
|
3972
|
+
const id = mutationId(m);
|
|
3973
|
+
return prefix !== void 0 ? id.startsWith(prefix) : id === idOrPrefix;
|
|
3974
|
+
});
|
|
3975
|
+
}
|
|
3898
3976
|
var CallError = class extends Error {
|
|
3899
3977
|
attempts;
|
|
3900
3978
|
lastErrorCode;
|
|
@@ -3948,12 +4026,46 @@ async function captureGoldenIr(ctx) {
|
|
|
3948
4026
|
}
|
|
3949
4027
|
|
|
3950
4028
|
// src/streaming.ts
|
|
4029
|
+
function classifyThrownFetchError(err) {
|
|
4030
|
+
const name = err?.name;
|
|
4031
|
+
if (name === "TimeoutError") {
|
|
4032
|
+
return { errorType: "retryable", errorCode: "timeout", message: String(err) };
|
|
4033
|
+
}
|
|
4034
|
+
if (name === "AbortError") {
|
|
4035
|
+
return { errorType: "terminal", errorCode: "aborted", message: "aborted by caller signal" };
|
|
4036
|
+
}
|
|
4037
|
+
return { errorType: "retryable", errorCode: "network_error", message: String(err) };
|
|
4038
|
+
}
|
|
4039
|
+
function createStallGuard(external, stallTimeoutMs) {
|
|
4040
|
+
const ctl = new AbortController();
|
|
4041
|
+
if (external) {
|
|
4042
|
+
if (external.aborted) ctl.abort(external.reason);
|
|
4043
|
+
else external.addEventListener("abort", () => ctl.abort(external.reason), { once: true });
|
|
4044
|
+
}
|
|
4045
|
+
let timer;
|
|
4046
|
+
const arm = () => {
|
|
4047
|
+
if (!stallTimeoutMs || stallTimeoutMs <= 0) return;
|
|
4048
|
+
if (timer) clearTimeout(timer);
|
|
4049
|
+
timer = setTimeout(() => {
|
|
4050
|
+
ctl.abort(
|
|
4051
|
+
new DOMException(`stream stalled: no bytes for ${stallTimeoutMs}ms`, "TimeoutError")
|
|
4052
|
+
);
|
|
4053
|
+
}, stallTimeoutMs);
|
|
4054
|
+
};
|
|
4055
|
+
const clear = () => {
|
|
4056
|
+
if (timer) clearTimeout(timer);
|
|
4057
|
+
timer = void 0;
|
|
4058
|
+
};
|
|
4059
|
+
return { signal: ctl.signal, arm, clear };
|
|
4060
|
+
}
|
|
3951
4061
|
var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
|
|
3952
4062
|
async function streamAnthropic(request, apiKey, opts) {
|
|
3953
4063
|
const { provider: _provider, ...body } = request;
|
|
3954
4064
|
const fetchFn = opts.fetchImpl ?? fetch;
|
|
4065
|
+
const guard = createStallGuard(opts.signal, opts.stallTimeoutMs);
|
|
3955
4066
|
let res;
|
|
3956
4067
|
try {
|
|
4068
|
+
guard.arm();
|
|
3957
4069
|
res = await fetchFn(ANTHROPIC_URL, {
|
|
3958
4070
|
method: "POST",
|
|
3959
4071
|
headers: {
|
|
@@ -3961,12 +4073,16 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
3961
4073
|
"anthropic-version": "2023-06-01",
|
|
3962
4074
|
"content-type": "application/json"
|
|
3963
4075
|
},
|
|
3964
|
-
body: JSON.stringify({ ...body, stream: true })
|
|
4076
|
+
body: JSON.stringify({ ...body, stream: true }),
|
|
4077
|
+
signal: guard.signal
|
|
3965
4078
|
});
|
|
3966
4079
|
} catch (err) {
|
|
3967
|
-
|
|
4080
|
+
guard.clear();
|
|
4081
|
+
const c = classifyThrownFetchError(err);
|
|
4082
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
3968
4083
|
}
|
|
3969
4084
|
if (!res.ok) {
|
|
4085
|
+
guard.clear();
|
|
3970
4086
|
const errBody = await res.json().catch(() => ({}));
|
|
3971
4087
|
return classifyHttpError(res.status, errBody);
|
|
3972
4088
|
}
|
|
@@ -4029,9 +4145,13 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
4029
4145
|
if (typeof p.usage?.output_tokens === "number") outputTokens = p.usage.output_tokens;
|
|
4030
4146
|
return;
|
|
4031
4147
|
}
|
|
4032
|
-
});
|
|
4148
|
+
}, guard.arm, guard.signal);
|
|
4033
4149
|
} catch (err) {
|
|
4034
|
-
|
|
4150
|
+
const c = classifyThrownFetchError(err);
|
|
4151
|
+
const code = c.errorCode === "network_error" ? "stream_interrupted" : c.errorCode;
|
|
4152
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: code, message: c.message, raw: null };
|
|
4153
|
+
} finally {
|
|
4154
|
+
guard.clear();
|
|
4035
4155
|
}
|
|
4036
4156
|
const toolCalls = Array.from(toolBlocks.values()).map((b) => ({
|
|
4037
4157
|
id: b.id,
|
|
@@ -4058,6 +4178,7 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
4058
4178
|
async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
4059
4179
|
const { provider: _provider, ...body } = request;
|
|
4060
4180
|
const fetchFn = opts.fetchImpl ?? fetch;
|
|
4181
|
+
const guard = createStallGuard(opts.signal, opts.stallTimeoutMs);
|
|
4061
4182
|
const reqBody = {
|
|
4062
4183
|
...body,
|
|
4063
4184
|
stream: true,
|
|
@@ -4068,18 +4189,23 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
4068
4189
|
};
|
|
4069
4190
|
let res;
|
|
4070
4191
|
try {
|
|
4192
|
+
guard.arm();
|
|
4071
4193
|
res = await fetchFn(url, {
|
|
4072
4194
|
method: "POST",
|
|
4073
4195
|
headers: {
|
|
4074
4196
|
authorization: `Bearer ${apiKey}`,
|
|
4075
4197
|
"content-type": "application/json"
|
|
4076
4198
|
},
|
|
4077
|
-
body: JSON.stringify(reqBody)
|
|
4199
|
+
body: JSON.stringify(reqBody),
|
|
4200
|
+
signal: guard.signal
|
|
4078
4201
|
});
|
|
4079
4202
|
} catch (err) {
|
|
4080
|
-
|
|
4203
|
+
guard.clear();
|
|
4204
|
+
const c = classifyThrownFetchError(err);
|
|
4205
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4081
4206
|
}
|
|
4082
4207
|
if (!res.ok) {
|
|
4208
|
+
guard.clear();
|
|
4083
4209
|
const errBody = await res.json().catch(() => ({}));
|
|
4084
4210
|
return classifyHttpError(res.status, errBody);
|
|
4085
4211
|
}
|
|
@@ -4132,9 +4258,13 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
4132
4258
|
const details = usage.prompt_tokens_details;
|
|
4133
4259
|
if (typeof details?.cached_tokens === "number") cachedTokens = details.cached_tokens;
|
|
4134
4260
|
}
|
|
4135
|
-
});
|
|
4261
|
+
}, guard.arm, guard.signal);
|
|
4136
4262
|
} catch (err) {
|
|
4137
|
-
|
|
4263
|
+
const c = classifyThrownFetchError(err);
|
|
4264
|
+
const code = c.errorCode === "network_error" ? "stream_interrupted" : c.errorCode;
|
|
4265
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: code, message: c.message, raw: null };
|
|
4266
|
+
} finally {
|
|
4267
|
+
guard.clear();
|
|
4138
4268
|
}
|
|
4139
4269
|
const toolCalls = Array.from(toolBuffers.values()).filter((b) => b.name.length > 0).map((b) => ({
|
|
4140
4270
|
id: b.id,
|
|
@@ -4157,15 +4287,28 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
4157
4287
|
};
|
|
4158
4288
|
return { ok: true, status: res.status, response };
|
|
4159
4289
|
}
|
|
4160
|
-
async function parseSSEStream(response, handler) {
|
|
4290
|
+
async function parseSSEStream(response, handler, onRead, signal) {
|
|
4161
4291
|
const body = response.body;
|
|
4162
4292
|
if (!body) throw new Error("Response has no body for SSE parse");
|
|
4163
4293
|
const reader = body.getReader();
|
|
4164
4294
|
const decoder = new TextDecoder("utf-8");
|
|
4165
4295
|
let buffer = "";
|
|
4296
|
+
const abortRace = signal ? new Promise((_resolve, reject) => {
|
|
4297
|
+
if (signal.aborted) return reject(signal.reason);
|
|
4298
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
4299
|
+
}) : void 0;
|
|
4166
4300
|
for (; ; ) {
|
|
4167
|
-
|
|
4301
|
+
let readResult;
|
|
4302
|
+
try {
|
|
4303
|
+
readResult = abortRace ? await Promise.race([reader.read(), abortRace]) : await reader.read();
|
|
4304
|
+
} catch (err) {
|
|
4305
|
+
void reader.cancel().catch(() => {
|
|
4306
|
+
});
|
|
4307
|
+
throw err;
|
|
4308
|
+
}
|
|
4309
|
+
const { value, done } = readResult;
|
|
4168
4310
|
if (done) break;
|
|
4311
|
+
onRead?.();
|
|
4169
4312
|
buffer += decoder.decode(value, { stream: true });
|
|
4170
4313
|
let sep;
|
|
4171
4314
|
while (sep = buffer.indexOf("\n\n"), sep !== -1) {
|
|
@@ -4222,8 +4365,33 @@ function isAuthSignatureBody(body, message) {
|
|
|
4222
4365
|
const m = message.toLowerCase();
|
|
4223
4366
|
return m.includes("api key not valid") || m.includes("invalid api key") || m.includes("invalid x-api-key") || m.includes("incorrect api key");
|
|
4224
4367
|
}
|
|
4368
|
+
function isBillingExhaustedBody(status, body, message) {
|
|
4369
|
+
if (status === 402) return true;
|
|
4370
|
+
const m = message.toLowerCase();
|
|
4371
|
+
if (m.includes("free_tier") || m.includes("free tier")) return false;
|
|
4372
|
+
if (m.includes("credit balance is too low")) return true;
|
|
4373
|
+
if (m.includes("insufficient balance")) return true;
|
|
4374
|
+
if (m.includes("billingnotenabled") || m.includes("billing not enabled")) return true;
|
|
4375
|
+
if (body && typeof body === "object") {
|
|
4376
|
+
const err = body.error;
|
|
4377
|
+
if (err && typeof err === "object") {
|
|
4378
|
+
const e = err;
|
|
4379
|
+
if (e.code === "insufficient_quota" || e.type === "insufficient_quota") return true;
|
|
4380
|
+
if (Array.isArray(e.details)) {
|
|
4381
|
+
for (const d of e.details) {
|
|
4382
|
+
if (d && typeof d === "object" && d.reason === "BILLING_DISABLED") {
|
|
4383
|
+
return true;
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
return false;
|
|
4390
|
+
}
|
|
4225
4391
|
function classifyHttpError(status, body) {
|
|
4226
4392
|
const message = extractErrorMessage(body) ?? `HTTP ${status}`;
|
|
4393
|
+
if (isBillingExhaustedBody(status, body, message))
|
|
4394
|
+
return { ok: false, status, errorType: "terminal", errorCode: "billing_exhausted", message, raw: body };
|
|
4227
4395
|
if (status === 429)
|
|
4228
4396
|
return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
|
|
4229
4397
|
if (status === 408)
|
|
@@ -4251,9 +4419,6 @@ function extractErrorMessage(body) {
|
|
|
4251
4419
|
if (typeof b.message === "string") return b.message;
|
|
4252
4420
|
return void 0;
|
|
4253
4421
|
}
|
|
4254
|
-
function retryableError(status, code, message, raw) {
|
|
4255
|
-
return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
|
|
4256
|
-
}
|
|
4257
4422
|
|
|
4258
4423
|
// src/execute.ts
|
|
4259
4424
|
var ANTHROPIC_URL2 = "https://api.anthropic.com/v1/messages";
|
|
@@ -4298,7 +4463,9 @@ async function executeAnthropic(request, opts) {
|
|
|
4298
4463
|
if (opts.onChunk) {
|
|
4299
4464
|
return streamAnthropic(request, apiKey, {
|
|
4300
4465
|
onChunk: opts.onChunk,
|
|
4301
|
-
fetchImpl: opts.fetchImpl
|
|
4466
|
+
fetchImpl: opts.fetchImpl,
|
|
4467
|
+
signal: opts.signal,
|
|
4468
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
4302
4469
|
});
|
|
4303
4470
|
}
|
|
4304
4471
|
const { provider: _provider, ...body } = request;
|
|
@@ -4313,11 +4480,13 @@ async function executeAnthropic(request, opts) {
|
|
|
4313
4480
|
"anthropic-version": "2023-06-01",
|
|
4314
4481
|
"content-type": "application/json"
|
|
4315
4482
|
},
|
|
4316
|
-
body: JSON.stringify(body)
|
|
4483
|
+
body: JSON.stringify(body),
|
|
4484
|
+
signal: opts.signal
|
|
4317
4485
|
});
|
|
4318
4486
|
json = await res.json().catch(() => ({}));
|
|
4319
4487
|
} catch (err) {
|
|
4320
|
-
|
|
4488
|
+
const c = classifyThrownFetchError(err);
|
|
4489
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4321
4490
|
}
|
|
4322
4491
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
4323
4492
|
return { ok: true, status: res.status, response: normalizeAnthropic(json) };
|
|
@@ -4349,11 +4518,13 @@ async function executeGoogle(request, opts) {
|
|
|
4349
4518
|
res = await fetchFn(url, {
|
|
4350
4519
|
method: "POST",
|
|
4351
4520
|
headers: { "content-type": "application/json" },
|
|
4352
|
-
body: JSON.stringify(body)
|
|
4521
|
+
body: JSON.stringify(body),
|
|
4522
|
+
signal: opts.signal
|
|
4353
4523
|
});
|
|
4354
4524
|
json = await res.json().catch(() => ({}));
|
|
4355
4525
|
} catch (err) {
|
|
4356
|
-
|
|
4526
|
+
const c = classifyThrownFetchError(err);
|
|
4527
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4357
4528
|
}
|
|
4358
4529
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
4359
4530
|
return { ok: true, status: res.status, response: normalizeGoogle(json) };
|
|
@@ -4385,7 +4556,9 @@ async function executeOpenAI(request, opts) {
|
|
|
4385
4556
|
if (opts.onChunk) {
|
|
4386
4557
|
return streamOpenAILike(OPENAI_URL, request, apiKey, "openai", {
|
|
4387
4558
|
onChunk: opts.onChunk,
|
|
4388
|
-
fetchImpl: opts.fetchImpl
|
|
4559
|
+
fetchImpl: opts.fetchImpl,
|
|
4560
|
+
signal: opts.signal,
|
|
4561
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
4389
4562
|
});
|
|
4390
4563
|
}
|
|
4391
4564
|
const { provider: _provider, ...body } = request;
|
|
@@ -4396,11 +4569,13 @@ async function executeOpenAI(request, opts) {
|
|
|
4396
4569
|
res = await fetchFn(OPENAI_URL, {
|
|
4397
4570
|
method: "POST",
|
|
4398
4571
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
4399
|
-
body: JSON.stringify(body)
|
|
4572
|
+
body: JSON.stringify(body),
|
|
4573
|
+
signal: opts.signal
|
|
4400
4574
|
});
|
|
4401
4575
|
json = await res.json().catch(() => ({}));
|
|
4402
4576
|
} catch (err) {
|
|
4403
|
-
|
|
4577
|
+
const c = classifyThrownFetchError(err);
|
|
4578
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4404
4579
|
}
|
|
4405
4580
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
4406
4581
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -4413,7 +4588,9 @@ async function executeDeepSeek(request, opts) {
|
|
|
4413
4588
|
if (opts.onChunk) {
|
|
4414
4589
|
return streamOpenAILike(DEEPSEEK_URL, request, apiKey, "deepseek", {
|
|
4415
4590
|
onChunk: opts.onChunk,
|
|
4416
|
-
fetchImpl: opts.fetchImpl
|
|
4591
|
+
fetchImpl: opts.fetchImpl,
|
|
4592
|
+
signal: opts.signal,
|
|
4593
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
4417
4594
|
});
|
|
4418
4595
|
}
|
|
4419
4596
|
const { provider: _provider, ...body } = request;
|
|
@@ -4424,11 +4601,13 @@ async function executeDeepSeek(request, opts) {
|
|
|
4424
4601
|
res = await fetchFn(DEEPSEEK_URL, {
|
|
4425
4602
|
method: "POST",
|
|
4426
4603
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
4427
|
-
body: JSON.stringify(body)
|
|
4604
|
+
body: JSON.stringify(body),
|
|
4605
|
+
signal: opts.signal
|
|
4428
4606
|
});
|
|
4429
4607
|
json = await res.json().catch(() => ({}));
|
|
4430
4608
|
} catch (err) {
|
|
4431
|
-
|
|
4609
|
+
const c = classifyThrownFetchError(err);
|
|
4610
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4432
4611
|
}
|
|
4433
4612
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
4434
4613
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -4441,7 +4620,9 @@ async function executeOpenAICompatible(request, opts, spec) {
|
|
|
4441
4620
|
if (opts.onChunk) {
|
|
4442
4621
|
return streamOpenAILike(spec.url, request, apiKey, spec.provider, {
|
|
4443
4622
|
onChunk: opts.onChunk,
|
|
4444
|
-
fetchImpl: opts.fetchImpl
|
|
4623
|
+
fetchImpl: opts.fetchImpl,
|
|
4624
|
+
signal: opts.signal,
|
|
4625
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
4445
4626
|
});
|
|
4446
4627
|
}
|
|
4447
4628
|
const { provider: _provider, ...body } = request;
|
|
@@ -4452,11 +4633,13 @@ async function executeOpenAICompatible(request, opts, spec) {
|
|
|
4452
4633
|
res = await fetchFn(spec.url, {
|
|
4453
4634
|
method: "POST",
|
|
4454
4635
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
4455
|
-
body: JSON.stringify(body)
|
|
4636
|
+
body: JSON.stringify(body),
|
|
4637
|
+
signal: opts.signal
|
|
4456
4638
|
});
|
|
4457
4639
|
json = await res.json().catch(() => ({}));
|
|
4458
4640
|
} catch (err) {
|
|
4459
|
-
|
|
4641
|
+
const c = classifyThrownFetchError(err);
|
|
4642
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4460
4643
|
}
|
|
4461
4644
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
4462
4645
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -4487,6 +4670,9 @@ function applyOverrides(request, overrides) {
|
|
|
4487
4670
|
}
|
|
4488
4671
|
function classifyHttpError2(status, body) {
|
|
4489
4672
|
const message = extractErrorMessage2(body) ?? `HTTP ${status}`;
|
|
4673
|
+
if (isBillingExhaustedBody(status, body, message)) {
|
|
4674
|
+
return { ok: false, status, errorType: "terminal", errorCode: "billing_exhausted", message, raw: body };
|
|
4675
|
+
}
|
|
4490
4676
|
if (status === 429) {
|
|
4491
4677
|
return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
|
|
4492
4678
|
}
|
|
@@ -4523,9 +4709,6 @@ function extractErrorMessage2(body) {
|
|
|
4523
4709
|
function terminalError(status, code, message) {
|
|
4524
4710
|
return { ok: false, status, errorType: "terminal", errorCode: code, message, raw: null };
|
|
4525
4711
|
}
|
|
4526
|
-
function retryableError2(status, code, message, raw) {
|
|
4527
|
-
return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
|
|
4528
|
-
}
|
|
4529
4712
|
function tryParseJson2(s) {
|
|
4530
4713
|
if (typeof s !== "string" || s.length === 0) return void 0;
|
|
4531
4714
|
try {
|
|
@@ -4675,18 +4858,28 @@ async function call(ir, opts = {}) {
|
|
|
4675
4858
|
}
|
|
4676
4859
|
let activeCompile = initial;
|
|
4677
4860
|
let lastErr;
|
|
4678
|
-
const failedProviders = /* @__PURE__ */ new
|
|
4861
|
+
const failedProviders = /* @__PURE__ */ new Map();
|
|
4679
4862
|
const sameModelRetryEnabled = opts.sameModelRetry ?? isSameModelRetryEnabledFromEnv();
|
|
4680
4863
|
let retriedSameModel = false;
|
|
4681
4864
|
for (let i = 0; i < targetsToTry.length; i++) {
|
|
4682
4865
|
const targetModel = targetsToTry[i];
|
|
4866
|
+
if (opts.abortSignal?.aborted) {
|
|
4867
|
+
attempts.push({
|
|
4868
|
+
model: targetModel,
|
|
4869
|
+
status: "terminal",
|
|
4870
|
+
errorCode: "aborted",
|
|
4871
|
+
message: "Skipped \u2014 caller abortSignal fired before this attempt started"
|
|
4872
|
+
});
|
|
4873
|
+
break;
|
|
4874
|
+
}
|
|
4683
4875
|
const targetProfile = tryGetProfile(targetModel);
|
|
4684
|
-
|
|
4876
|
+
const providerFailReason = targetProfile ? failedProviders.get(targetProfile.provider) : void 0;
|
|
4877
|
+
if (targetProfile && providerFailReason && !opts.noFallback) {
|
|
4685
4878
|
attempts.push({
|
|
4686
4879
|
model: targetModel,
|
|
4687
4880
|
status: "terminal",
|
|
4688
|
-
errorCode:
|
|
4689
|
-
message: `Skipped \u2014 provider ${targetProfile.provider} returned 401/403 earlier in this call; same key inferred to fail`
|
|
4881
|
+
errorCode: `${providerFailReason}_inferred`,
|
|
4882
|
+
message: providerFailReason === "billing_exhausted" ? `Skipped \u2014 provider ${targetProfile.provider} is out of credits (seen earlier in this call); a billing failure is account-wide, so this attempt cannot succeed until a human funds the account` : `Skipped \u2014 provider ${targetProfile.provider} returned 401/403 earlier in this call; same key inferred to fail`
|
|
4690
4883
|
});
|
|
4691
4884
|
continue;
|
|
4692
4885
|
}
|
|
@@ -4715,16 +4908,29 @@ async function call(ir, opts = {}) {
|
|
|
4715
4908
|
);
|
|
4716
4909
|
const targetSupportsStreaming = targetProfile?.streaming === true;
|
|
4717
4910
|
const streamingOnChunk = opts.onChunk && !opts.noStream && targetSupportsStreaming ? opts.onChunk : void 0;
|
|
4718
|
-
const
|
|
4911
|
+
const mkExecOpts = () => ({
|
|
4719
4912
|
apiKeys: opts.apiKeys,
|
|
4720
4913
|
fetchImpl: opts.fetchImpl,
|
|
4721
4914
|
providerOverrides: opts.providerOverrides,
|
|
4722
|
-
onChunk: streamingOnChunk
|
|
4723
|
-
|
|
4724
|
-
|
|
4915
|
+
onChunk: streamingOnChunk,
|
|
4916
|
+
signal: composeAttemptSignal(
|
|
4917
|
+
streamingOnChunk ? void 0 : opts.attemptTimeoutMs,
|
|
4918
|
+
opts.abortSignal
|
|
4919
|
+
),
|
|
4920
|
+
stallTimeoutMs: streamingOnChunk ? opts.attemptTimeoutMs : void 0
|
|
4921
|
+
});
|
|
4922
|
+
const exec = await execute(activeCompile.request, mkExecOpts());
|
|
4725
4923
|
let validated = exec.ok ? validateStructuredContract(exec, ir) : exec;
|
|
4726
4924
|
let servedByRetry = false;
|
|
4727
|
-
|
|
4925
|
+
let retrySuppressionNote;
|
|
4926
|
+
if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && getMeasuredFailureVerdict({
|
|
4927
|
+
appId: ir.appId,
|
|
4928
|
+
archetype: ir.intent.archetype,
|
|
4929
|
+
model: targetModel
|
|
4930
|
+
})?.gated === true) {
|
|
4931
|
+
retrySuppressionNote = " [sameModelRetry suppressed: this model carries an active measured-failure gate for this archetype \u2014 retrying it would near-certainly bill a second doomed inference; walking the chain instead]";
|
|
4932
|
+
}
|
|
4933
|
+
if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && !retrySuppressionNote) {
|
|
4728
4934
|
retriedSameModel = true;
|
|
4729
4935
|
attempts.push({
|
|
4730
4936
|
model: targetModel,
|
|
@@ -4736,7 +4942,7 @@ async function call(ir, opts = {}) {
|
|
|
4736
4942
|
safeEmit(
|
|
4737
4943
|
() => emitExecuteAttempt(traceId, ir.appId, { model: targetModel, attemptIndex: i })
|
|
4738
4944
|
);
|
|
4739
|
-
const retryExec = await execute(retryRequest,
|
|
4945
|
+
const retryExec = await execute(retryRequest, mkExecOpts());
|
|
4740
4946
|
validated = retryExec.ok ? validateStructuredContract(retryExec, ir) : retryExec;
|
|
4741
4947
|
servedByRetry = true;
|
|
4742
4948
|
}
|
|
@@ -4863,20 +5069,23 @@ async function call(ir, opts = {}) {
|
|
|
4863
5069
|
model: targetModel,
|
|
4864
5070
|
status: validated.errorType,
|
|
4865
5071
|
errorCode: validated.errorCode,
|
|
4866
|
-
message: validated.message,
|
|
5072
|
+
message: retrySuppressionNote ? validated.message + retrySuppressionNote : validated.message,
|
|
4867
5073
|
...servedByRetry ? { sameModelRetry: true } : {}
|
|
4868
5074
|
});
|
|
4869
5075
|
lastErr = validated;
|
|
4870
5076
|
if (validated.errorType === "terminal" || opts.noFallback) {
|
|
4871
|
-
if (validated.errorCode === "auth" && !opts.noFallback && activeCompile.provider) {
|
|
4872
|
-
failedProviders.
|
|
5077
|
+
if ((validated.errorCode === "auth" || validated.errorCode === "billing_exhausted") && !opts.noFallback && activeCompile.provider) {
|
|
5078
|
+
failedProviders.set(
|
|
5079
|
+
activeCompile.provider,
|
|
5080
|
+
validated.errorCode === "billing_exhausted" ? "billing_exhausted" : "auth"
|
|
5081
|
+
);
|
|
4873
5082
|
continue;
|
|
4874
5083
|
}
|
|
4875
5084
|
break;
|
|
4876
5085
|
}
|
|
4877
5086
|
}
|
|
4878
5087
|
const lastAttempted = [...attempts].reverse().find(
|
|
4879
|
-
(a) => a.status !== "success" && a.errorCode !== "auth_inferred" && a.errorCode !== "compile_error"
|
|
5088
|
+
(a) => a.status !== "success" && a.errorCode !== "auth_inferred" && a.errorCode !== "billing_exhausted_inferred" && a.errorCode !== "compile_error"
|
|
4880
5089
|
);
|
|
4881
5090
|
const latencyMs = Date.now() - start;
|
|
4882
5091
|
await record({
|
|
@@ -4899,6 +5108,25 @@ async function call(ir, opts = {}) {
|
|
|
4899
5108
|
lastErr?.errorCode
|
|
4900
5109
|
);
|
|
4901
5110
|
}
|
|
5111
|
+
function composeAttemptSignal(attemptTimeoutMs, callerSignal) {
|
|
5112
|
+
const signals = [];
|
|
5113
|
+
if (callerSignal) signals.push(callerSignal);
|
|
5114
|
+
if (typeof attemptTimeoutMs === "number" && attemptTimeoutMs > 0) {
|
|
5115
|
+
signals.push(AbortSignal.timeout(attemptTimeoutMs));
|
|
5116
|
+
}
|
|
5117
|
+
if (signals.length === 0) return void 0;
|
|
5118
|
+
if (signals.length === 1) return signals[0];
|
|
5119
|
+
if (typeof AbortSignal.any === "function") return AbortSignal.any(signals);
|
|
5120
|
+
const ctl = new AbortController();
|
|
5121
|
+
for (const s of signals) {
|
|
5122
|
+
if (s.aborted) {
|
|
5123
|
+
ctl.abort(s.reason);
|
|
5124
|
+
break;
|
|
5125
|
+
}
|
|
5126
|
+
s.addEventListener("abort", () => ctl.abort(s.reason), { once: true });
|
|
5127
|
+
}
|
|
5128
|
+
return ctl.signal;
|
|
5129
|
+
}
|
|
4902
5130
|
function compileAndRegister(ir, opts) {
|
|
4903
5131
|
const result = compile(ir, {
|
|
4904
5132
|
policy: opts.policy,
|
|
@@ -5198,6 +5426,9 @@ function normalizeFallbackReason(attempts) {
|
|
|
5198
5426
|
}
|
|
5199
5427
|
if (code === "cost_cap_exceeded") return "cost_cap";
|
|
5200
5428
|
if (code === "auth" || code === "auth_inferred") return "provider_auth_failed";
|
|
5429
|
+
if (code === "billing_exhausted" || code === "billing_exhausted_inferred") {
|
|
5430
|
+
return "provider_billing_exhausted";
|
|
5431
|
+
}
|
|
5201
5432
|
return "provider_error";
|
|
5202
5433
|
}
|
|
5203
5434
|
function generateTraceId() {
|
|
@@ -5950,6 +6181,12 @@ async function runGoldenEval(opts) {
|
|
|
5950
6181
|
);
|
|
5951
6182
|
}
|
|
5952
6183
|
}
|
|
6184
|
+
if (wins + ties + losses === 0) {
|
|
6185
|
+
notes.push(
|
|
6186
|
+
"zero cases judged \u2014 run row persisted, evidence advisory deliberately NOT written (a verdict on n=0 is not evidence)"
|
|
6187
|
+
);
|
|
6188
|
+
return result;
|
|
6189
|
+
}
|
|
5953
6190
|
const latestRes = await fetchFn(
|
|
5954
6191
|
rest(
|
|
5955
6192
|
`compile_outcomes?app_id=eq.${encodeURIComponent(opts.appId)}&select=id&order=id.desc&limit=1`
|
|
@@ -6177,19 +6414,32 @@ function resolveFetch(injected) {
|
|
|
6177
6414
|
function normalizeEndpoint2(endpoint) {
|
|
6178
6415
|
return endpoint.replace(/\/+$/, "");
|
|
6179
6416
|
}
|
|
6417
|
+
function resolveBrainReadTrio(opts) {
|
|
6418
|
+
const env = readBrainReadEnv();
|
|
6419
|
+
const brainEndpoint = opts.brainEndpoint ?? env.endpoint;
|
|
6420
|
+
const brainJwt = opts.brainJwt ?? env.jwt;
|
|
6421
|
+
const brainAnonKey = opts.brainAnonKey ?? env.anonKey;
|
|
6422
|
+
if (brainEndpoint && brainJwt && brainAnonKey) {
|
|
6423
|
+
return { ok: true, brainEndpoint, brainJwt, brainAnonKey };
|
|
6424
|
+
}
|
|
6425
|
+
const missingEnv = [];
|
|
6426
|
+
if (!brainEndpoint) missingEnv.push("KGAUTO_V2_BRAIN_SUPABASE_URL");
|
|
6427
|
+
if (!brainJwt) missingEnv.push("KGAUTO_V2_BRAIN_JWT");
|
|
6428
|
+
if (!brainAnonKey) missingEnv.push("KGAUTO_V2_BRAIN_ANON_KEY");
|
|
6429
|
+
return { ok: false, missingEnv };
|
|
6430
|
+
}
|
|
6180
6431
|
async function getActionableAdvisories(opts) {
|
|
6181
|
-
const {
|
|
6182
|
-
appId,
|
|
6183
|
-
severity,
|
|
6184
|
-
status,
|
|
6185
|
-
brainEndpoint,
|
|
6186
|
-
brainJwt,
|
|
6187
|
-
brainAnonKey,
|
|
6188
|
-
fetch: injectedFetch
|
|
6189
|
-
} = opts;
|
|
6432
|
+
const { appId, severity, status, fetch: injectedFetch } = opts;
|
|
6190
6433
|
if (!appId) {
|
|
6191
6434
|
throw new Error("getActionableAdvisories: appId is required");
|
|
6192
6435
|
}
|
|
6436
|
+
const trio = resolveBrainReadTrio(opts);
|
|
6437
|
+
if (!trio.ok) {
|
|
6438
|
+
throw new Error(
|
|
6439
|
+
`getActionableAdvisories: brain-read-not-configured \u2014 missing ${trio.missingEnv.join(", ")} (pass brainEndpoint/brainJwt/brainAnonKey explicitly or set the canonical env vars)`
|
|
6440
|
+
);
|
|
6441
|
+
}
|
|
6442
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
6193
6443
|
const doFetch = resolveFetch(injectedFetch);
|
|
6194
6444
|
const base = normalizeEndpoint2(brainEndpoint);
|
|
6195
6445
|
const qs = new URLSearchParams();
|
|
@@ -6242,17 +6492,15 @@ async function getActionableAdvisories(opts) {
|
|
|
6242
6492
|
return out;
|
|
6243
6493
|
}
|
|
6244
6494
|
async function markAdvisoryResolved(opts) {
|
|
6245
|
-
const {
|
|
6246
|
-
id,
|
|
6247
|
-
resolutionNote,
|
|
6248
|
-
brainEndpoint,
|
|
6249
|
-
brainJwt,
|
|
6250
|
-
brainAnonKey,
|
|
6251
|
-
fetch: injectedFetch
|
|
6252
|
-
} = opts;
|
|
6495
|
+
const { id, resolutionNote, fetch: injectedFetch } = opts;
|
|
6253
6496
|
if (!id) {
|
|
6254
6497
|
return { ok: false, reason: "id_required" };
|
|
6255
6498
|
}
|
|
6499
|
+
const trio = resolveBrainReadTrio(opts);
|
|
6500
|
+
if (!trio.ok) {
|
|
6501
|
+
return { ok: false, reason: `brain_read_not_configured:${trio.missingEnv.join(",")}` };
|
|
6502
|
+
}
|
|
6503
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
6256
6504
|
const doFetch = resolveFetch(injectedFetch);
|
|
6257
6505
|
const base = normalizeEndpoint2(brainEndpoint);
|
|
6258
6506
|
const lookupUrl = `${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=app_id,rule`;
|
|
@@ -6337,7 +6585,7 @@ async function markAdvisoryResolved(opts) {
|
|
|
6337
6585
|
}
|
|
6338
6586
|
}
|
|
6339
6587
|
if (outcomeIds.length === 0) {
|
|
6340
|
-
return { ok: true };
|
|
6588
|
+
return { ok: true, firingsResolved: 0, status: "unknown" };
|
|
6341
6589
|
}
|
|
6342
6590
|
const inList = outcomeIds.join(",");
|
|
6343
6591
|
const patchUrl = `${base}/rest/v1/compile_outcome_advisories?outcome_id=in.(${inList})&code=eq.${encodeURIComponent(code)}&resolved_at=is.null`;
|
|
@@ -6357,8 +6605,10 @@ async function markAdvisoryResolved(opts) {
|
|
|
6357
6605
|
apikey: brainAnonKey,
|
|
6358
6606
|
"Content-Type": "application/json",
|
|
6359
6607
|
Accept: "application/json",
|
|
6360
|
-
//
|
|
6361
|
-
|
|
6608
|
+
// alpha.78 — count what actually changed. An RLS-filtered PATCH
|
|
6609
|
+
// returns 2xx with zero rows; return=representation makes that
|
|
6610
|
+
// visible instead of success-shaped.
|
|
6611
|
+
Prefer: "return=representation"
|
|
6362
6612
|
},
|
|
6363
6613
|
body: JSON.stringify(patchBody)
|
|
6364
6614
|
});
|
|
@@ -6375,7 +6625,33 @@ async function markAdvisoryResolved(opts) {
|
|
|
6375
6625
|
if (!patchRes.ok) {
|
|
6376
6626
|
return { ok: false, reason: `patch_failed:${patchRes.status}` };
|
|
6377
6627
|
}
|
|
6378
|
-
|
|
6628
|
+
let patchedRows = [];
|
|
6629
|
+
try {
|
|
6630
|
+
patchedRows = await patchRes.json();
|
|
6631
|
+
} catch {
|
|
6632
|
+
}
|
|
6633
|
+
const firingsResolved = Array.isArray(patchedRows) ? patchedRows.length : 0;
|
|
6634
|
+
let status = "unknown";
|
|
6635
|
+
try {
|
|
6636
|
+
const statusRes = await doFetch(
|
|
6637
|
+
`${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=status`,
|
|
6638
|
+
{
|
|
6639
|
+
method: "GET",
|
|
6640
|
+
headers: {
|
|
6641
|
+
Authorization: `Bearer ${brainJwt}`,
|
|
6642
|
+
apikey: brainAnonKey,
|
|
6643
|
+
Accept: "application/json"
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
);
|
|
6647
|
+
if (statusRes.ok) {
|
|
6648
|
+
const rows = await statusRes.json();
|
|
6649
|
+
const s = Array.isArray(rows) ? rows[0]?.status : void 0;
|
|
6650
|
+
if (s === "open" || s === "resolved") status = s;
|
|
6651
|
+
}
|
|
6652
|
+
} catch {
|
|
6653
|
+
}
|
|
6654
|
+
return { ok: true, firingsResolved, status };
|
|
6379
6655
|
}
|
|
6380
6656
|
async function markExclusionFindingHandled(opts) {
|
|
6381
6657
|
const {
|
|
@@ -6384,12 +6660,14 @@ async function markExclusionFindingHandled(opts) {
|
|
|
6384
6660
|
excludedModel,
|
|
6385
6661
|
resolution,
|
|
6386
6662
|
resolutionNote,
|
|
6387
|
-
brainEndpoint,
|
|
6388
|
-
brainJwt,
|
|
6389
|
-
brainAnonKey,
|
|
6390
6663
|
fetch: injectedFetch
|
|
6391
6664
|
} = opts;
|
|
6392
6665
|
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
6666
|
+
const trio = resolveBrainReadTrio(opts);
|
|
6667
|
+
if (!trio.ok) {
|
|
6668
|
+
return { ok: false, reason: `brain_read_not_configured:${trio.missingEnv.join(",")}` };
|
|
6669
|
+
}
|
|
6670
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
6393
6671
|
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
6394
6672
|
if (!excludedModel) {
|
|
6395
6673
|
return { ok: false, reason: "excluded_model_required" };
|
|
@@ -6655,6 +6933,7 @@ export {
|
|
|
6655
6933
|
ALL_ARCHETYPES,
|
|
6656
6934
|
ARCHETYPE_FAMILY_FITS,
|
|
6657
6935
|
ARCHETYPE_FLOOR_DEFAULT,
|
|
6936
|
+
BRAIN_READ_ENV_NAMES,
|
|
6658
6937
|
COACH_CFG,
|
|
6659
6938
|
CallError,
|
|
6660
6939
|
DECOMPOSITION_TEMPLATES,
|
|
@@ -6673,6 +6952,7 @@ export {
|
|
|
6673
6952
|
MEASURED_GROUNDING_MIN_N,
|
|
6674
6953
|
PRODUCER_OWNED_RULE_CODES,
|
|
6675
6954
|
PROVIDER_ENV_KEYS,
|
|
6955
|
+
ROLLBACK_SUPPRESSION_WINDOW_DAYS,
|
|
6676
6956
|
RULE_DISCIPLINE_GATES_V1,
|
|
6677
6957
|
RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
6678
6958
|
RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
@@ -6726,12 +7006,14 @@ export {
|
|
|
6726
7006
|
getPerAxisMetrics,
|
|
6727
7007
|
getProfile,
|
|
6728
7008
|
getReachabilityDiagnostic,
|
|
7009
|
+
getRecentRollback,
|
|
6729
7010
|
getRecommendedPrimary,
|
|
6730
7011
|
getSequentialStarterChain,
|
|
6731
7012
|
getSequentialStarterChainWithGrounding,
|
|
6732
7013
|
getStaleExclusionFindings,
|
|
6733
7014
|
getStarterChain,
|
|
6734
7015
|
getStarterChainWithGrounding,
|
|
7016
|
+
hasMutation,
|
|
6735
7017
|
hashShape,
|
|
6736
7018
|
isArchetype,
|
|
6737
7019
|
isAutoPromoteEnabledFromEnv,
|
|
@@ -6752,9 +7034,11 @@ export {
|
|
|
6752
7034
|
loadChainsFromBrain,
|
|
6753
7035
|
loadModelsFromBrain,
|
|
6754
7036
|
loadPricingFromBrain,
|
|
7037
|
+
mapMeasuredFailureRows,
|
|
6755
7038
|
markAdvisoryResolved,
|
|
6756
7039
|
markExclusionFindingHandled,
|
|
6757
7040
|
markPromoteReadyHandled,
|
|
7041
|
+
mutationId,
|
|
6758
7042
|
parseGoldenCaptureRate,
|
|
6759
7043
|
parseJudgeVerdict,
|
|
6760
7044
|
peekBrainDeadLetter,
|