@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.js
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
ALL_ARCHETYPES: () => ALL_ARCHETYPES,
|
|
26
26
|
ARCHETYPE_FAMILY_FITS: () => ARCHETYPE_FAMILY_FITS,
|
|
27
27
|
ARCHETYPE_FLOOR_DEFAULT: () => ARCHETYPE_FLOOR_DEFAULT,
|
|
28
|
+
BRAIN_READ_ENV_NAMES: () => BRAIN_READ_ENV_NAMES,
|
|
28
29
|
COACH_CFG: () => COACH_CFG,
|
|
29
30
|
CallError: () => CallError,
|
|
30
31
|
DECOMPOSITION_TEMPLATES: () => DECOMPOSITION_TEMPLATES,
|
|
@@ -43,6 +44,7 @@ __export(index_exports, {
|
|
|
43
44
|
MEASURED_GROUNDING_MIN_N: () => MEASURED_GROUNDING_MIN_N,
|
|
44
45
|
PRODUCER_OWNED_RULE_CODES: () => PRODUCER_OWNED_RULE_CODES,
|
|
45
46
|
PROVIDER_ENV_KEYS: () => PROVIDER_ENV_KEYS,
|
|
47
|
+
ROLLBACK_SUPPRESSION_WINDOW_DAYS: () => ROLLBACK_SUPPRESSION_WINDOW_DAYS,
|
|
46
48
|
RULE_DISCIPLINE_GATES_V1: () => RULE_DISCIPLINE_GATES_V1,
|
|
47
49
|
RULE_DISCIPLINE_GATES_V1_STRUCTURED: () => RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
48
50
|
RULE_SEQUENTIAL_TOOL_CLIFF: () => RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
@@ -96,12 +98,14 @@ __export(index_exports, {
|
|
|
96
98
|
getPerAxisMetrics: () => getPerAxisMetrics,
|
|
97
99
|
getProfile: () => getProfile,
|
|
98
100
|
getReachabilityDiagnostic: () => getReachabilityDiagnostic,
|
|
101
|
+
getRecentRollback: () => getRecentRollback,
|
|
99
102
|
getRecommendedPrimary: () => getRecommendedPrimary,
|
|
100
103
|
getSequentialStarterChain: () => getSequentialStarterChain,
|
|
101
104
|
getSequentialStarterChainWithGrounding: () => getSequentialStarterChainWithGrounding,
|
|
102
105
|
getStaleExclusionFindings: () => getStaleExclusionFindings,
|
|
103
106
|
getStarterChain: () => getStarterChain,
|
|
104
107
|
getStarterChainWithGrounding: () => getStarterChainWithGrounding,
|
|
108
|
+
hasMutation: () => hasMutation,
|
|
105
109
|
hashShape: () => hashShape,
|
|
106
110
|
isArchetype: () => isArchetype,
|
|
107
111
|
isAutoPromoteEnabledFromEnv: () => isAutoPromoteEnabledFromEnv,
|
|
@@ -122,9 +126,11 @@ __export(index_exports, {
|
|
|
122
126
|
loadChainsFromBrain: () => loadChainsFromBrain,
|
|
123
127
|
loadModelsFromBrain: () => loadModelsFromBrain,
|
|
124
128
|
loadPricingFromBrain: () => loadPricingFromBrain,
|
|
129
|
+
mapMeasuredFailureRows: () => mapMeasuredFailureRows,
|
|
125
130
|
markAdvisoryResolved: () => markAdvisoryResolved,
|
|
126
131
|
markExclusionFindingHandled: () => markExclusionFindingHandled,
|
|
127
132
|
markPromoteReadyHandled: () => markPromoteReadyHandled,
|
|
133
|
+
mutationId: () => mutationId,
|
|
128
134
|
parseGoldenCaptureRate: () => parseGoldenCaptureRate,
|
|
129
135
|
parseJudgeVerdict: () => parseJudgeVerdict,
|
|
130
136
|
peekBrainDeadLetter: () => peekBrainDeadLetter,
|
|
@@ -693,6 +699,22 @@ var PROFILES_RAW = [
|
|
|
693
699
|
threshold: 16,
|
|
694
700
|
action: "drop_to_top_relevant",
|
|
695
701
|
reason: "Haiku reliability degrades above ~16 tools"
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
// alpha.78 — the declared `structuredOutput: 'grammar'` does NOT
|
|
705
|
+
// hold on long-input summarize. MEASURED (brain, playbacksam):
|
|
706
|
+
// 21 disambiguated `structured_output_parse_failed` fallover rows
|
|
707
|
+
// 2026-07-22..27, tokens_in 12,280–31,450; PB's gate counted 20/20
|
|
708
|
+
// in-window failures. Clean traffic p50 sits at ~9K tokens_in, so
|
|
709
|
+
// 12K gates the failing band without touching the working one.
|
|
710
|
+
// Short-input summarize carries no failure evidence and stays
|
|
711
|
+
// ungated — this is why it's a cliff, not an archetype-wide
|
|
712
|
+
// `structuredOutputHint: 'avoid'`.
|
|
713
|
+
metric: "input_tokens",
|
|
714
|
+
threshold: 12e3,
|
|
715
|
+
action: "quality_gate_structured",
|
|
716
|
+
whenIntent: "summarize",
|
|
717
|
+
reason: "Structured-output parse failures at 100% in-window on long-input summarize (measured on playbacksam, 2026-07-25..27; haiku only led when input size made price dominate, then failed every time)."
|
|
696
718
|
}
|
|
697
719
|
],
|
|
698
720
|
costInputPer1m: 1,
|
|
@@ -2939,11 +2961,18 @@ function passScoreTargets(ir, opts) {
|
|
|
2939
2961
|
}
|
|
2940
2962
|
}
|
|
2941
2963
|
let qualityGatePenalty = 0;
|
|
2964
|
+
let structuredCliffGate;
|
|
2942
2965
|
if (constraints.structuredOutput) {
|
|
2943
2966
|
const schemaWeak = effectiveConventions(profile).some(
|
|
2944
2967
|
(c) => c.archetype === ir.intent.archetype && c.structuredOutputHint === "avoid"
|
|
2945
2968
|
);
|
|
2946
2969
|
if (schemaWeak) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
2970
|
+
if (!schemaWeak) {
|
|
2971
|
+
structuredCliffGate = profile.cliffs.find(
|
|
2972
|
+
(c) => c.action === "quality_gate_structured" && (!c.whenIntent || c.whenIntent === ir.intent.archetype) && c.metric === "input_tokens" && opts.estimatedInputTokens >= c.threshold
|
|
2973
|
+
);
|
|
2974
|
+
if (structuredCliffGate) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
2975
|
+
}
|
|
2947
2976
|
}
|
|
2948
2977
|
const measuredGate = opts.measuredFailureGates?.get(modelId);
|
|
2949
2978
|
if (measuredGate) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
@@ -3008,6 +3037,16 @@ function passScoreTargets(ir, opts) {
|
|
|
3008
3037
|
rankAfter: rank,
|
|
3009
3038
|
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.`
|
|
3010
3039
|
});
|
|
3040
|
+
} else if (structuredCliffGate) {
|
|
3041
|
+
policyMutations.push({
|
|
3042
|
+
id: `quality-gate-structured-cliff-${modelId}`,
|
|
3043
|
+
source: "quality_gate",
|
|
3044
|
+
passName: "score_targets",
|
|
3045
|
+
rankDelta: -qualityGatePenalty,
|
|
3046
|
+
rankBefore,
|
|
3047
|
+
rankAfter: rank,
|
|
3048
|
+
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.`
|
|
3049
|
+
});
|
|
3011
3050
|
} else {
|
|
3012
3051
|
policyMutations.push({
|
|
3013
3052
|
id: `quality-gate-structured-${modelId}`,
|
|
@@ -3691,13 +3730,8 @@ function getArchetypePerfScore(modelId, archetype) {
|
|
|
3691
3730
|
return { score, n, grounding };
|
|
3692
3731
|
}
|
|
3693
3732
|
|
|
3694
|
-
// src/
|
|
3695
|
-
function
|
|
3696
|
-
if (!x || typeof x !== "object") return false;
|
|
3697
|
-
const r = x;
|
|
3698
|
-
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";
|
|
3699
|
-
}
|
|
3700
|
-
function coerceNumber(v) {
|
|
3733
|
+
// src/measured-failure-brain.ts
|
|
3734
|
+
function coerceCount(v) {
|
|
3701
3735
|
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
3702
3736
|
if (typeof v === "string") {
|
|
3703
3737
|
const n = Number(v);
|
|
@@ -3705,58 +3739,144 @@ function coerceNumber(v) {
|
|
|
3705
3739
|
}
|
|
3706
3740
|
return null;
|
|
3707
3741
|
}
|
|
3708
|
-
function
|
|
3742
|
+
function isRawFailureRow(x) {
|
|
3743
|
+
if (!x || typeof x !== "object") return false;
|
|
3744
|
+
const r = x;
|
|
3745
|
+
return typeof r.intent_archetype === "string" && typeof r.model === "string" && (typeof r.n === "number" || typeof r.n === "string");
|
|
3746
|
+
}
|
|
3747
|
+
function mapRows(rows) {
|
|
3709
3748
|
const out = [];
|
|
3710
3749
|
for (const row of rows) {
|
|
3711
|
-
if (!
|
|
3712
|
-
const
|
|
3713
|
-
const
|
|
3714
|
-
|
|
3715
|
-
if (sampleN === null || passRate === null || avgScore === null) continue;
|
|
3750
|
+
if (!isRawFailureRow(row)) continue;
|
|
3751
|
+
const n = coerceCount(row.n);
|
|
3752
|
+
const nFail = coerceCount(row.n_fail) ?? 0;
|
|
3753
|
+
if (n === null || n <= 0) continue;
|
|
3716
3754
|
out.push({
|
|
3717
3755
|
archetype: row.intent_archetype,
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
sampleN,
|
|
3722
|
-
judgePassRate: passRate,
|
|
3723
|
-
judgeAvgScore: avgScore,
|
|
3724
|
-
costDeltaPct: coerceNumber(row.cost_delta_pct),
|
|
3725
|
-
detectedAt: row.detected_at
|
|
3756
|
+
model: row.model,
|
|
3757
|
+
n,
|
|
3758
|
+
nFail
|
|
3726
3759
|
});
|
|
3727
3760
|
}
|
|
3728
3761
|
return out;
|
|
3729
3762
|
}
|
|
3763
|
+
var MEASURED_FAILURE_CFG = {
|
|
3764
|
+
/**
|
|
3765
|
+
* Hard minimum attempts before ANY gate may be created. Guards against
|
|
3766
|
+
* pathological tiny samples that the confidence bound alone would let
|
|
3767
|
+
* through in edge cases. At 5-for-5 the bound clears the threshold; at
|
|
3768
|
+
* 3-for-3 it does not, which is the behaviour we want (three failures is
|
|
3769
|
+
* a bad day, five in a row is a pattern).
|
|
3770
|
+
*/
|
|
3771
|
+
minSample: 5,
|
|
3772
|
+
/**
|
|
3773
|
+
* Gate when we are 95% confident the model fails MORE OFTEN THAN IT
|
|
3774
|
+
* SUCCEEDS on this surface. Deliberately unarguable rather than tuned —
|
|
3775
|
+
* a model that probably fails the majority of the time has no business
|
|
3776
|
+
* leading a surface, whatever its declared scores say.
|
|
3777
|
+
*/
|
|
3778
|
+
lowerBoundThreshold: 0.5,
|
|
3779
|
+
/** 95% one-sided-ish confidence (standard two-sided z at α=0.05). */
|
|
3780
|
+
z: 1.96,
|
|
3781
|
+
/** Must match the view's window. Documented here for the advisory text. */
|
|
3782
|
+
windowDays: 28
|
|
3783
|
+
};
|
|
3784
|
+
function wilsonLowerBound(failures, n, z = MEASURED_FAILURE_CFG.z) {
|
|
3785
|
+
if (n <= 0) return 0;
|
|
3786
|
+
const p = failures / n;
|
|
3787
|
+
const z2 = z * z;
|
|
3788
|
+
const denom = 1 + z2 / n;
|
|
3789
|
+
const centre = p + z2 / (2 * n);
|
|
3790
|
+
const margin = z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
|
|
3791
|
+
const lower2 = (centre - margin) / denom;
|
|
3792
|
+
return lower2 < 0 ? 0 : lower2;
|
|
3793
|
+
}
|
|
3794
|
+
function mapMeasuredFailureRows(rows) {
|
|
3795
|
+
return mapRows(rows);
|
|
3796
|
+
}
|
|
3797
|
+
function judgeMeasuredFailure(row, cfg = MEASURED_FAILURE_CFG) {
|
|
3798
|
+
if (!row) return void 0;
|
|
3799
|
+
const normalized = "nFail" in row && typeof row.n === "number" ? row : mapRows([row])[0];
|
|
3800
|
+
if (!normalized || normalized.n < cfg.minSample) return void 0;
|
|
3801
|
+
const lowerBound = wilsonLowerBound(normalized.nFail, normalized.n, cfg.z);
|
|
3802
|
+
return {
|
|
3803
|
+
gated: lowerBound > cfg.lowerBoundThreshold,
|
|
3804
|
+
rate: normalized.nFail / normalized.n,
|
|
3805
|
+
lowerBound,
|
|
3806
|
+
n: normalized.n,
|
|
3807
|
+
nFail: normalized.nFail
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3730
3810
|
var snapshots2 = /* @__PURE__ */ new Map();
|
|
3731
3811
|
var runtime3;
|
|
3732
3812
|
var warnedOnce2 = false;
|
|
3733
|
-
|
|
3813
|
+
var DEFAULT_MEASURED_FAILURE_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/measured-failure";
|
|
3814
|
+
function isMeasuredFailureGateEnabledFromEnv(envSource) {
|
|
3815
|
+
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
3816
|
+
const raw = (env.KGAUTO_MEASURED_FAILURE_GATE ?? "").trim().toLowerCase();
|
|
3817
|
+
return !(raw === "0" || raw === "false");
|
|
3818
|
+
}
|
|
3819
|
+
function configureMeasuredFailureBrain(rt) {
|
|
3820
|
+
runtime3 = rt;
|
|
3821
|
+
snapshots2.clear();
|
|
3822
|
+
warnedOnce2 = false;
|
|
3823
|
+
}
|
|
3824
|
+
function isMeasuredFailureBrainActive() {
|
|
3734
3825
|
return runtime3 !== void 0;
|
|
3735
3826
|
}
|
|
3736
|
-
function
|
|
3827
|
+
function prefetchMeasuredFailure(appId) {
|
|
3737
3828
|
const rt = runtime3;
|
|
3738
|
-
if (!rt) return
|
|
3739
|
-
|
|
3740
|
-
if (!
|
|
3829
|
+
if (!rt || !appId) return void 0;
|
|
3830
|
+
let snap = snapshots2.get(appId);
|
|
3831
|
+
if (!snap) {
|
|
3832
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
3833
|
+
snapshots2.set(appId, snap);
|
|
3834
|
+
}
|
|
3835
|
+
if (snap.expiresAt > Date.now()) return void 0;
|
|
3836
|
+
const inflight = pendingRefreshes2.get(appId);
|
|
3837
|
+
if (inflight) return inflight;
|
|
3838
|
+
if (snap.refreshing) return void 0;
|
|
3839
|
+
snap.refreshing = true;
|
|
3840
|
+
void asyncRefresh3(rt, appId);
|
|
3841
|
+
return pendingRefreshes2.get(appId);
|
|
3842
|
+
}
|
|
3843
|
+
async function awaitMeasuredFailureReady(appId, timeoutMs) {
|
|
3844
|
+
if (!runtime3 || !appId) return;
|
|
3845
|
+
const pending = prefetchMeasuredFailure(appId) ?? pendingRefreshes2.get(appId);
|
|
3846
|
+
if (!(timeoutMs > 0)) return;
|
|
3847
|
+
if (!pending) return;
|
|
3848
|
+
let timer;
|
|
3849
|
+
try {
|
|
3850
|
+
await Promise.race([
|
|
3851
|
+
pending,
|
|
3852
|
+
new Promise((resolve) => {
|
|
3853
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
3854
|
+
})
|
|
3855
|
+
]);
|
|
3856
|
+
} catch {
|
|
3857
|
+
} finally {
|
|
3858
|
+
if (timer) clearTimeout(timer);
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
function getMeasuredFailureVerdict(opts) {
|
|
3862
|
+
const rt = runtime3;
|
|
3863
|
+
if (!rt) return void 0;
|
|
3864
|
+
const { appId, archetype, model } = opts;
|
|
3865
|
+
if (!appId || !archetype || !model) return void 0;
|
|
3741
3866
|
let snap = snapshots2.get(appId);
|
|
3742
3867
|
if (!snap) {
|
|
3743
3868
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
3744
3869
|
snapshots2.set(appId, snap);
|
|
3745
3870
|
}
|
|
3746
3871
|
const now = Date.now();
|
|
3747
|
-
|
|
3748
|
-
if (stale && !snap.refreshing) {
|
|
3872
|
+
if (snap.expiresAt <= now && !snap.refreshing) {
|
|
3749
3873
|
snap.refreshing = true;
|
|
3750
3874
|
void asyncRefresh3(rt, appId);
|
|
3751
3875
|
}
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
if (opts.family) {
|
|
3757
|
-
rows = rows.filter((f) => f.family === opts.family);
|
|
3758
|
-
}
|
|
3759
|
-
return rows;
|
|
3876
|
+
const row = snap.data.find(
|
|
3877
|
+
(r) => r.archetype === archetype && r.model === model
|
|
3878
|
+
);
|
|
3879
|
+
return judgeMeasuredFailure(row);
|
|
3760
3880
|
}
|
|
3761
3881
|
var pendingRefreshes2 = /* @__PURE__ */ new Map();
|
|
3762
3882
|
async function asyncRefresh3(rt, appId) {
|
|
@@ -3780,12 +3900,11 @@ async function doRefresh3(rt, appId) {
|
|
|
3780
3900
|
try {
|
|
3781
3901
|
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
3782
3902
|
if (!res.ok) {
|
|
3783
|
-
throw new Error(`
|
|
3903
|
+
throw new Error(`measured-failure ${res.status}: ${res.statusText}`);
|
|
3784
3904
|
}
|
|
3785
3905
|
const body = await res.json();
|
|
3786
3906
|
if (runtime3 !== rt) return;
|
|
3787
|
-
|
|
3788
|
-
snap.data = rows;
|
|
3907
|
+
snap.data = Array.isArray(body) ? mapRows(body) : [];
|
|
3789
3908
|
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
3790
3909
|
snap.refreshing = false;
|
|
3791
3910
|
} catch (err) {
|
|
@@ -3800,193 +3919,84 @@ async function doRefresh3(rt, appId) {
|
|
|
3800
3919
|
}
|
|
3801
3920
|
function defaultOnError3(err) {
|
|
3802
3921
|
console.warn(
|
|
3803
|
-
"[kgauto]
|
|
3922
|
+
"[kgauto] measured-failure fetch failed (gate inactive until next refresh):",
|
|
3804
3923
|
err
|
|
3805
3924
|
);
|
|
3806
3925
|
}
|
|
3807
|
-
function
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3926
|
+
function _testResetMeasuredFailure() {
|
|
3927
|
+
runtime3 = void 0;
|
|
3928
|
+
snapshots2.clear();
|
|
3929
|
+
pendingRefreshes2 = /* @__PURE__ */ new Map();
|
|
3930
|
+
warnedOnce2 = false;
|
|
3812
3931
|
}
|
|
3813
|
-
async function
|
|
3814
|
-
const
|
|
3815
|
-
|
|
3816
|
-
archetype,
|
|
3817
|
-
family,
|
|
3818
|
-
resolution,
|
|
3819
|
-
resolutionNote,
|
|
3820
|
-
brainEndpoint,
|
|
3821
|
-
brainJwt,
|
|
3822
|
-
brainAnonKey,
|
|
3823
|
-
fetch: injectedFetch
|
|
3824
|
-
} = opts;
|
|
3825
|
-
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
3826
|
-
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
3827
|
-
if (!family) return { ok: false, reason: "family_required" };
|
|
3828
|
-
if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
|
|
3829
|
-
return { ok: false, reason: "resolution_invalid" };
|
|
3830
|
-
}
|
|
3831
|
-
const doFetch = resolveFetchImpl(injectedFetch);
|
|
3832
|
-
const base = normalizeEndpoint(brainEndpoint);
|
|
3833
|
-
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`;
|
|
3834
|
-
const patchBody = {
|
|
3835
|
-
resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3836
|
-
resolution
|
|
3837
|
-
};
|
|
3838
|
-
if (resolutionNote !== void 0) {
|
|
3839
|
-
patchBody.resolution_note = resolutionNote;
|
|
3840
|
-
}
|
|
3841
|
-
let res;
|
|
3842
|
-
try {
|
|
3843
|
-
res = await doFetch(url, {
|
|
3844
|
-
method: "PATCH",
|
|
3845
|
-
headers: {
|
|
3846
|
-
Authorization: `Bearer ${brainJwt}`,
|
|
3847
|
-
apikey: brainAnonKey,
|
|
3848
|
-
"Content-Type": "application/json",
|
|
3849
|
-
Accept: "application/json",
|
|
3850
|
-
Prefer: "return=minimal"
|
|
3851
|
-
},
|
|
3852
|
-
body: JSON.stringify(patchBody)
|
|
3853
|
-
});
|
|
3854
|
-
} catch (err) {
|
|
3855
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3856
|
-
return { ok: false, reason: `network_error:${msg}` };
|
|
3857
|
-
}
|
|
3858
|
-
if (res.status === 401 || res.status === 403) {
|
|
3859
|
-
return { ok: false, reason: "brain_auth_misconfig" };
|
|
3860
|
-
}
|
|
3861
|
-
if (res.status >= 500) {
|
|
3862
|
-
return { ok: false, reason: "brain_unavailable" };
|
|
3863
|
-
}
|
|
3864
|
-
if (!res.ok) {
|
|
3865
|
-
return { ok: false, reason: `patch_failed:${res.status}` };
|
|
3866
|
-
}
|
|
3867
|
-
return { ok: true };
|
|
3932
|
+
async function _testWaitForMeasuredFailureRefresh() {
|
|
3933
|
+
const pending = Array.from(pendingRefreshes2.values());
|
|
3934
|
+
if (pending.length > 0) await Promise.all(pending);
|
|
3868
3935
|
}
|
|
3869
3936
|
|
|
3870
|
-
// src/
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
function shouldFirePromoteReady(finding, resolvedPrimary) {
|
|
3876
|
-
if (finding.currentModel !== resolvedPrimary) return false;
|
|
3877
|
-
if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
|
|
3878
|
-
if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
|
|
3879
|
-
return true;
|
|
3937
|
+
// src/promotions-brain.ts
|
|
3938
|
+
function isRawPromotionRow(x) {
|
|
3939
|
+
if (!x || typeof x !== "object") return false;
|
|
3940
|
+
const r = x;
|
|
3941
|
+
return (typeof r.id === "number" || typeof r.id === "string") && typeof r.intent_archetype === "string" && typeof r.promoted_model === "string" && typeof r.incumbent_model === "string";
|
|
3880
3942
|
}
|
|
3881
|
-
function
|
|
3882
|
-
if (
|
|
3883
|
-
if (
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
|
|
3888
|
-
if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
|
|
3889
|
-
if (modelId.startsWith("deepseek-")) return "deepseek-chat";
|
|
3890
|
-
if (modelId.startsWith("gpt-")) return "openai-gpt";
|
|
3943
|
+
function coerceId(v) {
|
|
3944
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
3945
|
+
if (typeof v === "string") {
|
|
3946
|
+
const n = Number(v);
|
|
3947
|
+
return Number.isFinite(n) ? n : null;
|
|
3948
|
+
}
|
|
3891
3949
|
return null;
|
|
3892
3950
|
}
|
|
3893
|
-
function
|
|
3894
|
-
if (!isPromoteReadyBrainActive()) return [];
|
|
3895
|
-
if (!ctx.appId) return [];
|
|
3896
|
-
if (!ctx.resolvedPrimary) return [];
|
|
3897
|
-
const family = deriveFamilyLocal(ctx.resolvedPrimary);
|
|
3898
|
-
if (!family) return [];
|
|
3899
|
-
const findings = loadPromoteReadyFindings({
|
|
3900
|
-
appId: ctx.appId,
|
|
3901
|
-
archetype: ctx.archetype,
|
|
3902
|
-
family
|
|
3903
|
-
});
|
|
3904
|
-
if (findings.length === 0) return [];
|
|
3905
|
-
const qualifying = findings.filter(
|
|
3906
|
-
(f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
|
|
3907
|
-
);
|
|
3908
|
-
if (qualifying.length === 0) return [];
|
|
3909
|
-
qualifying.sort((a, b) => {
|
|
3910
|
-
if (a.judgeAvgScore !== b.judgeAvgScore) {
|
|
3911
|
-
return b.judgeAvgScore - a.judgeAvgScore;
|
|
3912
|
-
}
|
|
3913
|
-
return b.judgePassRate - a.judgePassRate;
|
|
3914
|
-
});
|
|
3915
|
-
const top = qualifying[0];
|
|
3916
|
-
const pctPass = Math.round(top.judgePassRate * 100);
|
|
3917
|
-
const score = top.judgeAvgScore.toFixed(2);
|
|
3918
|
-
let costClause = "";
|
|
3919
|
-
if (top.costDeltaPct !== null) {
|
|
3920
|
-
const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
|
|
3921
|
-
const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
|
|
3922
|
-
costClause = `, cost ${magnitude}% ${sign}`;
|
|
3923
|
-
}
|
|
3924
|
-
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.`;
|
|
3925
|
-
return [
|
|
3926
|
-
{
|
|
3927
|
-
level: "info",
|
|
3928
|
-
code: "promote-ready",
|
|
3929
|
-
message,
|
|
3930
|
-
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.`,
|
|
3931
|
-
// alpha.36 architectural field — not a no-ai-needed case.
|
|
3932
|
-
recommendedArchitecture: void 0,
|
|
3933
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
3934
|
-
}
|
|
3935
|
-
];
|
|
3936
|
-
}
|
|
3937
|
-
|
|
3938
|
-
// src/advisor-rules/consumer-on-stale-model.ts
|
|
3939
|
-
function isStaleStatus(v) {
|
|
3940
|
-
return v === "legacy" || v === "deprecated";
|
|
3941
|
-
}
|
|
3942
|
-
function asString(v) {
|
|
3943
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
3944
|
-
}
|
|
3945
|
-
function mapRowsToFindings3(rows) {
|
|
3951
|
+
function mapRowsToPromotions(rows) {
|
|
3946
3952
|
const out = [];
|
|
3947
|
-
for (const
|
|
3948
|
-
if (!
|
|
3949
|
-
const
|
|
3950
|
-
|
|
3951
|
-
const
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
};
|
|
3969
|
-
const suggestion = asString(r.suggestion);
|
|
3970
|
-
if (suggestion) row.suggestion = suggestion;
|
|
3971
|
-
if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
|
|
3972
|
-
row.observationCount = r.observation_count;
|
|
3973
|
-
}
|
|
3974
|
-
out.push(row);
|
|
3953
|
+
for (const row of rows) {
|
|
3954
|
+
if (!isRawPromotionRow(row)) continue;
|
|
3955
|
+
const id = coerceId(row.id);
|
|
3956
|
+
if (id === null) continue;
|
|
3957
|
+
const mode = row.mode === "strategy" ? "strategy" : row.mode === "downswap" || row.mode === void 0 ? "downswap" : null;
|
|
3958
|
+
if (mode === null) continue;
|
|
3959
|
+
out.push({
|
|
3960
|
+
id,
|
|
3961
|
+
archetype: row.intent_archetype,
|
|
3962
|
+
mode,
|
|
3963
|
+
strategy: typeof row.strategy === "string" ? row.strategy : null,
|
|
3964
|
+
promotedModel: row.promoted_model,
|
|
3965
|
+
incumbentModel: row.incumbent_model,
|
|
3966
|
+
evalRunId: coerceId(row.eval_run_id ?? null),
|
|
3967
|
+
suppressQualityGate: row.suppress_quality_gate === true,
|
|
3968
|
+
promotedAt: typeof row.promoted_at === "string" ? row.promoted_at : "",
|
|
3969
|
+
// Pre-.78 endpoints serve no status column and only active rows —
|
|
3970
|
+
// defaulting to 'active' is exact, not optimistic.
|
|
3971
|
+
status: row.status === "rolled_back" ? "rolled_back" : "active",
|
|
3972
|
+
...typeof row.rolled_back_at === "string" ? { rolledBackAt: row.rolled_back_at } : {},
|
|
3973
|
+
...typeof row.rollback_class === "string" ? { rollbackClass: row.rollback_class } : {}
|
|
3974
|
+
});
|
|
3975
3975
|
}
|
|
3976
3976
|
return out;
|
|
3977
3977
|
}
|
|
3978
3978
|
var snapshots3 = /* @__PURE__ */ new Map();
|
|
3979
3979
|
var runtime4;
|
|
3980
3980
|
var warnedOnce3 = false;
|
|
3981
|
-
var
|
|
3982
|
-
function
|
|
3981
|
+
var DEFAULT_PROMOTIONS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/promotions";
|
|
3982
|
+
function isAutoPromoteEnabledFromEnv(envSource) {
|
|
3983
|
+
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
3984
|
+
const raw = (env.KGAUTO_AUTO_PROMOTE ?? "").trim().toLowerCase();
|
|
3985
|
+
return raw === "1" || raw === "true";
|
|
3986
|
+
}
|
|
3987
|
+
function configurePromotionsBrain(rt) {
|
|
3988
|
+
runtime4 = rt;
|
|
3989
|
+
snapshots3.clear();
|
|
3990
|
+
warnedOnce3 = false;
|
|
3991
|
+
}
|
|
3992
|
+
function isPromotionsBrainActive() {
|
|
3983
3993
|
return runtime4 !== void 0;
|
|
3984
3994
|
}
|
|
3985
|
-
function
|
|
3995
|
+
function getApplicablePromotion(opts) {
|
|
3986
3996
|
const rt = runtime4;
|
|
3987
|
-
if (!rt) return
|
|
3997
|
+
if (!rt) return void 0;
|
|
3988
3998
|
const appId = opts.appId;
|
|
3989
|
-
if (!appId) return
|
|
3999
|
+
if (!appId || !opts.archetype || !opts.mode) return void 0;
|
|
3990
4000
|
let snap = snapshots3.get(appId);
|
|
3991
4001
|
if (!snap) {
|
|
3992
4002
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
@@ -3998,11 +4008,31 @@ function getStaleModelFindings(opts) {
|
|
|
3998
4008
|
snap.refreshing = true;
|
|
3999
4009
|
void asyncRefresh4(rt, appId);
|
|
4000
4010
|
}
|
|
4001
|
-
|
|
4002
|
-
|
|
4011
|
+
return snap.data.find(
|
|
4012
|
+
(p) => p.status === "active" && p.archetype === opts.archetype && p.mode === opts.mode
|
|
4013
|
+
);
|
|
4014
|
+
}
|
|
4015
|
+
var ROLLBACK_SUPPRESSION_WINDOW_DAYS = 28;
|
|
4016
|
+
function getRecentRollback(opts) {
|
|
4017
|
+
const rt = runtime4;
|
|
4018
|
+
if (!rt) return void 0;
|
|
4019
|
+
if (!opts.appId || !opts.archetype || !opts.model) return void 0;
|
|
4020
|
+
let snap = snapshots3.get(opts.appId);
|
|
4021
|
+
if (!snap) {
|
|
4022
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4023
|
+
snapshots3.set(opts.appId, snap);
|
|
4003
4024
|
}
|
|
4004
|
-
|
|
4025
|
+
const now = opts.nowMs ?? Date.now();
|
|
4026
|
+
if (snap.expiresAt <= now && !snap.refreshing) {
|
|
4027
|
+
snap.refreshing = true;
|
|
4028
|
+
void asyncRefresh4(rt, opts.appId);
|
|
4029
|
+
}
|
|
4030
|
+
const windowMs = (opts.windowDays ?? ROLLBACK_SUPPRESSION_WINDOW_DAYS) * 864e5;
|
|
4031
|
+
return snap.data.find(
|
|
4032
|
+
(p) => p.status === "rolled_back" && p.archetype === opts.archetype && p.promotedModel === opts.model && typeof p.rolledBackAt === "string" && now - Date.parse(p.rolledBackAt) <= windowMs
|
|
4033
|
+
);
|
|
4005
4034
|
}
|
|
4035
|
+
var pendingRefreshes3 = /* @__PURE__ */ new Map();
|
|
4006
4036
|
async function asyncRefresh4(rt, appId) {
|
|
4007
4037
|
const promise = doRefresh4(rt, appId);
|
|
4008
4038
|
pendingRefreshes3.set(appId, promise);
|
|
@@ -4015,7 +4045,7 @@ async function asyncRefresh4(rt, appId) {
|
|
|
4015
4045
|
}
|
|
4016
4046
|
}
|
|
4017
4047
|
async function doRefresh4(rt, appId) {
|
|
4018
|
-
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
4048
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}&with_rollbacks=1`;
|
|
4019
4049
|
let snap = snapshots3.get(appId);
|
|
4020
4050
|
if (!snap) {
|
|
4021
4051
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
@@ -4024,11 +4054,11 @@ async function doRefresh4(rt, appId) {
|
|
|
4024
4054
|
try {
|
|
4025
4055
|
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
4026
4056
|
if (!res.ok) {
|
|
4027
|
-
throw new Error(`
|
|
4057
|
+
throw new Error(`promotions ${res.status}: ${res.statusText}`);
|
|
4028
4058
|
}
|
|
4029
4059
|
const body = await res.json();
|
|
4030
4060
|
if (runtime4 !== rt) return;
|
|
4031
|
-
const rows = Array.isArray(body) ?
|
|
4061
|
+
const rows = Array.isArray(body) ? mapRowsToPromotions(body) : [];
|
|
4032
4062
|
snap.data = rows;
|
|
4033
4063
|
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4034
4064
|
snap.refreshing = false;
|
|
@@ -4044,847 +4074,905 @@ async function doRefresh4(rt, appId) {
|
|
|
4044
4074
|
}
|
|
4045
4075
|
function defaultOnError4(err) {
|
|
4046
4076
|
console.warn(
|
|
4047
|
-
"[kgauto]
|
|
4077
|
+
"[kgauto] promotions fetch failed (promotion boost inactive until next refresh):",
|
|
4048
4078
|
err
|
|
4049
4079
|
);
|
|
4050
4080
|
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
if (
|
|
4060
|
-
const ranked = [...findings].sort((a, b) => {
|
|
4061
|
-
if (a.staleStatus !== b.staleStatus) {
|
|
4062
|
-
return a.staleStatus === "deprecated" ? -1 : 1;
|
|
4063
|
-
}
|
|
4064
|
-
return a.staleModel.localeCompare(b.staleModel);
|
|
4065
|
-
});
|
|
4066
|
-
const top = ranked[0];
|
|
4067
|
-
const extraCount = findings.length - 1;
|
|
4068
|
-
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
4069
|
-
return [
|
|
4070
|
-
{
|
|
4071
|
-
level: "warn",
|
|
4072
|
-
code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
|
|
4073
|
-
message: `${top.message}${extraNote}`,
|
|
4074
|
-
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}.`,
|
|
4075
|
-
recommendationType: "model-swap",
|
|
4076
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4077
|
-
}
|
|
4078
|
-
];
|
|
4081
|
+
function _testResetPromotions() {
|
|
4082
|
+
runtime4 = void 0;
|
|
4083
|
+
snapshots3.clear();
|
|
4084
|
+
pendingRefreshes3 = /* @__PURE__ */ new Map();
|
|
4085
|
+
warnedOnce3 = false;
|
|
4086
|
+
}
|
|
4087
|
+
async function _testWaitForPromotionsRefresh() {
|
|
4088
|
+
const pending = Array.from(pendingRefreshes3.values());
|
|
4089
|
+
if (pending.length > 0) await Promise.all(pending);
|
|
4079
4090
|
}
|
|
4080
4091
|
|
|
4081
|
-
// src/
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
{
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
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.",
|
|
4093
|
-
costGuidance: "comparable or cheaper at current pricing"
|
|
4094
|
-
}
|
|
4095
|
-
]);
|
|
4096
|
-
function findBetterFit(archetype, currentFamily) {
|
|
4097
|
-
for (const fit of ARCHETYPE_FAMILY_FITS) {
|
|
4098
|
-
if (fit.archetype !== archetype) continue;
|
|
4099
|
-
if (fit.betterFitFamily === currentFamily) return null;
|
|
4100
|
-
return fit;
|
|
4092
|
+
// src/promote-ready-brain.ts
|
|
4093
|
+
function isRawPromoteReadyRow(x) {
|
|
4094
|
+
if (!x || typeof x !== "object") return false;
|
|
4095
|
+
const r = x;
|
|
4096
|
+
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";
|
|
4097
|
+
}
|
|
4098
|
+
function coerceNumber(v) {
|
|
4099
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
4100
|
+
if (typeof v === "string") {
|
|
4101
|
+
const n = Number(v);
|
|
4102
|
+
return Number.isFinite(n) ? n : null;
|
|
4101
4103
|
}
|
|
4102
4104
|
return null;
|
|
4103
4105
|
}
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
const
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
if (
|
|
4112
|
-
|
|
4106
|
+
function mapRowsToFindings2(rows) {
|
|
4107
|
+
const out = [];
|
|
4108
|
+
for (const row of rows) {
|
|
4109
|
+
if (!isRawPromoteReadyRow(row)) continue;
|
|
4110
|
+
const sampleN = coerceNumber(row.sample_n);
|
|
4111
|
+
const passRate = coerceNumber(row.judge_pass_rate);
|
|
4112
|
+
const avgScore = coerceNumber(row.judge_avg_score);
|
|
4113
|
+
if (sampleN === null || passRate === null || avgScore === null) continue;
|
|
4114
|
+
out.push({
|
|
4115
|
+
archetype: row.intent_archetype,
|
|
4116
|
+
family: row.family,
|
|
4117
|
+
candidateModel: row.candidate_model,
|
|
4118
|
+
currentModel: row.current_model,
|
|
4119
|
+
sampleN,
|
|
4120
|
+
judgePassRate: passRate,
|
|
4121
|
+
judgeAvgScore: avgScore,
|
|
4122
|
+
costDeltaPct: coerceNumber(row.cost_delta_pct),
|
|
4123
|
+
detectedAt: row.detected_at
|
|
4124
|
+
});
|
|
4113
4125
|
}
|
|
4114
|
-
return
|
|
4126
|
+
return out;
|
|
4115
4127
|
}
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
if (profile.status !== "current") continue;
|
|
4122
|
-
if (profile.active === false) continue;
|
|
4123
|
-
candidates.push(profile.id);
|
|
4124
|
-
if (candidates.length >= 3) break;
|
|
4125
|
-
}
|
|
4126
|
-
return candidates;
|
|
4128
|
+
var snapshots4 = /* @__PURE__ */ new Map();
|
|
4129
|
+
var runtime5;
|
|
4130
|
+
var warnedOnce4 = false;
|
|
4131
|
+
function isPromoteReadyBrainActive() {
|
|
4132
|
+
return runtime5 !== void 0;
|
|
4127
4133
|
}
|
|
4128
|
-
function
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
if (!
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
const
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4134
|
+
function loadPromoteReadyFindings(opts) {
|
|
4135
|
+
const rt = runtime5;
|
|
4136
|
+
if (!rt) return [];
|
|
4137
|
+
const appId = opts.appId;
|
|
4138
|
+
if (!appId) return [];
|
|
4139
|
+
let snap = snapshots4.get(appId);
|
|
4140
|
+
if (!snap) {
|
|
4141
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4142
|
+
snapshots4.set(appId, snap);
|
|
4143
|
+
}
|
|
4144
|
+
const now = Date.now();
|
|
4145
|
+
const stale = snap.expiresAt <= now;
|
|
4146
|
+
if (stale && !snap.refreshing) {
|
|
4147
|
+
snap.refreshing = true;
|
|
4148
|
+
void asyncRefresh5(rt, appId);
|
|
4149
|
+
}
|
|
4150
|
+
let rows = snap.data;
|
|
4151
|
+
if (opts.archetype) {
|
|
4152
|
+
rows = rows.filter((f) => f.archetype === opts.archetype);
|
|
4153
|
+
}
|
|
4154
|
+
if (opts.family) {
|
|
4155
|
+
rows = rows.filter((f) => f.family === opts.family);
|
|
4156
|
+
}
|
|
4157
|
+
return rows;
|
|
4158
|
+
}
|
|
4159
|
+
var pendingRefreshes4 = /* @__PURE__ */ new Map();
|
|
4160
|
+
async function asyncRefresh5(rt, appId) {
|
|
4161
|
+
const promise = doRefresh5(rt, appId);
|
|
4162
|
+
pendingRefreshes4.set(appId, promise);
|
|
4163
|
+
try {
|
|
4164
|
+
await promise;
|
|
4165
|
+
} finally {
|
|
4166
|
+
if (pendingRefreshes4.get(appId) === promise) {
|
|
4167
|
+
pendingRefreshes4.delete(appId);
|
|
4148
4168
|
}
|
|
4149
|
-
|
|
4169
|
+
}
|
|
4150
4170
|
}
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4171
|
+
async function doRefresh5(rt, appId) {
|
|
4172
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
4173
|
+
let snap = snapshots4.get(appId);
|
|
4174
|
+
if (!snap) {
|
|
4175
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4176
|
+
snapshots4.set(appId, snap);
|
|
4177
|
+
}
|
|
4178
|
+
try {
|
|
4179
|
+
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
4180
|
+
if (!res.ok) {
|
|
4181
|
+
throw new Error(`promote-ready ${res.status}: ${res.statusText}`);
|
|
4182
|
+
}
|
|
4183
|
+
const body = await res.json();
|
|
4184
|
+
if (runtime5 !== rt) return;
|
|
4185
|
+
const rows = Array.isArray(body) ? mapRowsToFindings2(body) : [];
|
|
4186
|
+
snap.data = rows;
|
|
4187
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4188
|
+
snap.refreshing = false;
|
|
4189
|
+
} catch (err) {
|
|
4190
|
+
if (runtime5 !== rt) return;
|
|
4191
|
+
snap.refreshing = false;
|
|
4192
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4193
|
+
if (!warnedOnce4) {
|
|
4194
|
+
warnedOnce4 = true;
|
|
4195
|
+
(rt.onError ?? defaultOnError5)(err);
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4162
4198
|
}
|
|
4163
|
-
function
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4199
|
+
function defaultOnError5(err) {
|
|
4200
|
+
console.warn(
|
|
4201
|
+
"[kgauto] promote-ready fetch failed (using empty fallback):",
|
|
4202
|
+
err
|
|
4203
|
+
);
|
|
4204
|
+
}
|
|
4205
|
+
function resolveFetchImpl(injected) {
|
|
4206
|
+
return injected ?? ((...args) => globalThis.fetch(...args));
|
|
4207
|
+
}
|
|
4208
|
+
function normalizeEndpoint(endpoint) {
|
|
4209
|
+
return endpoint.replace(/\/+$/, "");
|
|
4210
|
+
}
|
|
4211
|
+
async function markPromoteReadyHandled(opts) {
|
|
4212
|
+
const {
|
|
4213
|
+
appId,
|
|
4214
|
+
archetype,
|
|
4215
|
+
family,
|
|
4216
|
+
resolution,
|
|
4217
|
+
resolutionNote,
|
|
4218
|
+
brainEndpoint,
|
|
4219
|
+
brainJwt,
|
|
4220
|
+
brainAnonKey,
|
|
4221
|
+
fetch: injectedFetch
|
|
4222
|
+
} = opts;
|
|
4223
|
+
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
4224
|
+
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
4225
|
+
if (!family) return { ok: false, reason: "family_required" };
|
|
4226
|
+
if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
|
|
4227
|
+
return { ok: false, reason: "resolution_invalid" };
|
|
4174
4228
|
}
|
|
4175
|
-
|
|
4176
|
-
|
|
4229
|
+
const doFetch = resolveFetchImpl(injectedFetch);
|
|
4230
|
+
const base = normalizeEndpoint(brainEndpoint);
|
|
4231
|
+
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`;
|
|
4232
|
+
const patchBody = {
|
|
4233
|
+
resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4234
|
+
resolution
|
|
4235
|
+
};
|
|
4236
|
+
if (resolutionNote !== void 0) {
|
|
4237
|
+
patchBody.resolution_note = resolutionNote;
|
|
4177
4238
|
}
|
|
4178
|
-
|
|
4179
|
-
|
|
4239
|
+
let res;
|
|
4240
|
+
try {
|
|
4241
|
+
res = await doFetch(url, {
|
|
4242
|
+
method: "PATCH",
|
|
4243
|
+
headers: {
|
|
4244
|
+
Authorization: `Bearer ${brainJwt}`,
|
|
4245
|
+
apikey: brainAnonKey,
|
|
4246
|
+
"Content-Type": "application/json",
|
|
4247
|
+
Accept: "application/json",
|
|
4248
|
+
Prefer: "return=minimal"
|
|
4249
|
+
},
|
|
4250
|
+
body: JSON.stringify(patchBody)
|
|
4251
|
+
});
|
|
4252
|
+
} catch (err) {
|
|
4253
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4254
|
+
return { ok: false, reason: `network_error:${msg}` };
|
|
4180
4255
|
}
|
|
4181
|
-
if (
|
|
4182
|
-
|
|
4183
|
-
...advisorRulePromoteReady({
|
|
4184
|
-
appId: ir.appId,
|
|
4185
|
-
archetype: ir.intent.archetype,
|
|
4186
|
-
resolvedPrimary: profile.id
|
|
4187
|
-
})
|
|
4188
|
-
);
|
|
4189
|
-
out.push(...advisorRuleConsumerOnStaleModel(ir));
|
|
4256
|
+
if (res.status === 401 || res.status === 403) {
|
|
4257
|
+
return { ok: false, reason: "brain_auth_misconfig" };
|
|
4190
4258
|
}
|
|
4191
|
-
if (
|
|
4192
|
-
|
|
4193
|
-
...advisorRuleCrossFamilyFit({
|
|
4194
|
-
archetype: ir.intent.archetype,
|
|
4195
|
-
resolvedPrimary: profile.id
|
|
4196
|
-
})
|
|
4197
|
-
);
|
|
4259
|
+
if (res.status >= 500) {
|
|
4260
|
+
return { ok: false, reason: "brain_unavailable" };
|
|
4198
4261
|
}
|
|
4199
|
-
|
|
4200
|
-
}
|
|
4201
|
-
function translatorClearedToolCallCliff(phase2) {
|
|
4202
|
-
const rewrites = phase2?.sectionRewritesApplied;
|
|
4203
|
-
if (!rewrites || rewrites.length === 0) return false;
|
|
4204
|
-
for (const rw of rewrites) {
|
|
4205
|
-
if (rw.kind === "tool_call_contract") return true;
|
|
4262
|
+
if (!res.ok) {
|
|
4263
|
+
return { ok: false, reason: `patch_failed:${res.status}` };
|
|
4206
4264
|
}
|
|
4207
|
-
return
|
|
4265
|
+
return { ok: true };
|
|
4208
4266
|
}
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4267
|
+
|
|
4268
|
+
// src/advisor-rules/promote-ready.ts
|
|
4269
|
+
var PROMOTE_READY_THRESHOLDS = {
|
|
4270
|
+
minPassRate: 0.8,
|
|
4271
|
+
minAvgScore: 4
|
|
4272
|
+
};
|
|
4273
|
+
function shouldFirePromoteReady(finding, resolvedPrimary) {
|
|
4274
|
+
if (finding.currentModel !== resolvedPrimary) return false;
|
|
4275
|
+
if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
|
|
4276
|
+
if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
|
|
4277
|
+
return true;
|
|
4278
|
+
}
|
|
4279
|
+
function deriveFamilyLocal(modelId) {
|
|
4280
|
+
if (modelId.startsWith("claude-opus-")) return "claude-opus";
|
|
4281
|
+
if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
|
|
4282
|
+
if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
|
|
4283
|
+
if (/^gemini-.*-flash-lite/.test(modelId)) return "gemini-flash-lite";
|
|
4284
|
+
if (/^gemini-.*-flash/.test(modelId)) return "gemini-flash";
|
|
4285
|
+
if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
|
|
4286
|
+
if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
|
|
4287
|
+
if (modelId.startsWith("deepseek-")) return "deepseek-chat";
|
|
4288
|
+
if (modelId.startsWith("gpt-")) return "openai-gpt";
|
|
4289
|
+
return null;
|
|
4290
|
+
}
|
|
4291
|
+
function advisorRulePromoteReady(ctx) {
|
|
4292
|
+
if (!isPromoteReadyBrainActive()) return [];
|
|
4293
|
+
if (!ctx.appId) return [];
|
|
4294
|
+
if (!ctx.resolvedPrimary) return [];
|
|
4295
|
+
const family = deriveFamilyLocal(ctx.resolvedPrimary);
|
|
4296
|
+
if (!family) return [];
|
|
4297
|
+
const findings = loadPromoteReadyFindings({
|
|
4298
|
+
appId: ctx.appId,
|
|
4299
|
+
archetype: ctx.archetype,
|
|
4300
|
+
family
|
|
4301
|
+
});
|
|
4302
|
+
if (findings.length === 0) return [];
|
|
4303
|
+
const qualifying = findings.filter(
|
|
4304
|
+
(f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
|
|
4305
|
+
);
|
|
4306
|
+
if (qualifying.length === 0) return [];
|
|
4307
|
+
qualifying.sort((a, b) => {
|
|
4308
|
+
if (a.judgeAvgScore !== b.judgeAvgScore) {
|
|
4309
|
+
return b.judgeAvgScore - a.judgeAvgScore;
|
|
4310
|
+
}
|
|
4311
|
+
return b.judgePassRate - a.judgePassRate;
|
|
4312
|
+
});
|
|
4313
|
+
const top = qualifying[0];
|
|
4314
|
+
const pctPass = Math.round(top.judgePassRate * 100);
|
|
4315
|
+
const score = top.judgeAvgScore.toFixed(2);
|
|
4316
|
+
let costClause = "";
|
|
4317
|
+
if (top.costDeltaPct !== null) {
|
|
4318
|
+
const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
|
|
4319
|
+
const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
|
|
4320
|
+
costClause = `, cost ${magnitude}% ${sign}`;
|
|
4321
|
+
}
|
|
4322
|
+
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.`;
|
|
4215
4323
|
return [
|
|
4216
4324
|
{
|
|
4217
|
-
level: "
|
|
4218
|
-
code: "
|
|
4219
|
-
message
|
|
4220
|
-
suggestion:
|
|
4325
|
+
level: "info",
|
|
4326
|
+
code: "promote-ready",
|
|
4327
|
+
message,
|
|
4328
|
+
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.`,
|
|
4329
|
+
// alpha.36 architectural field — not a no-ai-needed case.
|
|
4330
|
+
recommendedArchitecture: void 0,
|
|
4221
4331
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4222
4332
|
}
|
|
4223
4333
|
];
|
|
4224
4334
|
}
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
if (!only || only.text.length <= 1e3) return [];
|
|
4230
|
-
return [
|
|
4231
|
-
{
|
|
4232
|
-
level: "info",
|
|
4233
|
-
code: "single-chunk-system",
|
|
4234
|
-
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.`,
|
|
4235
|
-
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.",
|
|
4236
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4237
|
-
}
|
|
4238
|
-
];
|
|
4335
|
+
|
|
4336
|
+
// src/advisor-rules/consumer-on-stale-model.ts
|
|
4337
|
+
function isStaleStatus(v) {
|
|
4338
|
+
return v === "legacy" || v === "deprecated";
|
|
4239
4339
|
}
|
|
4240
|
-
function
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
"
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
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.`,
|
|
4257
|
-
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.",
|
|
4258
|
-
docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
|
|
4340
|
+
function asString(v) {
|
|
4341
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
4342
|
+
}
|
|
4343
|
+
function mapRowsToFindings3(rows) {
|
|
4344
|
+
const out = [];
|
|
4345
|
+
for (const raw of rows) {
|
|
4346
|
+
if (!raw || typeof raw !== "object") continue;
|
|
4347
|
+
const r = raw;
|
|
4348
|
+
const archetype = asString(r.intent_archetype) ?? asString(r.applies_to_archetype);
|
|
4349
|
+
const staleModel = asString(r.stale_model) ?? asString(r.applies_to_model);
|
|
4350
|
+
const staleProvider = asString(r.stale_provider);
|
|
4351
|
+
const recommendedModel = asString(r.recommended_model);
|
|
4352
|
+
const family = asString(r.family);
|
|
4353
|
+
const message = asString(r.message);
|
|
4354
|
+
if (!archetype || !staleModel || !recommendedModel || !family || !message) {
|
|
4355
|
+
continue;
|
|
4259
4356
|
}
|
|
4260
|
-
|
|
4357
|
+
if (!isStaleStatus(r.stale_status)) continue;
|
|
4358
|
+
const row = {
|
|
4359
|
+
archetype,
|
|
4360
|
+
staleModel,
|
|
4361
|
+
staleProvider: staleProvider ?? "unknown",
|
|
4362
|
+
staleStatus: r.stale_status,
|
|
4363
|
+
recommendedModel,
|
|
4364
|
+
family,
|
|
4365
|
+
message
|
|
4366
|
+
};
|
|
4367
|
+
const suggestion = asString(r.suggestion);
|
|
4368
|
+
if (suggestion) row.suggestion = suggestion;
|
|
4369
|
+
if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
|
|
4370
|
+
row.observationCount = r.observation_count;
|
|
4371
|
+
}
|
|
4372
|
+
out.push(row);
|
|
4373
|
+
}
|
|
4374
|
+
return out;
|
|
4261
4375
|
}
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4376
|
+
var snapshots5 = /* @__PURE__ */ new Map();
|
|
4377
|
+
var runtime6;
|
|
4378
|
+
var warnedOnce5 = false;
|
|
4379
|
+
var pendingRefreshes5 = /* @__PURE__ */ new Map();
|
|
4380
|
+
function isStaleModelFindingsBrainActive() {
|
|
4381
|
+
return runtime6 !== void 0;
|
|
4382
|
+
}
|
|
4383
|
+
function getStaleModelFindings(opts) {
|
|
4384
|
+
const rt = runtime6;
|
|
4385
|
+
if (!rt) return [];
|
|
4386
|
+
const appId = opts.appId;
|
|
4387
|
+
if (!appId) return [];
|
|
4388
|
+
let snap = snapshots5.get(appId);
|
|
4389
|
+
if (!snap) {
|
|
4390
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4391
|
+
snapshots5.set(appId, snap);
|
|
4267
4392
|
}
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4393
|
+
const now = Date.now();
|
|
4394
|
+
const stale = snap.expiresAt <= now;
|
|
4395
|
+
if (stale && !snap.refreshing) {
|
|
4396
|
+
snap.refreshing = true;
|
|
4397
|
+
void asyncRefresh6(rt, appId);
|
|
4398
|
+
}
|
|
4399
|
+
if (opts.archetype) {
|
|
4400
|
+
return snap.data.filter((f) => f.archetype === opts.archetype);
|
|
4401
|
+
}
|
|
4402
|
+
return snap.data;
|
|
4277
4403
|
}
|
|
4278
|
-
function
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
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.`,
|
|
4287
|
-
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.",
|
|
4288
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
|
|
4404
|
+
async function asyncRefresh6(rt, appId) {
|
|
4405
|
+
const promise = doRefresh6(rt, appId);
|
|
4406
|
+
pendingRefreshes5.set(appId, promise);
|
|
4407
|
+
try {
|
|
4408
|
+
await promise;
|
|
4409
|
+
} finally {
|
|
4410
|
+
if (pendingRefreshes5.get(appId) === promise) {
|
|
4411
|
+
pendingRefreshes5.delete(appId);
|
|
4289
4412
|
}
|
|
4290
|
-
|
|
4413
|
+
}
|
|
4291
4414
|
}
|
|
4292
|
-
function
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
if (!chosenHasRoomToGrow) return [];
|
|
4299
|
-
let bestAlt = null;
|
|
4300
|
-
for (const altId of phase2.fallbackChain) {
|
|
4301
|
-
const altProfile = phase2.profileResolver(altId);
|
|
4302
|
-
if (!altProfile) continue;
|
|
4303
|
-
if (altProfile.id === profile.id) continue;
|
|
4304
|
-
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
4305
|
-
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
4306
|
-
if (altScore.score < chosenScore.score) continue;
|
|
4307
|
-
if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
|
|
4308
|
-
if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
|
|
4309
|
-
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
4310
|
-
}
|
|
4415
|
+
async function doRefresh6(rt, appId) {
|
|
4416
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
4417
|
+
let snap = snapshots5.get(appId);
|
|
4418
|
+
if (!snap) {
|
|
4419
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4420
|
+
snapshots5.set(appId, snap);
|
|
4311
4421
|
}
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
|
|
4317
|
-
return [
|
|
4318
|
-
{
|
|
4319
|
-
level: "warn",
|
|
4320
|
-
code: "cost-mismatched-archetype",
|
|
4321
|
-
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.`,
|
|
4322
|
-
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.`,
|
|
4323
|
-
recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
|
|
4324
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4422
|
+
try {
|
|
4423
|
+
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
4424
|
+
if (!res.ok) {
|
|
4425
|
+
throw new Error(`stale-model findings ${res.status}: ${res.statusText}`);
|
|
4325
4426
|
}
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
recommendationType: "prompt-fix",
|
|
4340
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4427
|
+
const body = await res.json();
|
|
4428
|
+
if (runtime6 !== rt) return;
|
|
4429
|
+
const rows = Array.isArray(body) ? mapRowsToFindings3(body) : [];
|
|
4430
|
+
snap.data = rows;
|
|
4431
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4432
|
+
snap.refreshing = false;
|
|
4433
|
+
} catch (err) {
|
|
4434
|
+
if (runtime6 !== rt) return;
|
|
4435
|
+
snap.refreshing = false;
|
|
4436
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4437
|
+
if (!warnedOnce5) {
|
|
4438
|
+
warnedOnce5 = true;
|
|
4439
|
+
(rt.onError ?? defaultOnError6)(err);
|
|
4341
4440
|
}
|
|
4342
|
-
|
|
4441
|
+
}
|
|
4343
4442
|
}
|
|
4344
|
-
function
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
if (
|
|
4361
|
-
|
|
4443
|
+
function defaultOnError6(err) {
|
|
4444
|
+
console.warn(
|
|
4445
|
+
"[kgauto] stale-model findings fetch failed (using empty fallback):",
|
|
4446
|
+
err
|
|
4447
|
+
);
|
|
4448
|
+
}
|
|
4449
|
+
var CONSUMER_ON_STALE_MODEL_RULE_CODE = "consumer-on-stale-model";
|
|
4450
|
+
function advisorRuleConsumerOnStaleModel(ir) {
|
|
4451
|
+
if (!isStaleModelFindingsBrainActive()) return [];
|
|
4452
|
+
if (!ir.appId) return [];
|
|
4453
|
+
const findings = getStaleModelFindings({
|
|
4454
|
+
appId: ir.appId,
|
|
4455
|
+
archetype: ir.intent.archetype
|
|
4456
|
+
});
|
|
4457
|
+
if (findings.length === 0) return [];
|
|
4458
|
+
const ranked = [...findings].sort((a, b) => {
|
|
4459
|
+
if (a.staleStatus !== b.staleStatus) {
|
|
4460
|
+
return a.staleStatus === "deprecated" ? -1 : 1;
|
|
4362
4461
|
}
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
const
|
|
4462
|
+
return a.staleModel.localeCompare(b.staleModel);
|
|
4463
|
+
});
|
|
4464
|
+
const top = ranked[0];
|
|
4465
|
+
const extraCount = findings.length - 1;
|
|
4466
|
+
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
4366
4467
|
return [
|
|
4367
4468
|
{
|
|
4368
4469
|
level: "warn",
|
|
4369
|
-
code:
|
|
4370
|
-
message:
|
|
4371
|
-
suggestion: `
|
|
4372
|
-
recommendationType: "
|
|
4470
|
+
code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
|
|
4471
|
+
message: `${top.message}${extraNote}`,
|
|
4472
|
+
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}.`,
|
|
4473
|
+
recommendationType: "model-swap",
|
|
4373
4474
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4374
4475
|
}
|
|
4375
4476
|
];
|
|
4376
4477
|
}
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4478
|
+
|
|
4479
|
+
// src/archetype-fits.ts
|
|
4480
|
+
var ARCHETYPE_FAMILY_FITS = Object.freeze([
|
|
4481
|
+
{
|
|
4482
|
+
archetype: "plan",
|
|
4483
|
+
betterFitFamily: "deepseek-reasoner",
|
|
4484
|
+
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).",
|
|
4485
|
+
costGuidance: "substantially cheaper at current pricing (deepseek-v4-pro promo: ~7-17x cheaper than sonnet)"
|
|
4486
|
+
},
|
|
4487
|
+
{
|
|
4488
|
+
archetype: "critique",
|
|
4489
|
+
betterFitFamily: "deepseek-reasoner",
|
|
4490
|
+
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.",
|
|
4491
|
+
costGuidance: "comparable or cheaper at current pricing"
|
|
4492
|
+
}
|
|
4493
|
+
]);
|
|
4494
|
+
function findBetterFit(archetype, currentFamily) {
|
|
4495
|
+
for (const fit of ARCHETYPE_FAMILY_FITS) {
|
|
4496
|
+
if (fit.archetype !== archetype) continue;
|
|
4497
|
+
if (fit.betterFitFamily === currentFamily) return null;
|
|
4498
|
+
return fit;
|
|
4395
4499
|
}
|
|
4500
|
+
return null;
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4503
|
+
// src/advisor-rules/cross-family-fit.ts
|
|
4504
|
+
function familyHasCurrentActiveModel(family) {
|
|
4505
|
+
for (const profile of allProfiles()) {
|
|
4506
|
+
const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
|
|
4507
|
+
if (profileFamily !== family) continue;
|
|
4508
|
+
if (profile.status !== "current") continue;
|
|
4509
|
+
if (profile.active === false) continue;
|
|
4510
|
+
return true;
|
|
4511
|
+
}
|
|
4512
|
+
return false;
|
|
4513
|
+
}
|
|
4514
|
+
function listCandidatesInFamily(family) {
|
|
4515
|
+
const candidates = [];
|
|
4516
|
+
for (const profile of allProfiles()) {
|
|
4517
|
+
const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
|
|
4518
|
+
if (profileFamily !== family) continue;
|
|
4519
|
+
if (profile.status !== "current") continue;
|
|
4520
|
+
if (profile.active === false) continue;
|
|
4521
|
+
candidates.push(profile.id);
|
|
4522
|
+
if (candidates.length >= 3) break;
|
|
4523
|
+
}
|
|
4524
|
+
return candidates;
|
|
4525
|
+
}
|
|
4526
|
+
function advisorRuleCrossFamilyFit(ctx) {
|
|
4527
|
+
if (!ctx.resolvedPrimary) return [];
|
|
4528
|
+
const currentFamily = deriveFamilyFromModelId(ctx.resolvedPrimary);
|
|
4529
|
+
if (!currentFamily) return [];
|
|
4530
|
+
const fit = findBetterFit(ctx.archetype, currentFamily);
|
|
4531
|
+
if (!fit) return [];
|
|
4532
|
+
if (!familyHasCurrentActiveModel(fit.betterFitFamily)) return [];
|
|
4533
|
+
const candidates = listCandidatesInFamily(fit.betterFitFamily);
|
|
4534
|
+
if (candidates.length === 0) return [];
|
|
4535
|
+
const candidateStr = candidates.join(", ");
|
|
4536
|
+
const message = `Your ${currentFamily} call on ${ctx.archetype} could shift to ${fit.betterFitFamily} \u2014 typically better quality + ${fit.costGuidance}. Suggested candidates: ${candidateStr}.`;
|
|
4396
4537
|
return [
|
|
4397
4538
|
{
|
|
4398
|
-
level: "
|
|
4399
|
-
code: "
|
|
4400
|
-
|
|
4401
|
-
|
|
4539
|
+
level: "info",
|
|
4540
|
+
code: "cross-family-fit-candidate",
|
|
4541
|
+
ownership: "consumer-actionable",
|
|
4542
|
+
message,
|
|
4543
|
+
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.`,
|
|
4402
4544
|
recommendationType: "model-swap",
|
|
4403
4545
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4404
4546
|
}
|
|
4405
4547
|
];
|
|
4406
4548
|
}
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4549
|
+
|
|
4550
|
+
// src/advisor.ts
|
|
4551
|
+
var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
|
|
4552
|
+
var TIER_DOWN_COST_RATIO = 0.5;
|
|
4553
|
+
var COST_MISMATCHED_CHOSEN_SCORE_CEILING = 7;
|
|
4554
|
+
var PRODUCER_OWNED_RULE_CODES = Object.freeze(
|
|
4555
|
+
/* @__PURE__ */ new Set(["model-stale-evidence", "promote-ready"])
|
|
4556
|
+
);
|
|
4557
|
+
function deriveOwnership(code, selfDeclared) {
|
|
4558
|
+
if (selfDeclared) return selfDeclared;
|
|
4559
|
+
return PRODUCER_OWNED_RULE_CODES.has(code) ? "producer-owned" : "consumer-actionable";
|
|
4560
|
+
}
|
|
4561
|
+
function runAdvisor(ir, result, profile, policy, phase2) {
|
|
4562
|
+
const out = [];
|
|
4563
|
+
out.push(...detectCachingOff(ir, profile));
|
|
4564
|
+
out.push(...detectSingleChunkSystem(ir, profile));
|
|
4565
|
+
out.push(...detectToolBloat(ir, result));
|
|
4566
|
+
out.push(...detectHistoryUncached(ir, profile));
|
|
4567
|
+
out.push(...detectSingleModelArray(ir, policy));
|
|
4568
|
+
if (policy?.posture !== "locked") {
|
|
4569
|
+
out.push(...detectCostMismatchedArchetype(ir, profile, phase2));
|
|
4570
|
+
out.push(...detectModelStaleEvidence(ir, profile));
|
|
4571
|
+
out.push(...detectTierDown(ir, profile, phase2));
|
|
4572
|
+
}
|
|
4573
|
+
if (!translatorClearedToolCallCliff(phase2)) {
|
|
4574
|
+
out.push(...detectArchetypePerfFloorBreach(ir, profile));
|
|
4575
|
+
}
|
|
4576
|
+
if (policy?.posture !== "locked") {
|
|
4577
|
+
out.push(...detectStaleExclusionCandidate(ir));
|
|
4578
|
+
}
|
|
4579
|
+
if (policy?.posture !== "locked" && ir.appId) {
|
|
4580
|
+
out.push(
|
|
4581
|
+
...advisorRulePromoteReady({
|
|
4582
|
+
appId: ir.appId,
|
|
4583
|
+
archetype: ir.intent.archetype,
|
|
4584
|
+
resolvedPrimary: profile.id
|
|
4585
|
+
})
|
|
4586
|
+
);
|
|
4587
|
+
out.push(...advisorRuleConsumerOnStaleModel(ir));
|
|
4588
|
+
}
|
|
4589
|
+
if (policy?.posture !== "locked") {
|
|
4590
|
+
out.push(
|
|
4591
|
+
...advisorRuleCrossFamilyFit({
|
|
4592
|
+
archetype: ir.intent.archetype,
|
|
4593
|
+
resolvedPrimary: profile.id
|
|
4594
|
+
})
|
|
4595
|
+
);
|
|
4596
|
+
}
|
|
4597
|
+
return out;
|
|
4598
|
+
}
|
|
4599
|
+
function translatorClearedToolCallCliff(phase2) {
|
|
4600
|
+
const rewrites = phase2?.sectionRewritesApplied;
|
|
4601
|
+
if (!rewrites || rewrites.length === 0) return false;
|
|
4602
|
+
for (const rw of rewrites) {
|
|
4603
|
+
if (rw.kind === "tool_call_contract") return true;
|
|
4604
|
+
}
|
|
4605
|
+
return false;
|
|
4606
|
+
}
|
|
4607
|
+
function detectCachingOff(ir, profile) {
|
|
4608
|
+
if (profile.provider !== "anthropic") return [];
|
|
4609
|
+
const totalChars = ir.sections.reduce((s, sec) => s + sec.text.length, 0);
|
|
4610
|
+
if (totalChars < 2e3) return [];
|
|
4611
|
+
const anyCacheable = ir.sections.some((s) => s.cacheable === true);
|
|
4612
|
+
if (anyCacheable) return [];
|
|
4424
4613
|
return [
|
|
4425
4614
|
{
|
|
4426
|
-
level: "
|
|
4427
|
-
code: "
|
|
4428
|
-
message:
|
|
4429
|
-
suggestion:
|
|
4430
|
-
recommendationType: "tier-down",
|
|
4615
|
+
level: "warn",
|
|
4616
|
+
code: "caching-off-on-claude",
|
|
4617
|
+
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.`,
|
|
4618
|
+
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.",
|
|
4431
4619
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4432
4620
|
}
|
|
4433
4621
|
];
|
|
4434
4622
|
}
|
|
4435
|
-
function
|
|
4436
|
-
if (
|
|
4437
|
-
if (
|
|
4438
|
-
|
|
4623
|
+
function detectSingleChunkSystem(ir, profile) {
|
|
4624
|
+
if (profile.provider !== "anthropic") return [];
|
|
4625
|
+
if (ir.sections.length !== 1) return [];
|
|
4626
|
+
const only = ir.sections[0];
|
|
4627
|
+
if (!only || only.text.length <= 1e3) return [];
|
|
4628
|
+
return [
|
|
4629
|
+
{
|
|
4630
|
+
level: "info",
|
|
4631
|
+
code: "single-chunk-system",
|
|
4632
|
+
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.`,
|
|
4633
|
+
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.",
|
|
4634
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4635
|
+
}
|
|
4636
|
+
];
|
|
4439
4637
|
}
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4460
|
-
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
4461
|
-
4. Label each claim: mark it observed, inferred, or assumed.
|
|
4462
|
-
5. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
4463
|
-
var RULE_DISCIPLINE_GATES_V1_STRUCTURED = "discipline-gates-v1-structured";
|
|
4464
|
-
var DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4465
|
-
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4466
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4467
|
-
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
4468
|
-
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
4469
|
-
var DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4470
|
-
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4471
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4472
|
-
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
4473
|
-
var DISCIPLINE_ELIGIBLE_ARCHETYPES = /* @__PURE__ */ new Set([
|
|
4474
|
-
"hunt",
|
|
4475
|
-
"summarize",
|
|
4476
|
-
"plan",
|
|
4477
|
-
"critique",
|
|
4478
|
-
"judge"
|
|
4479
|
-
]);
|
|
4480
|
-
function matchRule(kind, profile, archetype, ctx) {
|
|
4481
|
-
if (kind === "discipline_contract") {
|
|
4482
|
-
if (!DISCIPLINE_ELIGIBLE_ARCHETYPES.has(archetype)) return null;
|
|
4483
|
-
if (ctx.outputMode !== "text") {
|
|
4484
|
-
return {
|
|
4485
|
-
id: RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
4486
|
-
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS : DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS
|
|
4487
|
-
};
|
|
4638
|
+
function detectToolBloat(ir, result) {
|
|
4639
|
+
const SHORT_OUTPUT = /* @__PURE__ */ new Set([
|
|
4640
|
+
"classify",
|
|
4641
|
+
"extract",
|
|
4642
|
+
"summarize",
|
|
4643
|
+
"transform",
|
|
4644
|
+
"critique"
|
|
4645
|
+
]);
|
|
4646
|
+
if (!ir.tools || ir.tools.length === 0) return [];
|
|
4647
|
+
const toolsKept = result.diagnostics.toolsKept;
|
|
4648
|
+
if (toolsKept <= 10) return [];
|
|
4649
|
+
if (!SHORT_OUTPUT.has(ir.intent.archetype)) return [];
|
|
4650
|
+
return [
|
|
4651
|
+
{
|
|
4652
|
+
level: "warn",
|
|
4653
|
+
code: "tool-bloat",
|
|
4654
|
+
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.`,
|
|
4655
|
+
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.",
|
|
4656
|
+
docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
|
|
4488
4657
|
}
|
|
4489
|
-
|
|
4490
|
-
id: RULE_DISCIPLINE_GATES_V1,
|
|
4491
|
-
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_WITH_TOOLS : DISCIPLINE_GATES_V1_NO_TOOLS
|
|
4492
|
-
};
|
|
4493
|
-
}
|
|
4494
|
-
if (kind === "tool_call_contract") {
|
|
4495
|
-
if (!profile.archetypePerf) return null;
|
|
4496
|
-
const archetypeScore = profile.archetypePerf[archetype];
|
|
4497
|
-
if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
|
|
4498
|
-
return null;
|
|
4499
|
-
}
|
|
4500
|
-
return {
|
|
4501
|
-
id: RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
4502
|
-
preamble: SEQUENTIAL_TOOL_PREAMBLE,
|
|
4503
|
-
wireOverrides: { parallelToolCalls: false }
|
|
4504
|
-
};
|
|
4505
|
-
}
|
|
4506
|
-
if (kind === "narration_contract") {
|
|
4507
|
-
if (profile.provider === "anthropic") {
|
|
4508
|
-
return {
|
|
4509
|
-
id: RULE_NARRATION_DRIFT_ANTHROPIC,
|
|
4510
|
-
preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
|
|
4511
|
-
};
|
|
4512
|
-
}
|
|
4513
|
-
if (profile.provider === "deepseek") {
|
|
4514
|
-
return {
|
|
4515
|
-
id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
|
|
4516
|
-
preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
|
|
4517
|
-
};
|
|
4518
|
-
}
|
|
4519
|
-
return null;
|
|
4520
|
-
}
|
|
4521
|
-
return null;
|
|
4522
|
-
}
|
|
4523
|
-
function applySectionRewrites(args) {
|
|
4524
|
-
const { ir, profile, archetype } = args;
|
|
4525
|
-
if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
|
|
4526
|
-
return { rewrittenIR: ir, rewrites: [] };
|
|
4527
|
-
}
|
|
4528
|
-
const outputMode = args.outputMode ?? resolveOutputMode({
|
|
4529
|
-
declared: ir.constraints?.outputMode,
|
|
4530
|
-
structuredOutput: ir.constraints?.structuredOutput,
|
|
4531
|
-
toolCount: ir.tools?.length ?? 0
|
|
4532
|
-
});
|
|
4533
|
-
const hasTools = (ir.tools?.length ?? 0) > 0;
|
|
4534
|
-
const ctx = { outputMode, hasTools };
|
|
4535
|
-
const rewrites = [];
|
|
4536
|
-
const newSections = ir.sections.map((section) => {
|
|
4537
|
-
if (!section.kind || section.kind === "arbitrary") return section;
|
|
4538
|
-
const rule = matchRule(section.kind, profile, archetype, ctx);
|
|
4539
|
-
if (!rule) return section;
|
|
4540
|
-
const originalText = section.text;
|
|
4541
|
-
const transformedText = `${rule.preamble}
|
|
4542
|
-
|
|
4543
|
-
${originalText}`;
|
|
4544
|
-
rewrites.push({
|
|
4545
|
-
sectionId: section.id,
|
|
4546
|
-
kind: section.kind,
|
|
4547
|
-
rule: rule.id,
|
|
4548
|
-
originalText,
|
|
4549
|
-
transformedText,
|
|
4550
|
-
...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
|
|
4551
|
-
});
|
|
4552
|
-
return { ...section, text: transformedText };
|
|
4553
|
-
});
|
|
4554
|
-
if (rewrites.length === 0) {
|
|
4555
|
-
return { rewrittenIR: ir, rewrites: [] };
|
|
4556
|
-
}
|
|
4557
|
-
const rewrittenIR = { ...ir, sections: newSections };
|
|
4558
|
-
return { rewrittenIR, rewrites };
|
|
4559
|
-
}
|
|
4560
|
-
|
|
4561
|
-
// src/promotions-brain.ts
|
|
4562
|
-
function isRawPromotionRow(x) {
|
|
4563
|
-
if (!x || typeof x !== "object") return false;
|
|
4564
|
-
const r = x;
|
|
4565
|
-
return (typeof r.id === "number" || typeof r.id === "string") && typeof r.intent_archetype === "string" && typeof r.promoted_model === "string" && typeof r.incumbent_model === "string";
|
|
4566
|
-
}
|
|
4567
|
-
function coerceId(v) {
|
|
4568
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
4569
|
-
if (typeof v === "string") {
|
|
4570
|
-
const n = Number(v);
|
|
4571
|
-
return Number.isFinite(n) ? n : null;
|
|
4572
|
-
}
|
|
4573
|
-
return null;
|
|
4574
|
-
}
|
|
4575
|
-
function mapRowsToPromotions(rows) {
|
|
4576
|
-
const out = [];
|
|
4577
|
-
for (const row of rows) {
|
|
4578
|
-
if (!isRawPromotionRow(row)) continue;
|
|
4579
|
-
const id = coerceId(row.id);
|
|
4580
|
-
if (id === null) continue;
|
|
4581
|
-
const mode = row.mode === "strategy" ? "strategy" : row.mode === "downswap" || row.mode === void 0 ? "downswap" : null;
|
|
4582
|
-
if (mode === null) continue;
|
|
4583
|
-
out.push({
|
|
4584
|
-
id,
|
|
4585
|
-
archetype: row.intent_archetype,
|
|
4586
|
-
mode,
|
|
4587
|
-
strategy: typeof row.strategy === "string" ? row.strategy : null,
|
|
4588
|
-
promotedModel: row.promoted_model,
|
|
4589
|
-
incumbentModel: row.incumbent_model,
|
|
4590
|
-
evalRunId: coerceId(row.eval_run_id ?? null),
|
|
4591
|
-
suppressQualityGate: row.suppress_quality_gate === true,
|
|
4592
|
-
promotedAt: typeof row.promoted_at === "string" ? row.promoted_at : ""
|
|
4593
|
-
});
|
|
4594
|
-
}
|
|
4595
|
-
return out;
|
|
4596
|
-
}
|
|
4597
|
-
var snapshots4 = /* @__PURE__ */ new Map();
|
|
4598
|
-
var runtime5;
|
|
4599
|
-
var warnedOnce4 = false;
|
|
4600
|
-
var DEFAULT_PROMOTIONS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/promotions";
|
|
4601
|
-
function isAutoPromoteEnabledFromEnv(envSource) {
|
|
4602
|
-
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
4603
|
-
const raw = (env.KGAUTO_AUTO_PROMOTE ?? "").trim().toLowerCase();
|
|
4604
|
-
return raw === "1" || raw === "true";
|
|
4605
|
-
}
|
|
4606
|
-
function configurePromotionsBrain(rt) {
|
|
4607
|
-
runtime5 = rt;
|
|
4608
|
-
snapshots4.clear();
|
|
4609
|
-
warnedOnce4 = false;
|
|
4610
|
-
}
|
|
4611
|
-
function isPromotionsBrainActive() {
|
|
4612
|
-
return runtime5 !== void 0;
|
|
4658
|
+
];
|
|
4613
4659
|
}
|
|
4614
|
-
function
|
|
4615
|
-
|
|
4616
|
-
if (!
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
let snap = snapshots4.get(appId);
|
|
4620
|
-
if (!snap) {
|
|
4621
|
-
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4622
|
-
snapshots4.set(appId, snap);
|
|
4623
|
-
}
|
|
4624
|
-
const now = Date.now();
|
|
4625
|
-
const stale = snap.expiresAt <= now;
|
|
4626
|
-
if (stale && !snap.refreshing) {
|
|
4627
|
-
snap.refreshing = true;
|
|
4628
|
-
void asyncRefresh5(rt, appId);
|
|
4660
|
+
function detectHistoryUncached(ir, profile) {
|
|
4661
|
+
if (profile.provider !== "anthropic") return [];
|
|
4662
|
+
if (!ir.history || ir.history.length < 2) return [];
|
|
4663
|
+
if (ir.historyCachePolicy && ir.historyCachePolicy.strategy !== "none") {
|
|
4664
|
+
return [];
|
|
4629
4665
|
}
|
|
4630
|
-
return
|
|
4631
|
-
|
|
4632
|
-
|
|
4666
|
+
return [
|
|
4667
|
+
{
|
|
4668
|
+
level: "warn",
|
|
4669
|
+
code: "history-uncached-on-claude",
|
|
4670
|
+
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.`,
|
|
4671
|
+
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.",
|
|
4672
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4673
|
+
}
|
|
4674
|
+
];
|
|
4633
4675
|
}
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4676
|
+
function detectSingleModelArray(ir, policy) {
|
|
4677
|
+
if (ir.models.length !== 1) return [];
|
|
4678
|
+
if (policy?.posture === "locked") return [];
|
|
4679
|
+
const only = ir.models[0];
|
|
4680
|
+
return [
|
|
4681
|
+
{
|
|
4682
|
+
level: "warn",
|
|
4683
|
+
code: "single-model-array",
|
|
4684
|
+
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.`,
|
|
4685
|
+
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.",
|
|
4686
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
|
|
4643
4687
|
}
|
|
4644
|
-
|
|
4688
|
+
];
|
|
4645
4689
|
}
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4690
|
+
function detectCostMismatchedArchetype(ir, profile, phase2) {
|
|
4691
|
+
if (!phase2 || phase2.fallbackChain.length === 0) return [];
|
|
4692
|
+
if (!phase2.profileResolver) return [];
|
|
4693
|
+
const archetype = ir.intent.archetype;
|
|
4694
|
+
const chosenScore = getArchetypePerfScore(profile.id, archetype);
|
|
4695
|
+
const chosenHasRoomToGrow = chosenScore.grounding === "judgment" || chosenScore.score < COST_MISMATCHED_CHOSEN_SCORE_CEILING;
|
|
4696
|
+
if (!chosenHasRoomToGrow) return [];
|
|
4697
|
+
let bestAlt = null;
|
|
4698
|
+
for (const altId of phase2.fallbackChain) {
|
|
4699
|
+
const altProfile = phase2.profileResolver(altId);
|
|
4700
|
+
if (!altProfile) continue;
|
|
4701
|
+
if (altProfile.id === profile.id) continue;
|
|
4702
|
+
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
4703
|
+
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
4704
|
+
if (altScore.score < chosenScore.score) continue;
|
|
4705
|
+
if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
|
|
4706
|
+
if (getMeasuredFailureVerdict({
|
|
4707
|
+
appId: ir.appId,
|
|
4708
|
+
archetype,
|
|
4709
|
+
model: altProfile.id
|
|
4710
|
+
})?.gated === true) {
|
|
4711
|
+
continue;
|
|
4657
4712
|
}
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4713
|
+
if (ir.constraints?.structuredOutput && effectiveConventions(altProfile).some(
|
|
4714
|
+
(c) => c.archetype === archetype && c.structuredOutputHint === "avoid"
|
|
4715
|
+
)) {
|
|
4716
|
+
continue;
|
|
4717
|
+
}
|
|
4718
|
+
if (getRecentRollback({
|
|
4719
|
+
appId: ir.appId,
|
|
4720
|
+
archetype,
|
|
4721
|
+
model: altProfile.id
|
|
4722
|
+
}) !== void 0) {
|
|
4723
|
+
continue;
|
|
4724
|
+
}
|
|
4725
|
+
if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
|
|
4726
|
+
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
4671
4727
|
}
|
|
4672
4728
|
}
|
|
4729
|
+
if (!bestAlt) return [];
|
|
4730
|
+
const tierDownWouldFire = bestAlt.score.grounding === "measured" && bestAlt.profile.costInputPer1m <= profile.costInputPer1m * TIER_DOWN_COST_RATIO;
|
|
4731
|
+
if (tierDownWouldFire) return [];
|
|
4732
|
+
const chosenGrounding = chosenScore.grounding === "judgment" ? `archetypePerf.${archetype}=judgment` : `archetypePerf.${archetype}=${chosenScore.score}`;
|
|
4733
|
+
const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
|
|
4734
|
+
return [
|
|
4735
|
+
{
|
|
4736
|
+
level: "warn",
|
|
4737
|
+
code: "cost-mismatched-archetype",
|
|
4738
|
+
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.`,
|
|
4739
|
+
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.`,
|
|
4740
|
+
recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
|
|
4741
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4742
|
+
}
|
|
4743
|
+
];
|
|
4673
4744
|
}
|
|
4674
|
-
function
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
);
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
}
|
|
4690
|
-
|
|
4691
|
-
// src/measured-failure-brain.ts
|
|
4692
|
-
function coerceCount(v) {
|
|
4693
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
4694
|
-
if (typeof v === "string") {
|
|
4695
|
-
const n = Number(v);
|
|
4696
|
-
return Number.isFinite(n) ? n : null;
|
|
4697
|
-
}
|
|
4698
|
-
return null;
|
|
4699
|
-
}
|
|
4700
|
-
function isRawFailureRow(x) {
|
|
4701
|
-
if (!x || typeof x !== "object") return false;
|
|
4702
|
-
const r = x;
|
|
4703
|
-
return typeof r.intent_archetype === "string" && typeof r.model === "string" && (typeof r.n === "number" || typeof r.n === "string");
|
|
4745
|
+
function detectModelStaleEvidence(ir, profile) {
|
|
4746
|
+
if (!isBrainQueryActiveFor("kgauto_archetype_perf")) return [];
|
|
4747
|
+
const archetype = ir.intent.archetype;
|
|
4748
|
+
const chosen = getArchetypePerfScore(profile.id, archetype);
|
|
4749
|
+
if (chosen.grounding !== "judgment") return [];
|
|
4750
|
+
return [
|
|
4751
|
+
{
|
|
4752
|
+
level: "info",
|
|
4753
|
+
code: "model-stale-evidence",
|
|
4754
|
+
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.`,
|
|
4755
|
+
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.)",
|
|
4756
|
+
recommendationType: "prompt-fix",
|
|
4757
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4758
|
+
}
|
|
4759
|
+
];
|
|
4704
4760
|
}
|
|
4705
|
-
function
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4761
|
+
function detectTierDown(ir, profile, phase2) {
|
|
4762
|
+
if (!phase2 || phase2.fallbackChain.length === 0) return [];
|
|
4763
|
+
if (!phase2.profileResolver) return [];
|
|
4764
|
+
const archetype = ir.intent.archetype;
|
|
4765
|
+
const chosenScore = getArchetypePerfScore(profile.id, archetype);
|
|
4766
|
+
const chosenCost = profile.costInputPer1m;
|
|
4767
|
+
let bestAlt = null;
|
|
4768
|
+
for (const altId of phase2.fallbackChain) {
|
|
4769
|
+
const altProfile = phase2.profileResolver(altId);
|
|
4770
|
+
if (!altProfile) continue;
|
|
4771
|
+
if (altProfile.id === profile.id) continue;
|
|
4772
|
+
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
4773
|
+
if (altScore.grounding !== "measured") continue;
|
|
4774
|
+
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
4775
|
+
if (altScore.score < chosenScore.score) continue;
|
|
4776
|
+
if (altProfile.costInputPer1m > chosenCost * TIER_DOWN_COST_RATIO) continue;
|
|
4777
|
+
if (!bestAlt || altProfile.costInputPer1m < bestAlt.profile.costInputPer1m || altProfile.costInputPer1m === bestAlt.profile.costInputPer1m && altScore.score > bestAlt.score.score) {
|
|
4778
|
+
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
4779
|
+
}
|
|
4718
4780
|
}
|
|
4719
|
-
return
|
|
4720
|
-
}
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
* Gate when we are 95% confident the model fails MORE OFTEN THAN IT
|
|
4732
|
-
* SUCCEEDS on this surface. Deliberately unarguable rather than tuned —
|
|
4733
|
-
* a model that probably fails the majority of the time has no business
|
|
4734
|
-
* leading a surface, whatever its declared scores say.
|
|
4735
|
-
*/
|
|
4736
|
-
lowerBoundThreshold: 0.5,
|
|
4737
|
-
/** 95% one-sided-ish confidence (standard two-sided z at α=0.05). */
|
|
4738
|
-
z: 1.96,
|
|
4739
|
-
/** Must match the view's window. Documented here for the advisory text. */
|
|
4740
|
-
windowDays: 28
|
|
4741
|
-
};
|
|
4742
|
-
function wilsonLowerBound(failures, n, z = MEASURED_FAILURE_CFG.z) {
|
|
4743
|
-
if (n <= 0) return 0;
|
|
4744
|
-
const p = failures / n;
|
|
4745
|
-
const z2 = z * z;
|
|
4746
|
-
const denom = 1 + z2 / n;
|
|
4747
|
-
const centre = p + z2 / (2 * n);
|
|
4748
|
-
const margin = z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
|
|
4749
|
-
const lower2 = (centre - margin) / denom;
|
|
4750
|
-
return lower2 < 0 ? 0 : lower2;
|
|
4751
|
-
}
|
|
4752
|
-
function judgeMeasuredFailure(row, cfg = MEASURED_FAILURE_CFG) {
|
|
4753
|
-
if (!row || row.n < cfg.minSample) return void 0;
|
|
4754
|
-
const lowerBound = wilsonLowerBound(row.nFail, row.n, cfg.z);
|
|
4755
|
-
return {
|
|
4756
|
-
gated: lowerBound > cfg.lowerBoundThreshold,
|
|
4757
|
-
rate: row.nFail / row.n,
|
|
4758
|
-
lowerBound,
|
|
4759
|
-
n: row.n,
|
|
4760
|
-
nFail: row.nFail
|
|
4761
|
-
};
|
|
4762
|
-
}
|
|
4763
|
-
var snapshots5 = /* @__PURE__ */ new Map();
|
|
4764
|
-
var runtime6;
|
|
4765
|
-
var warnedOnce5 = false;
|
|
4766
|
-
var DEFAULT_MEASURED_FAILURE_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/measured-failure";
|
|
4767
|
-
function isMeasuredFailureGateEnabledFromEnv(envSource) {
|
|
4768
|
-
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
4769
|
-
const raw = (env.KGAUTO_MEASURED_FAILURE_GATE ?? "").trim().toLowerCase();
|
|
4770
|
-
return !(raw === "0" || raw === "false");
|
|
4771
|
-
}
|
|
4772
|
-
function configureMeasuredFailureBrain(rt) {
|
|
4773
|
-
runtime6 = rt;
|
|
4774
|
-
snapshots5.clear();
|
|
4775
|
-
warnedOnce5 = false;
|
|
4776
|
-
}
|
|
4777
|
-
function isMeasuredFailureBrainActive() {
|
|
4778
|
-
return runtime6 !== void 0;
|
|
4781
|
+
if (!bestAlt) return [];
|
|
4782
|
+
const chosenDesc = chosenScore.grounding === "measured" ? `archetypePerf.${archetype}=${chosenScore.score} (measured, n=${chosenScore.n})` : `archetypePerf.${archetype}=${chosenScore.score} (${chosenScore.grounding})`;
|
|
4783
|
+
return [
|
|
4784
|
+
{
|
|
4785
|
+
level: "warn",
|
|
4786
|
+
code: "tier-down",
|
|
4787
|
+
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.`,
|
|
4788
|
+
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.`,
|
|
4789
|
+
recommendationType: "tier-down",
|
|
4790
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4791
|
+
}
|
|
4792
|
+
];
|
|
4779
4793
|
}
|
|
4780
|
-
function
|
|
4781
|
-
const
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4794
|
+
function detectArchetypePerfFloorBreach(ir, profile) {
|
|
4795
|
+
const compat = getModelCompatibility(profile.id, {
|
|
4796
|
+
archetype: ir.intent.archetype,
|
|
4797
|
+
toolOrchestration: ir.constraints?.toolOrchestration
|
|
4798
|
+
});
|
|
4799
|
+
if (compat.status === "compatible") return [];
|
|
4800
|
+
if (compat.status === "requires-adapter") {
|
|
4801
|
+
return [
|
|
4802
|
+
{
|
|
4803
|
+
level: "warn",
|
|
4804
|
+
code: "archetype-perf-floor-breach",
|
|
4805
|
+
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}`,
|
|
4806
|
+
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.`,
|
|
4807
|
+
recommendationType: "prompt-fix",
|
|
4808
|
+
suggestedAdaptation: compat.adapter,
|
|
4809
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4810
|
+
}
|
|
4811
|
+
];
|
|
4787
4812
|
}
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4813
|
+
return [
|
|
4814
|
+
{
|
|
4815
|
+
level: "critical",
|
|
4816
|
+
code: "archetype-perf-floor-breach",
|
|
4817
|
+
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}`,
|
|
4818
|
+
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.`,
|
|
4819
|
+
recommendationType: "model-swap",
|
|
4820
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4821
|
+
}
|
|
4822
|
+
];
|
|
4795
4823
|
}
|
|
4796
|
-
|
|
4797
|
-
if (!
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
}
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
}
|
|
4824
|
+
function detectStaleExclusionCandidate(ir) {
|
|
4825
|
+
if (!isExclusionFindingsBrainActive()) return [];
|
|
4826
|
+
if (!ir.appId) return [];
|
|
4827
|
+
const findings = getStaleExclusionFindings({
|
|
4828
|
+
appId: ir.appId,
|
|
4829
|
+
archetype: ir.intent.archetype
|
|
4830
|
+
});
|
|
4831
|
+
if (findings.length === 0) return [];
|
|
4832
|
+
const ranked = [...findings].sort((a, b) => {
|
|
4833
|
+
const sa = a.estimatedSavingsUsd30d ?? -Infinity;
|
|
4834
|
+
const sb = b.estimatedSavingsUsd30d ?? -Infinity;
|
|
4835
|
+
if (sa !== sb) return sb - sa;
|
|
4836
|
+
return confidenceRank(b.confidence) - confidenceRank(a.confidence);
|
|
4837
|
+
});
|
|
4838
|
+
const top = ranked[0];
|
|
4839
|
+
const extraCount = findings.length - 1;
|
|
4840
|
+
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more excluded model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
4841
|
+
return [
|
|
4842
|
+
{
|
|
4843
|
+
level: "info",
|
|
4844
|
+
code: "stale-exclusion-candidate",
|
|
4845
|
+
message: `${top.message}${extraNote}`,
|
|
4846
|
+
suggestion: top.suggestion,
|
|
4847
|
+
recommendationType: "tier-down",
|
|
4848
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4849
|
+
}
|
|
4850
|
+
];
|
|
4813
4851
|
}
|
|
4814
|
-
function
|
|
4815
|
-
|
|
4816
|
-
if (
|
|
4817
|
-
|
|
4818
|
-
if (!appId || !archetype || !model) return void 0;
|
|
4819
|
-
let snap = snapshots5.get(appId);
|
|
4820
|
-
if (!snap) {
|
|
4821
|
-
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4822
|
-
snapshots5.set(appId, snap);
|
|
4823
|
-
}
|
|
4824
|
-
const now = Date.now();
|
|
4825
|
-
if (snap.expiresAt <= now && !snap.refreshing) {
|
|
4826
|
-
snap.refreshing = true;
|
|
4827
|
-
void asyncRefresh6(rt, appId);
|
|
4828
|
-
}
|
|
4829
|
-
const row = snap.data.find(
|
|
4830
|
-
(r) => r.archetype === archetype && r.model === model
|
|
4831
|
-
);
|
|
4832
|
-
return judgeMeasuredFailure(row);
|
|
4852
|
+
function confidenceRank(c) {
|
|
4853
|
+
if (c === "high") return 3;
|
|
4854
|
+
if (c === "medium") return 2;
|
|
4855
|
+
return 1;
|
|
4833
4856
|
}
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4857
|
+
|
|
4858
|
+
// src/translator.ts
|
|
4859
|
+
var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
|
|
4860
|
+
var RULE_SEQUENTIAL_TOOL_CLIFF = "sequential-tool-cliff-below-floor";
|
|
4861
|
+
var RULE_NARRATION_DRIFT_ANTHROPIC = "narration-drift-anthropic";
|
|
4862
|
+
var RULE_NARRATION_THINKING_LEAK_DEEPSEEK = "narration-thinking-leak-deepseek";
|
|
4863
|
+
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.";
|
|
4864
|
+
var NARRATION_DRIFT_ANTHROPIC_PREAMBLE = "Output ONLY the requested content. Do not narrate your thought process. Each line \u2264 12 words.";
|
|
4865
|
+
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.";
|
|
4866
|
+
var RULE_DISCIPLINE_GATES_V1 = "discipline-gates-v1";
|
|
4867
|
+
var DISCIPLINE_GATES_V1_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4868
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4869
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4870
|
+
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
4871
|
+
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
4872
|
+
5. Label each claim: mark it observed, inferred, or assumed.
|
|
4873
|
+
6. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
4874
|
+
var DISCIPLINE_GATES_V1_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4875
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4876
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4877
|
+
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
4878
|
+
4. Label each claim: mark it observed, inferred, or assumed.
|
|
4879
|
+
5. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
4880
|
+
var RULE_DISCIPLINE_GATES_V1_STRUCTURED = "discipline-gates-v1-structured";
|
|
4881
|
+
var DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4882
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4883
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4884
|
+
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
4885
|
+
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
4886
|
+
var DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4887
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4888
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4889
|
+
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
4890
|
+
var DISCIPLINE_ELIGIBLE_ARCHETYPES = /* @__PURE__ */ new Set([
|
|
4891
|
+
"hunt",
|
|
4892
|
+
"summarize",
|
|
4893
|
+
"plan",
|
|
4894
|
+
"critique",
|
|
4895
|
+
"judge"
|
|
4896
|
+
]);
|
|
4897
|
+
function matchRule(kind, profile, archetype, ctx) {
|
|
4898
|
+
if (kind === "discipline_contract") {
|
|
4899
|
+
if (!DISCIPLINE_ELIGIBLE_ARCHETYPES.has(archetype)) return null;
|
|
4900
|
+
if (ctx.outputMode !== "text") {
|
|
4901
|
+
return {
|
|
4902
|
+
id: RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
4903
|
+
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS : DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS
|
|
4904
|
+
};
|
|
4843
4905
|
}
|
|
4906
|
+
return {
|
|
4907
|
+
id: RULE_DISCIPLINE_GATES_V1,
|
|
4908
|
+
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_WITH_TOOLS : DISCIPLINE_GATES_V1_NO_TOOLS
|
|
4909
|
+
};
|
|
4844
4910
|
}
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4911
|
+
if (kind === "tool_call_contract") {
|
|
4912
|
+
if (!profile.archetypePerf) return null;
|
|
4913
|
+
const archetypeScore = profile.archetypePerf[archetype];
|
|
4914
|
+
if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
|
|
4915
|
+
return null;
|
|
4916
|
+
}
|
|
4917
|
+
return {
|
|
4918
|
+
id: RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
4919
|
+
preamble: SEQUENTIAL_TOOL_PREAMBLE,
|
|
4920
|
+
wireOverrides: { parallelToolCalls: false }
|
|
4921
|
+
};
|
|
4852
4922
|
}
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4923
|
+
if (kind === "narration_contract") {
|
|
4924
|
+
if (profile.provider === "anthropic") {
|
|
4925
|
+
return {
|
|
4926
|
+
id: RULE_NARRATION_DRIFT_ANTHROPIC,
|
|
4927
|
+
preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
|
|
4928
|
+
};
|
|
4857
4929
|
}
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
} catch (err) {
|
|
4864
|
-
if (runtime6 !== rt) return;
|
|
4865
|
-
snap.refreshing = false;
|
|
4866
|
-
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4867
|
-
if (!warnedOnce5) {
|
|
4868
|
-
warnedOnce5 = true;
|
|
4869
|
-
(rt.onError ?? defaultOnError6)(err);
|
|
4930
|
+
if (profile.provider === "deepseek") {
|
|
4931
|
+
return {
|
|
4932
|
+
id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
|
|
4933
|
+
preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
|
|
4934
|
+
};
|
|
4870
4935
|
}
|
|
4936
|
+
return null;
|
|
4871
4937
|
}
|
|
4938
|
+
return null;
|
|
4872
4939
|
}
|
|
4873
|
-
function
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
}
|
|
4885
|
-
|
|
4886
|
-
const
|
|
4887
|
-
|
|
4940
|
+
function applySectionRewrites(args) {
|
|
4941
|
+
const { ir, profile, archetype } = args;
|
|
4942
|
+
if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
|
|
4943
|
+
return { rewrittenIR: ir, rewrites: [] };
|
|
4944
|
+
}
|
|
4945
|
+
const outputMode = args.outputMode ?? resolveOutputMode({
|
|
4946
|
+
declared: ir.constraints?.outputMode,
|
|
4947
|
+
structuredOutput: ir.constraints?.structuredOutput,
|
|
4948
|
+
toolCount: ir.tools?.length ?? 0
|
|
4949
|
+
});
|
|
4950
|
+
const hasTools = (ir.tools?.length ?? 0) > 0;
|
|
4951
|
+
const ctx = { outputMode, hasTools };
|
|
4952
|
+
const rewrites = [];
|
|
4953
|
+
const newSections = ir.sections.map((section) => {
|
|
4954
|
+
if (!section.kind || section.kind === "arbitrary") return section;
|
|
4955
|
+
const rule = matchRule(section.kind, profile, archetype, ctx);
|
|
4956
|
+
if (!rule) return section;
|
|
4957
|
+
const originalText = section.text;
|
|
4958
|
+
const transformedText = `${rule.preamble}
|
|
4959
|
+
|
|
4960
|
+
${originalText}`;
|
|
4961
|
+
rewrites.push({
|
|
4962
|
+
sectionId: section.id,
|
|
4963
|
+
kind: section.kind,
|
|
4964
|
+
rule: rule.id,
|
|
4965
|
+
originalText,
|
|
4966
|
+
transformedText,
|
|
4967
|
+
...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
|
|
4968
|
+
});
|
|
4969
|
+
return { ...section, text: transformedText };
|
|
4970
|
+
});
|
|
4971
|
+
if (rewrites.length === 0) {
|
|
4972
|
+
return { rewrittenIR: ir, rewrites: [] };
|
|
4973
|
+
}
|
|
4974
|
+
const rewrittenIR = { ...ir, sections: newSections };
|
|
4975
|
+
return { rewrittenIR, rewrites };
|
|
4888
4976
|
}
|
|
4889
4977
|
|
|
4890
4978
|
// src/compile.ts
|
|
@@ -6014,6 +6102,17 @@ function parseJsonLoose(raw) {
|
|
|
6014
6102
|
var FAILED = /* @__PURE__ */ Symbol("parse-failed");
|
|
6015
6103
|
|
|
6016
6104
|
// src/ir.ts
|
|
6105
|
+
function mutationId(m) {
|
|
6106
|
+
return typeof m === "string" ? m : m.id;
|
|
6107
|
+
}
|
|
6108
|
+
function hasMutation(list, idOrPrefix) {
|
|
6109
|
+
if (!Array.isArray(list)) return false;
|
|
6110
|
+
const prefix = idOrPrefix.endsWith("*") ? idOrPrefix.slice(0, -1) : void 0;
|
|
6111
|
+
return list.some((m) => {
|
|
6112
|
+
const id = mutationId(m);
|
|
6113
|
+
return prefix !== void 0 ? id.startsWith(prefix) : id === idOrPrefix;
|
|
6114
|
+
});
|
|
6115
|
+
}
|
|
6017
6116
|
var CallError = class extends Error {
|
|
6018
6117
|
attempts;
|
|
6019
6118
|
lastErrorCode;
|
|
@@ -6136,6 +6235,17 @@ function getReachabilityDiagnostic(opts = {}) {
|
|
|
6136
6235
|
}
|
|
6137
6236
|
return out;
|
|
6138
6237
|
}
|
|
6238
|
+
var BRAIN_READ_ENV_NAMES = {
|
|
6239
|
+
endpoint: "KGAUTO_V2_BRAIN_SUPABASE_URL",
|
|
6240
|
+
jwt: "KGAUTO_V2_BRAIN_JWT",
|
|
6241
|
+
anonKey: "KGAUTO_V2_BRAIN_ANON_KEY",
|
|
6242
|
+
/** Accepted fallbacks, in resolution order after the canonical name. */
|
|
6243
|
+
fallbacks: {
|
|
6244
|
+
endpoint: ["KGAUTO_V2_BRAIN_URL"],
|
|
6245
|
+
jwt: ["GLASSBOX_BRAIN_JWT"],
|
|
6246
|
+
anonKey: []
|
|
6247
|
+
}
|
|
6248
|
+
};
|
|
6139
6249
|
function readBrainReadEnv(envSource = defaultEnv()) {
|
|
6140
6250
|
const endpoint = readKeyValue(envSource.KGAUTO_V2_BRAIN_SUPABASE_URL) ?? readKeyValue(envSource.KGAUTO_V2_BRAIN_URL);
|
|
6141
6251
|
const jwt = readKeyValue(envSource.KGAUTO_V2_BRAIN_JWT) ?? readKeyValue(envSource.GLASSBOX_BRAIN_JWT);
|
|
@@ -6152,12 +6262,46 @@ function isSameModelRetryEnabledFromEnv(envSource = defaultEnv()) {
|
|
|
6152
6262
|
}
|
|
6153
6263
|
|
|
6154
6264
|
// src/streaming.ts
|
|
6265
|
+
function classifyThrownFetchError(err) {
|
|
6266
|
+
const name = err?.name;
|
|
6267
|
+
if (name === "TimeoutError") {
|
|
6268
|
+
return { errorType: "retryable", errorCode: "timeout", message: String(err) };
|
|
6269
|
+
}
|
|
6270
|
+
if (name === "AbortError") {
|
|
6271
|
+
return { errorType: "terminal", errorCode: "aborted", message: "aborted by caller signal" };
|
|
6272
|
+
}
|
|
6273
|
+
return { errorType: "retryable", errorCode: "network_error", message: String(err) };
|
|
6274
|
+
}
|
|
6275
|
+
function createStallGuard(external, stallTimeoutMs) {
|
|
6276
|
+
const ctl = new AbortController();
|
|
6277
|
+
if (external) {
|
|
6278
|
+
if (external.aborted) ctl.abort(external.reason);
|
|
6279
|
+
else external.addEventListener("abort", () => ctl.abort(external.reason), { once: true });
|
|
6280
|
+
}
|
|
6281
|
+
let timer;
|
|
6282
|
+
const arm = () => {
|
|
6283
|
+
if (!stallTimeoutMs || stallTimeoutMs <= 0) return;
|
|
6284
|
+
if (timer) clearTimeout(timer);
|
|
6285
|
+
timer = setTimeout(() => {
|
|
6286
|
+
ctl.abort(
|
|
6287
|
+
new DOMException(`stream stalled: no bytes for ${stallTimeoutMs}ms`, "TimeoutError")
|
|
6288
|
+
);
|
|
6289
|
+
}, stallTimeoutMs);
|
|
6290
|
+
};
|
|
6291
|
+
const clear = () => {
|
|
6292
|
+
if (timer) clearTimeout(timer);
|
|
6293
|
+
timer = void 0;
|
|
6294
|
+
};
|
|
6295
|
+
return { signal: ctl.signal, arm, clear };
|
|
6296
|
+
}
|
|
6155
6297
|
var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
|
|
6156
6298
|
async function streamAnthropic(request, apiKey, opts) {
|
|
6157
6299
|
const { provider: _provider, ...body } = request;
|
|
6158
6300
|
const fetchFn = opts.fetchImpl ?? fetch;
|
|
6301
|
+
const guard = createStallGuard(opts.signal, opts.stallTimeoutMs);
|
|
6159
6302
|
let res;
|
|
6160
6303
|
try {
|
|
6304
|
+
guard.arm();
|
|
6161
6305
|
res = await fetchFn(ANTHROPIC_URL, {
|
|
6162
6306
|
method: "POST",
|
|
6163
6307
|
headers: {
|
|
@@ -6165,12 +6309,16 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
6165
6309
|
"anthropic-version": "2023-06-01",
|
|
6166
6310
|
"content-type": "application/json"
|
|
6167
6311
|
},
|
|
6168
|
-
body: JSON.stringify({ ...body, stream: true })
|
|
6312
|
+
body: JSON.stringify({ ...body, stream: true }),
|
|
6313
|
+
signal: guard.signal
|
|
6169
6314
|
});
|
|
6170
6315
|
} catch (err) {
|
|
6171
|
-
|
|
6316
|
+
guard.clear();
|
|
6317
|
+
const c = classifyThrownFetchError(err);
|
|
6318
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6172
6319
|
}
|
|
6173
6320
|
if (!res.ok) {
|
|
6321
|
+
guard.clear();
|
|
6174
6322
|
const errBody = await res.json().catch(() => ({}));
|
|
6175
6323
|
return classifyHttpError(res.status, errBody);
|
|
6176
6324
|
}
|
|
@@ -6233,9 +6381,13 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
6233
6381
|
if (typeof p.usage?.output_tokens === "number") outputTokens = p.usage.output_tokens;
|
|
6234
6382
|
return;
|
|
6235
6383
|
}
|
|
6236
|
-
});
|
|
6384
|
+
}, guard.arm, guard.signal);
|
|
6237
6385
|
} catch (err) {
|
|
6238
|
-
|
|
6386
|
+
const c = classifyThrownFetchError(err);
|
|
6387
|
+
const code = c.errorCode === "network_error" ? "stream_interrupted" : c.errorCode;
|
|
6388
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: code, message: c.message, raw: null };
|
|
6389
|
+
} finally {
|
|
6390
|
+
guard.clear();
|
|
6239
6391
|
}
|
|
6240
6392
|
const toolCalls = Array.from(toolBlocks.values()).map((b) => ({
|
|
6241
6393
|
id: b.id,
|
|
@@ -6262,6 +6414,7 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
6262
6414
|
async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
6263
6415
|
const { provider: _provider, ...body } = request;
|
|
6264
6416
|
const fetchFn = opts.fetchImpl ?? fetch;
|
|
6417
|
+
const guard = createStallGuard(opts.signal, opts.stallTimeoutMs);
|
|
6265
6418
|
const reqBody = {
|
|
6266
6419
|
...body,
|
|
6267
6420
|
stream: true,
|
|
@@ -6272,18 +6425,23 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
6272
6425
|
};
|
|
6273
6426
|
let res;
|
|
6274
6427
|
try {
|
|
6428
|
+
guard.arm();
|
|
6275
6429
|
res = await fetchFn(url, {
|
|
6276
6430
|
method: "POST",
|
|
6277
6431
|
headers: {
|
|
6278
6432
|
authorization: `Bearer ${apiKey}`,
|
|
6279
6433
|
"content-type": "application/json"
|
|
6280
6434
|
},
|
|
6281
|
-
body: JSON.stringify(reqBody)
|
|
6435
|
+
body: JSON.stringify(reqBody),
|
|
6436
|
+
signal: guard.signal
|
|
6282
6437
|
});
|
|
6283
6438
|
} catch (err) {
|
|
6284
|
-
|
|
6439
|
+
guard.clear();
|
|
6440
|
+
const c = classifyThrownFetchError(err);
|
|
6441
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6285
6442
|
}
|
|
6286
6443
|
if (!res.ok) {
|
|
6444
|
+
guard.clear();
|
|
6287
6445
|
const errBody = await res.json().catch(() => ({}));
|
|
6288
6446
|
return classifyHttpError(res.status, errBody);
|
|
6289
6447
|
}
|
|
@@ -6336,9 +6494,13 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
6336
6494
|
const details = usage.prompt_tokens_details;
|
|
6337
6495
|
if (typeof details?.cached_tokens === "number") cachedTokens = details.cached_tokens;
|
|
6338
6496
|
}
|
|
6339
|
-
});
|
|
6497
|
+
}, guard.arm, guard.signal);
|
|
6340
6498
|
} catch (err) {
|
|
6341
|
-
|
|
6499
|
+
const c = classifyThrownFetchError(err);
|
|
6500
|
+
const code = c.errorCode === "network_error" ? "stream_interrupted" : c.errorCode;
|
|
6501
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: code, message: c.message, raw: null };
|
|
6502
|
+
} finally {
|
|
6503
|
+
guard.clear();
|
|
6342
6504
|
}
|
|
6343
6505
|
const toolCalls = Array.from(toolBuffers.values()).filter((b) => b.name.length > 0).map((b) => ({
|
|
6344
6506
|
id: b.id,
|
|
@@ -6361,15 +6523,28 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
6361
6523
|
};
|
|
6362
6524
|
return { ok: true, status: res.status, response };
|
|
6363
6525
|
}
|
|
6364
|
-
async function parseSSEStream(response, handler) {
|
|
6526
|
+
async function parseSSEStream(response, handler, onRead, signal) {
|
|
6365
6527
|
const body = response.body;
|
|
6366
6528
|
if (!body) throw new Error("Response has no body for SSE parse");
|
|
6367
6529
|
const reader = body.getReader();
|
|
6368
6530
|
const decoder = new TextDecoder("utf-8");
|
|
6369
6531
|
let buffer = "";
|
|
6532
|
+
const abortRace = signal ? new Promise((_resolve, reject) => {
|
|
6533
|
+
if (signal.aborted) return reject(signal.reason);
|
|
6534
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
6535
|
+
}) : void 0;
|
|
6370
6536
|
for (; ; ) {
|
|
6371
|
-
|
|
6537
|
+
let readResult;
|
|
6538
|
+
try {
|
|
6539
|
+
readResult = abortRace ? await Promise.race([reader.read(), abortRace]) : await reader.read();
|
|
6540
|
+
} catch (err) {
|
|
6541
|
+
void reader.cancel().catch(() => {
|
|
6542
|
+
});
|
|
6543
|
+
throw err;
|
|
6544
|
+
}
|
|
6545
|
+
const { value, done } = readResult;
|
|
6372
6546
|
if (done) break;
|
|
6547
|
+
onRead?.();
|
|
6373
6548
|
buffer += decoder.decode(value, { stream: true });
|
|
6374
6549
|
let sep;
|
|
6375
6550
|
while (sep = buffer.indexOf("\n\n"), sep !== -1) {
|
|
@@ -6426,8 +6601,33 @@ function isAuthSignatureBody(body, message) {
|
|
|
6426
6601
|
const m = message.toLowerCase();
|
|
6427
6602
|
return m.includes("api key not valid") || m.includes("invalid api key") || m.includes("invalid x-api-key") || m.includes("incorrect api key");
|
|
6428
6603
|
}
|
|
6604
|
+
function isBillingExhaustedBody(status, body, message) {
|
|
6605
|
+
if (status === 402) return true;
|
|
6606
|
+
const m = message.toLowerCase();
|
|
6607
|
+
if (m.includes("free_tier") || m.includes("free tier")) return false;
|
|
6608
|
+
if (m.includes("credit balance is too low")) return true;
|
|
6609
|
+
if (m.includes("insufficient balance")) return true;
|
|
6610
|
+
if (m.includes("billingnotenabled") || m.includes("billing not enabled")) return true;
|
|
6611
|
+
if (body && typeof body === "object") {
|
|
6612
|
+
const err = body.error;
|
|
6613
|
+
if (err && typeof err === "object") {
|
|
6614
|
+
const e = err;
|
|
6615
|
+
if (e.code === "insufficient_quota" || e.type === "insufficient_quota") return true;
|
|
6616
|
+
if (Array.isArray(e.details)) {
|
|
6617
|
+
for (const d of e.details) {
|
|
6618
|
+
if (d && typeof d === "object" && d.reason === "BILLING_DISABLED") {
|
|
6619
|
+
return true;
|
|
6620
|
+
}
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
return false;
|
|
6626
|
+
}
|
|
6429
6627
|
function classifyHttpError(status, body) {
|
|
6430
6628
|
const message = extractErrorMessage(body) ?? `HTTP ${status}`;
|
|
6629
|
+
if (isBillingExhaustedBody(status, body, message))
|
|
6630
|
+
return { ok: false, status, errorType: "terminal", errorCode: "billing_exhausted", message, raw: body };
|
|
6431
6631
|
if (status === 429)
|
|
6432
6632
|
return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
|
|
6433
6633
|
if (status === 408)
|
|
@@ -6455,9 +6655,6 @@ function extractErrorMessage(body) {
|
|
|
6455
6655
|
if (typeof b.message === "string") return b.message;
|
|
6456
6656
|
return void 0;
|
|
6457
6657
|
}
|
|
6458
|
-
function retryableError(status, code, message, raw) {
|
|
6459
|
-
return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
|
|
6460
|
-
}
|
|
6461
6658
|
|
|
6462
6659
|
// src/execute.ts
|
|
6463
6660
|
var ANTHROPIC_URL2 = "https://api.anthropic.com/v1/messages";
|
|
@@ -6502,7 +6699,9 @@ async function executeAnthropic(request, opts) {
|
|
|
6502
6699
|
if (opts.onChunk) {
|
|
6503
6700
|
return streamAnthropic(request, apiKey, {
|
|
6504
6701
|
onChunk: opts.onChunk,
|
|
6505
|
-
fetchImpl: opts.fetchImpl
|
|
6702
|
+
fetchImpl: opts.fetchImpl,
|
|
6703
|
+
signal: opts.signal,
|
|
6704
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
6506
6705
|
});
|
|
6507
6706
|
}
|
|
6508
6707
|
const { provider: _provider, ...body } = request;
|
|
@@ -6517,11 +6716,13 @@ async function executeAnthropic(request, opts) {
|
|
|
6517
6716
|
"anthropic-version": "2023-06-01",
|
|
6518
6717
|
"content-type": "application/json"
|
|
6519
6718
|
},
|
|
6520
|
-
body: JSON.stringify(body)
|
|
6719
|
+
body: JSON.stringify(body),
|
|
6720
|
+
signal: opts.signal
|
|
6521
6721
|
});
|
|
6522
6722
|
json = await res.json().catch(() => ({}));
|
|
6523
6723
|
} catch (err) {
|
|
6524
|
-
|
|
6724
|
+
const c = classifyThrownFetchError(err);
|
|
6725
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6525
6726
|
}
|
|
6526
6727
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
6527
6728
|
return { ok: true, status: res.status, response: normalizeAnthropic(json) };
|
|
@@ -6553,11 +6754,13 @@ async function executeGoogle(request, opts) {
|
|
|
6553
6754
|
res = await fetchFn(url, {
|
|
6554
6755
|
method: "POST",
|
|
6555
6756
|
headers: { "content-type": "application/json" },
|
|
6556
|
-
body: JSON.stringify(body)
|
|
6757
|
+
body: JSON.stringify(body),
|
|
6758
|
+
signal: opts.signal
|
|
6557
6759
|
});
|
|
6558
6760
|
json = await res.json().catch(() => ({}));
|
|
6559
6761
|
} catch (err) {
|
|
6560
|
-
|
|
6762
|
+
const c = classifyThrownFetchError(err);
|
|
6763
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6561
6764
|
}
|
|
6562
6765
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
6563
6766
|
return { ok: true, status: res.status, response: normalizeGoogle(json) };
|
|
@@ -6589,7 +6792,9 @@ async function executeOpenAI(request, opts) {
|
|
|
6589
6792
|
if (opts.onChunk) {
|
|
6590
6793
|
return streamOpenAILike(OPENAI_URL, request, apiKey, "openai", {
|
|
6591
6794
|
onChunk: opts.onChunk,
|
|
6592
|
-
fetchImpl: opts.fetchImpl
|
|
6795
|
+
fetchImpl: opts.fetchImpl,
|
|
6796
|
+
signal: opts.signal,
|
|
6797
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
6593
6798
|
});
|
|
6594
6799
|
}
|
|
6595
6800
|
const { provider: _provider, ...body } = request;
|
|
@@ -6600,11 +6805,13 @@ async function executeOpenAI(request, opts) {
|
|
|
6600
6805
|
res = await fetchFn(OPENAI_URL, {
|
|
6601
6806
|
method: "POST",
|
|
6602
6807
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
6603
|
-
body: JSON.stringify(body)
|
|
6808
|
+
body: JSON.stringify(body),
|
|
6809
|
+
signal: opts.signal
|
|
6604
6810
|
});
|
|
6605
6811
|
json = await res.json().catch(() => ({}));
|
|
6606
6812
|
} catch (err) {
|
|
6607
|
-
|
|
6813
|
+
const c = classifyThrownFetchError(err);
|
|
6814
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6608
6815
|
}
|
|
6609
6816
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
6610
6817
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -6617,7 +6824,9 @@ async function executeDeepSeek(request, opts) {
|
|
|
6617
6824
|
if (opts.onChunk) {
|
|
6618
6825
|
return streamOpenAILike(DEEPSEEK_URL, request, apiKey, "deepseek", {
|
|
6619
6826
|
onChunk: opts.onChunk,
|
|
6620
|
-
fetchImpl: opts.fetchImpl
|
|
6827
|
+
fetchImpl: opts.fetchImpl,
|
|
6828
|
+
signal: opts.signal,
|
|
6829
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
6621
6830
|
});
|
|
6622
6831
|
}
|
|
6623
6832
|
const { provider: _provider, ...body } = request;
|
|
@@ -6628,11 +6837,13 @@ async function executeDeepSeek(request, opts) {
|
|
|
6628
6837
|
res = await fetchFn(DEEPSEEK_URL, {
|
|
6629
6838
|
method: "POST",
|
|
6630
6839
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
6631
|
-
body: JSON.stringify(body)
|
|
6840
|
+
body: JSON.stringify(body),
|
|
6841
|
+
signal: opts.signal
|
|
6632
6842
|
});
|
|
6633
6843
|
json = await res.json().catch(() => ({}));
|
|
6634
6844
|
} catch (err) {
|
|
6635
|
-
|
|
6845
|
+
const c = classifyThrownFetchError(err);
|
|
6846
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6636
6847
|
}
|
|
6637
6848
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
6638
6849
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -6645,7 +6856,9 @@ async function executeOpenAICompatible(request, opts, spec) {
|
|
|
6645
6856
|
if (opts.onChunk) {
|
|
6646
6857
|
return streamOpenAILike(spec.url, request, apiKey, spec.provider, {
|
|
6647
6858
|
onChunk: opts.onChunk,
|
|
6648
|
-
fetchImpl: opts.fetchImpl
|
|
6859
|
+
fetchImpl: opts.fetchImpl,
|
|
6860
|
+
signal: opts.signal,
|
|
6861
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
6649
6862
|
});
|
|
6650
6863
|
}
|
|
6651
6864
|
const { provider: _provider, ...body } = request;
|
|
@@ -6656,11 +6869,13 @@ async function executeOpenAICompatible(request, opts, spec) {
|
|
|
6656
6869
|
res = await fetchFn(spec.url, {
|
|
6657
6870
|
method: "POST",
|
|
6658
6871
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
6659
|
-
body: JSON.stringify(body)
|
|
6872
|
+
body: JSON.stringify(body),
|
|
6873
|
+
signal: opts.signal
|
|
6660
6874
|
});
|
|
6661
6875
|
json = await res.json().catch(() => ({}));
|
|
6662
6876
|
} catch (err) {
|
|
6663
|
-
|
|
6877
|
+
const c = classifyThrownFetchError(err);
|
|
6878
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6664
6879
|
}
|
|
6665
6880
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
6666
6881
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -6691,6 +6906,9 @@ function applyOverrides(request, overrides) {
|
|
|
6691
6906
|
}
|
|
6692
6907
|
function classifyHttpError2(status, body) {
|
|
6693
6908
|
const message = extractErrorMessage2(body) ?? `HTTP ${status}`;
|
|
6909
|
+
if (isBillingExhaustedBody(status, body, message)) {
|
|
6910
|
+
return { ok: false, status, errorType: "terminal", errorCode: "billing_exhausted", message, raw: body };
|
|
6911
|
+
}
|
|
6694
6912
|
if (status === 429) {
|
|
6695
6913
|
return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
|
|
6696
6914
|
}
|
|
@@ -6727,9 +6945,6 @@ function extractErrorMessage2(body) {
|
|
|
6727
6945
|
function terminalError(status, code, message) {
|
|
6728
6946
|
return { ok: false, status, errorType: "terminal", errorCode: code, message, raw: null };
|
|
6729
6947
|
}
|
|
6730
|
-
function retryableError2(status, code, message, raw) {
|
|
6731
|
-
return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
|
|
6732
|
-
}
|
|
6733
6948
|
function tryParseJson2(s) {
|
|
6734
6949
|
if (typeof s !== "string" || s.length === 0) return void 0;
|
|
6735
6950
|
try {
|
|
@@ -7533,18 +7748,28 @@ async function call(ir, opts = {}) {
|
|
|
7533
7748
|
}
|
|
7534
7749
|
let activeCompile = initial;
|
|
7535
7750
|
let lastErr;
|
|
7536
|
-
const failedProviders = /* @__PURE__ */ new
|
|
7751
|
+
const failedProviders = /* @__PURE__ */ new Map();
|
|
7537
7752
|
const sameModelRetryEnabled = opts.sameModelRetry ?? isSameModelRetryEnabledFromEnv();
|
|
7538
7753
|
let retriedSameModel = false;
|
|
7539
7754
|
for (let i = 0; i < targetsToTry.length; i++) {
|
|
7540
7755
|
const targetModel = targetsToTry[i];
|
|
7756
|
+
if (opts.abortSignal?.aborted) {
|
|
7757
|
+
attempts.push({
|
|
7758
|
+
model: targetModel,
|
|
7759
|
+
status: "terminal",
|
|
7760
|
+
errorCode: "aborted",
|
|
7761
|
+
message: "Skipped \u2014 caller abortSignal fired before this attempt started"
|
|
7762
|
+
});
|
|
7763
|
+
break;
|
|
7764
|
+
}
|
|
7541
7765
|
const targetProfile = tryGetProfile(targetModel);
|
|
7542
|
-
|
|
7766
|
+
const providerFailReason = targetProfile ? failedProviders.get(targetProfile.provider) : void 0;
|
|
7767
|
+
if (targetProfile && providerFailReason && !opts.noFallback) {
|
|
7543
7768
|
attempts.push({
|
|
7544
7769
|
model: targetModel,
|
|
7545
7770
|
status: "terminal",
|
|
7546
|
-
errorCode:
|
|
7547
|
-
message: `Skipped \u2014 provider ${targetProfile.provider} returned 401/403 earlier in this call; same key inferred to fail`
|
|
7771
|
+
errorCode: `${providerFailReason}_inferred`,
|
|
7772
|
+
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`
|
|
7548
7773
|
});
|
|
7549
7774
|
continue;
|
|
7550
7775
|
}
|
|
@@ -7573,16 +7798,29 @@ async function call(ir, opts = {}) {
|
|
|
7573
7798
|
);
|
|
7574
7799
|
const targetSupportsStreaming = targetProfile?.streaming === true;
|
|
7575
7800
|
const streamingOnChunk = opts.onChunk && !opts.noStream && targetSupportsStreaming ? opts.onChunk : void 0;
|
|
7576
|
-
const
|
|
7801
|
+
const mkExecOpts = () => ({
|
|
7577
7802
|
apiKeys: opts.apiKeys,
|
|
7578
7803
|
fetchImpl: opts.fetchImpl,
|
|
7579
7804
|
providerOverrides: opts.providerOverrides,
|
|
7580
|
-
onChunk: streamingOnChunk
|
|
7581
|
-
|
|
7582
|
-
|
|
7805
|
+
onChunk: streamingOnChunk,
|
|
7806
|
+
signal: composeAttemptSignal(
|
|
7807
|
+
streamingOnChunk ? void 0 : opts.attemptTimeoutMs,
|
|
7808
|
+
opts.abortSignal
|
|
7809
|
+
),
|
|
7810
|
+
stallTimeoutMs: streamingOnChunk ? opts.attemptTimeoutMs : void 0
|
|
7811
|
+
});
|
|
7812
|
+
const exec = await execute(activeCompile.request, mkExecOpts());
|
|
7583
7813
|
let validated = exec.ok ? validateStructuredContract(exec, ir) : exec;
|
|
7584
7814
|
let servedByRetry = false;
|
|
7585
|
-
|
|
7815
|
+
let retrySuppressionNote;
|
|
7816
|
+
if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && getMeasuredFailureVerdict({
|
|
7817
|
+
appId: ir.appId,
|
|
7818
|
+
archetype: ir.intent.archetype,
|
|
7819
|
+
model: targetModel
|
|
7820
|
+
})?.gated === true) {
|
|
7821
|
+
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]";
|
|
7822
|
+
}
|
|
7823
|
+
if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && !retrySuppressionNote) {
|
|
7586
7824
|
retriedSameModel = true;
|
|
7587
7825
|
attempts.push({
|
|
7588
7826
|
model: targetModel,
|
|
@@ -7594,7 +7832,7 @@ async function call(ir, opts = {}) {
|
|
|
7594
7832
|
safeEmit(
|
|
7595
7833
|
() => emitExecuteAttempt(traceId, ir.appId, { model: targetModel, attemptIndex: i })
|
|
7596
7834
|
);
|
|
7597
|
-
const retryExec = await execute(retryRequest,
|
|
7835
|
+
const retryExec = await execute(retryRequest, mkExecOpts());
|
|
7598
7836
|
validated = retryExec.ok ? validateStructuredContract(retryExec, ir) : retryExec;
|
|
7599
7837
|
servedByRetry = true;
|
|
7600
7838
|
}
|
|
@@ -7721,20 +7959,23 @@ async function call(ir, opts = {}) {
|
|
|
7721
7959
|
model: targetModel,
|
|
7722
7960
|
status: validated.errorType,
|
|
7723
7961
|
errorCode: validated.errorCode,
|
|
7724
|
-
message: validated.message,
|
|
7962
|
+
message: retrySuppressionNote ? validated.message + retrySuppressionNote : validated.message,
|
|
7725
7963
|
...servedByRetry ? { sameModelRetry: true } : {}
|
|
7726
7964
|
});
|
|
7727
7965
|
lastErr = validated;
|
|
7728
7966
|
if (validated.errorType === "terminal" || opts.noFallback) {
|
|
7729
|
-
if (validated.errorCode === "auth" && !opts.noFallback && activeCompile.provider) {
|
|
7730
|
-
failedProviders.
|
|
7967
|
+
if ((validated.errorCode === "auth" || validated.errorCode === "billing_exhausted") && !opts.noFallback && activeCompile.provider) {
|
|
7968
|
+
failedProviders.set(
|
|
7969
|
+
activeCompile.provider,
|
|
7970
|
+
validated.errorCode === "billing_exhausted" ? "billing_exhausted" : "auth"
|
|
7971
|
+
);
|
|
7731
7972
|
continue;
|
|
7732
7973
|
}
|
|
7733
7974
|
break;
|
|
7734
7975
|
}
|
|
7735
7976
|
}
|
|
7736
7977
|
const lastAttempted = [...attempts].reverse().find(
|
|
7737
|
-
(a) => a.status !== "success" && a.errorCode !== "auth_inferred" && a.errorCode !== "compile_error"
|
|
7978
|
+
(a) => a.status !== "success" && a.errorCode !== "auth_inferred" && a.errorCode !== "billing_exhausted_inferred" && a.errorCode !== "compile_error"
|
|
7738
7979
|
);
|
|
7739
7980
|
const latencyMs = Date.now() - start;
|
|
7740
7981
|
await record({
|
|
@@ -7757,6 +7998,25 @@ async function call(ir, opts = {}) {
|
|
|
7757
7998
|
lastErr?.errorCode
|
|
7758
7999
|
);
|
|
7759
8000
|
}
|
|
8001
|
+
function composeAttemptSignal(attemptTimeoutMs, callerSignal) {
|
|
8002
|
+
const signals = [];
|
|
8003
|
+
if (callerSignal) signals.push(callerSignal);
|
|
8004
|
+
if (typeof attemptTimeoutMs === "number" && attemptTimeoutMs > 0) {
|
|
8005
|
+
signals.push(AbortSignal.timeout(attemptTimeoutMs));
|
|
8006
|
+
}
|
|
8007
|
+
if (signals.length === 0) return void 0;
|
|
8008
|
+
if (signals.length === 1) return signals[0];
|
|
8009
|
+
if (typeof AbortSignal.any === "function") return AbortSignal.any(signals);
|
|
8010
|
+
const ctl = new AbortController();
|
|
8011
|
+
for (const s of signals) {
|
|
8012
|
+
if (s.aborted) {
|
|
8013
|
+
ctl.abort(s.reason);
|
|
8014
|
+
break;
|
|
8015
|
+
}
|
|
8016
|
+
s.addEventListener("abort", () => ctl.abort(s.reason), { once: true });
|
|
8017
|
+
}
|
|
8018
|
+
return ctl.signal;
|
|
8019
|
+
}
|
|
7760
8020
|
function compileAndRegister(ir, opts) {
|
|
7761
8021
|
const result = compile(ir, {
|
|
7762
8022
|
policy: opts.policy,
|
|
@@ -8056,6 +8316,9 @@ function normalizeFallbackReason(attempts) {
|
|
|
8056
8316
|
}
|
|
8057
8317
|
if (code === "cost_cap_exceeded") return "cost_cap";
|
|
8058
8318
|
if (code === "auth" || code === "auth_inferred") return "provider_auth_failed";
|
|
8319
|
+
if (code === "billing_exhausted" || code === "billing_exhausted_inferred") {
|
|
8320
|
+
return "provider_billing_exhausted";
|
|
8321
|
+
}
|
|
8059
8322
|
return "provider_error";
|
|
8060
8323
|
}
|
|
8061
8324
|
function generateTraceId() {
|
|
@@ -8808,6 +9071,12 @@ async function runGoldenEval(opts) {
|
|
|
8808
9071
|
);
|
|
8809
9072
|
}
|
|
8810
9073
|
}
|
|
9074
|
+
if (wins + ties + losses === 0) {
|
|
9075
|
+
notes.push(
|
|
9076
|
+
"zero cases judged \u2014 run row persisted, evidence advisory deliberately NOT written (a verdict on n=0 is not evidence)"
|
|
9077
|
+
);
|
|
9078
|
+
return result;
|
|
9079
|
+
}
|
|
8811
9080
|
const latestRes = await fetchFn(
|
|
8812
9081
|
rest(
|
|
8813
9082
|
`compile_outcomes?app_id=eq.${encodeURIComponent(opts.appId)}&select=id&order=id.desc&limit=1`
|
|
@@ -9043,7 +9312,7 @@ function createBrainForwardRoutes(config) {
|
|
|
9043
9312
|
}
|
|
9044
9313
|
|
|
9045
9314
|
// src/version.ts
|
|
9046
|
-
var LIBRARY_VERSION = "2.0.0-alpha.
|
|
9315
|
+
var LIBRARY_VERSION = "2.0.0-alpha.78";
|
|
9047
9316
|
|
|
9048
9317
|
// src/key-health.ts
|
|
9049
9318
|
var JSON_HEADERS2 = { "Content-Type": "application/json" };
|
|
@@ -9400,19 +9669,32 @@ function resolveFetch(injected) {
|
|
|
9400
9669
|
function normalizeEndpoint2(endpoint) {
|
|
9401
9670
|
return endpoint.replace(/\/+$/, "");
|
|
9402
9671
|
}
|
|
9672
|
+
function resolveBrainReadTrio(opts) {
|
|
9673
|
+
const env = readBrainReadEnv();
|
|
9674
|
+
const brainEndpoint = opts.brainEndpoint ?? env.endpoint;
|
|
9675
|
+
const brainJwt = opts.brainJwt ?? env.jwt;
|
|
9676
|
+
const brainAnonKey = opts.brainAnonKey ?? env.anonKey;
|
|
9677
|
+
if (brainEndpoint && brainJwt && brainAnonKey) {
|
|
9678
|
+
return { ok: true, brainEndpoint, brainJwt, brainAnonKey };
|
|
9679
|
+
}
|
|
9680
|
+
const missingEnv = [];
|
|
9681
|
+
if (!brainEndpoint) missingEnv.push("KGAUTO_V2_BRAIN_SUPABASE_URL");
|
|
9682
|
+
if (!brainJwt) missingEnv.push("KGAUTO_V2_BRAIN_JWT");
|
|
9683
|
+
if (!brainAnonKey) missingEnv.push("KGAUTO_V2_BRAIN_ANON_KEY");
|
|
9684
|
+
return { ok: false, missingEnv };
|
|
9685
|
+
}
|
|
9403
9686
|
async function getActionableAdvisories(opts) {
|
|
9404
|
-
const {
|
|
9405
|
-
appId,
|
|
9406
|
-
severity,
|
|
9407
|
-
status,
|
|
9408
|
-
brainEndpoint,
|
|
9409
|
-
brainJwt,
|
|
9410
|
-
brainAnonKey,
|
|
9411
|
-
fetch: injectedFetch
|
|
9412
|
-
} = opts;
|
|
9687
|
+
const { appId, severity, status, fetch: injectedFetch } = opts;
|
|
9413
9688
|
if (!appId) {
|
|
9414
9689
|
throw new Error("getActionableAdvisories: appId is required");
|
|
9415
9690
|
}
|
|
9691
|
+
const trio = resolveBrainReadTrio(opts);
|
|
9692
|
+
if (!trio.ok) {
|
|
9693
|
+
throw new Error(
|
|
9694
|
+
`getActionableAdvisories: brain-read-not-configured \u2014 missing ${trio.missingEnv.join(", ")} (pass brainEndpoint/brainJwt/brainAnonKey explicitly or set the canonical env vars)`
|
|
9695
|
+
);
|
|
9696
|
+
}
|
|
9697
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
9416
9698
|
const doFetch = resolveFetch(injectedFetch);
|
|
9417
9699
|
const base = normalizeEndpoint2(brainEndpoint);
|
|
9418
9700
|
const qs = new URLSearchParams();
|
|
@@ -9465,17 +9747,15 @@ async function getActionableAdvisories(opts) {
|
|
|
9465
9747
|
return out;
|
|
9466
9748
|
}
|
|
9467
9749
|
async function markAdvisoryResolved(opts) {
|
|
9468
|
-
const {
|
|
9469
|
-
id,
|
|
9470
|
-
resolutionNote,
|
|
9471
|
-
brainEndpoint,
|
|
9472
|
-
brainJwt,
|
|
9473
|
-
brainAnonKey,
|
|
9474
|
-
fetch: injectedFetch
|
|
9475
|
-
} = opts;
|
|
9750
|
+
const { id, resolutionNote, fetch: injectedFetch } = opts;
|
|
9476
9751
|
if (!id) {
|
|
9477
9752
|
return { ok: false, reason: "id_required" };
|
|
9478
9753
|
}
|
|
9754
|
+
const trio = resolveBrainReadTrio(opts);
|
|
9755
|
+
if (!trio.ok) {
|
|
9756
|
+
return { ok: false, reason: `brain_read_not_configured:${trio.missingEnv.join(",")}` };
|
|
9757
|
+
}
|
|
9758
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
9479
9759
|
const doFetch = resolveFetch(injectedFetch);
|
|
9480
9760
|
const base = normalizeEndpoint2(brainEndpoint);
|
|
9481
9761
|
const lookupUrl = `${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=app_id,rule`;
|
|
@@ -9560,7 +9840,7 @@ async function markAdvisoryResolved(opts) {
|
|
|
9560
9840
|
}
|
|
9561
9841
|
}
|
|
9562
9842
|
if (outcomeIds.length === 0) {
|
|
9563
|
-
return { ok: true };
|
|
9843
|
+
return { ok: true, firingsResolved: 0, status: "unknown" };
|
|
9564
9844
|
}
|
|
9565
9845
|
const inList = outcomeIds.join(",");
|
|
9566
9846
|
const patchUrl = `${base}/rest/v1/compile_outcome_advisories?outcome_id=in.(${inList})&code=eq.${encodeURIComponent(code)}&resolved_at=is.null`;
|
|
@@ -9580,8 +9860,10 @@ async function markAdvisoryResolved(opts) {
|
|
|
9580
9860
|
apikey: brainAnonKey,
|
|
9581
9861
|
"Content-Type": "application/json",
|
|
9582
9862
|
Accept: "application/json",
|
|
9583
|
-
//
|
|
9584
|
-
|
|
9863
|
+
// alpha.78 — count what actually changed. An RLS-filtered PATCH
|
|
9864
|
+
// returns 2xx with zero rows; return=representation makes that
|
|
9865
|
+
// visible instead of success-shaped.
|
|
9866
|
+
Prefer: "return=representation"
|
|
9585
9867
|
},
|
|
9586
9868
|
body: JSON.stringify(patchBody)
|
|
9587
9869
|
});
|
|
@@ -9598,7 +9880,33 @@ async function markAdvisoryResolved(opts) {
|
|
|
9598
9880
|
if (!patchRes.ok) {
|
|
9599
9881
|
return { ok: false, reason: `patch_failed:${patchRes.status}` };
|
|
9600
9882
|
}
|
|
9601
|
-
|
|
9883
|
+
let patchedRows = [];
|
|
9884
|
+
try {
|
|
9885
|
+
patchedRows = await patchRes.json();
|
|
9886
|
+
} catch {
|
|
9887
|
+
}
|
|
9888
|
+
const firingsResolved = Array.isArray(patchedRows) ? patchedRows.length : 0;
|
|
9889
|
+
let status = "unknown";
|
|
9890
|
+
try {
|
|
9891
|
+
const statusRes = await doFetch(
|
|
9892
|
+
`${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=status`,
|
|
9893
|
+
{
|
|
9894
|
+
method: "GET",
|
|
9895
|
+
headers: {
|
|
9896
|
+
Authorization: `Bearer ${brainJwt}`,
|
|
9897
|
+
apikey: brainAnonKey,
|
|
9898
|
+
Accept: "application/json"
|
|
9899
|
+
}
|
|
9900
|
+
}
|
|
9901
|
+
);
|
|
9902
|
+
if (statusRes.ok) {
|
|
9903
|
+
const rows = await statusRes.json();
|
|
9904
|
+
const s = Array.isArray(rows) ? rows[0]?.status : void 0;
|
|
9905
|
+
if (s === "open" || s === "resolved") status = s;
|
|
9906
|
+
}
|
|
9907
|
+
} catch {
|
|
9908
|
+
}
|
|
9909
|
+
return { ok: true, firingsResolved, status };
|
|
9602
9910
|
}
|
|
9603
9911
|
async function markExclusionFindingHandled(opts) {
|
|
9604
9912
|
const {
|
|
@@ -9607,12 +9915,14 @@ async function markExclusionFindingHandled(opts) {
|
|
|
9607
9915
|
excludedModel,
|
|
9608
9916
|
resolution,
|
|
9609
9917
|
resolutionNote,
|
|
9610
|
-
brainEndpoint,
|
|
9611
|
-
brainJwt,
|
|
9612
|
-
brainAnonKey,
|
|
9613
9918
|
fetch: injectedFetch
|
|
9614
9919
|
} = opts;
|
|
9615
9920
|
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
9921
|
+
const trio = resolveBrainReadTrio(opts);
|
|
9922
|
+
if (!trio.ok) {
|
|
9923
|
+
return { ok: false, reason: `brain_read_not_configured:${trio.missingEnv.join(",")}` };
|
|
9924
|
+
}
|
|
9925
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
9616
9926
|
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
9617
9927
|
if (!excludedModel) {
|
|
9618
9928
|
return { ok: false, reason: "excluded_model_required" };
|
|
@@ -9879,6 +10189,7 @@ function compile2(ir, opts) {
|
|
|
9879
10189
|
ALL_ARCHETYPES,
|
|
9880
10190
|
ARCHETYPE_FAMILY_FITS,
|
|
9881
10191
|
ARCHETYPE_FLOOR_DEFAULT,
|
|
10192
|
+
BRAIN_READ_ENV_NAMES,
|
|
9882
10193
|
COACH_CFG,
|
|
9883
10194
|
CallError,
|
|
9884
10195
|
DECOMPOSITION_TEMPLATES,
|
|
@@ -9897,6 +10208,7 @@ function compile2(ir, opts) {
|
|
|
9897
10208
|
MEASURED_GROUNDING_MIN_N,
|
|
9898
10209
|
PRODUCER_OWNED_RULE_CODES,
|
|
9899
10210
|
PROVIDER_ENV_KEYS,
|
|
10211
|
+
ROLLBACK_SUPPRESSION_WINDOW_DAYS,
|
|
9900
10212
|
RULE_DISCIPLINE_GATES_V1,
|
|
9901
10213
|
RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
9902
10214
|
RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
@@ -9950,12 +10262,14 @@ function compile2(ir, opts) {
|
|
|
9950
10262
|
getPerAxisMetrics,
|
|
9951
10263
|
getProfile,
|
|
9952
10264
|
getReachabilityDiagnostic,
|
|
10265
|
+
getRecentRollback,
|
|
9953
10266
|
getRecommendedPrimary,
|
|
9954
10267
|
getSequentialStarterChain,
|
|
9955
10268
|
getSequentialStarterChainWithGrounding,
|
|
9956
10269
|
getStaleExclusionFindings,
|
|
9957
10270
|
getStarterChain,
|
|
9958
10271
|
getStarterChainWithGrounding,
|
|
10272
|
+
hasMutation,
|
|
9959
10273
|
hashShape,
|
|
9960
10274
|
isArchetype,
|
|
9961
10275
|
isAutoPromoteEnabledFromEnv,
|
|
@@ -9976,9 +10290,11 @@ function compile2(ir, opts) {
|
|
|
9976
10290
|
loadChainsFromBrain,
|
|
9977
10291
|
loadModelsFromBrain,
|
|
9978
10292
|
loadPricingFromBrain,
|
|
10293
|
+
mapMeasuredFailureRows,
|
|
9979
10294
|
markAdvisoryResolved,
|
|
9980
10295
|
markExclusionFindingHandled,
|
|
9981
10296
|
markPromoteReadyHandled,
|
|
10297
|
+
mutationId,
|
|
9982
10298
|
parseGoldenCaptureRate,
|
|
9983
10299
|
parseJudgeVerdict,
|
|
9984
10300
|
peekBrainDeadLetter,
|