modelrig 0.0.1 → 0.1.0
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/LICENSE +201 -0
- package/README.md +115 -8
- package/dist/adapters/anthropic.d.ts +46 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/anthropic.js +202 -0
- package/dist/adapters/deepseek.d.ts +41 -0
- package/dist/adapters/deepseek.d.ts.map +1 -0
- package/dist/adapters/deepseek.js +163 -0
- package/dist/adapters/gemini.d.ts +44 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +222 -0
- package/dist/adapters/grok.d.ts +44 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +168 -0
- package/dist/adapters/openai-compat.d.ts +74 -0
- package/dist/adapters/openai-compat.d.ts.map +1 -0
- package/dist/adapters/openai-compat.js +187 -0
- package/dist/adapters/openai.d.ts +49 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +222 -0
- package/dist/bakeoff/engine.d.ts +52 -0
- package/dist/bakeoff/engine.d.ts.map +1 -0
- package/dist/bakeoff/engine.js +163 -0
- package/dist/bakeoff/replay.d.ts +52 -0
- package/dist/bakeoff/replay.d.ts.map +1 -0
- package/dist/bakeoff/replay.js +94 -0
- package/dist/bakeoff/samples.d.ts +62 -0
- package/dist/bakeoff/samples.d.ts.map +1 -0
- package/dist/bakeoff/samples.js +128 -0
- package/dist/bakeoff/stats.d.ts +13 -0
- package/dist/bakeoff/stats.d.ts.map +1 -0
- package/dist/bakeoff/stats.js +48 -0
- package/dist/bakeoff/types.d.ts +37 -0
- package/dist/bakeoff/types.d.ts.map +1 -0
- package/dist/bakeoff/types.js +10 -0
- package/dist/cli.d.ts +27 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +572 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +148 -0
- package/dist/feedback/coverage.d.ts +32 -0
- package/dist/feedback/coverage.d.ts.map +1 -0
- package/dist/feedback/coverage.js +95 -0
- package/dist/feedback/feedback.d.ts +35 -0
- package/dist/feedback/feedback.d.ts.map +1 -0
- package/dist/feedback/feedback.js +39 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +448 -0
- package/dist/init/finder.d.ts +79 -0
- package/dist/init/finder.d.ts.map +1 -0
- package/dist/init/finder.js +297 -0
- package/dist/init/run.d.ts +99 -0
- package/dist/init/run.d.ts.map +1 -0
- package/dist/init/run.js +281 -0
- package/dist/init/signatures.d.ts +39 -0
- package/dist/init/signatures.d.ts.map +1 -0
- package/dist/init/signatures.js +116 -0
- package/dist/matrix/aggregate.d.ts +89 -0
- package/dist/matrix/aggregate.d.ts.map +1 -0
- package/dist/matrix/aggregate.js +186 -0
- package/dist/query/grammar.d.ts +35 -0
- package/dist/query/grammar.d.ts.map +1 -0
- package/dist/query/grammar.js +279 -0
- package/dist/query/run.d.ts +8 -0
- package/dist/query/run.d.ts.map +1 -0
- package/dist/query/run.js +21 -0
- package/dist/registry/build.d.ts +126 -0
- package/dist/registry/build.d.ts.map +1 -0
- package/dist/registry/build.js +192 -0
- package/dist/registry/call-notes.d.ts +11 -0
- package/dist/registry/call-notes.d.ts.map +1 -0
- package/dist/registry/call-notes.js +72 -0
- package/dist/registry/claims.d.ts +63 -0
- package/dist/registry/claims.d.ts.map +1 -0
- package/dist/registry/claims.js +107 -0
- package/dist/registry/facts.d.ts +32 -0
- package/dist/registry/facts.d.ts.map +1 -0
- package/dist/registry/facts.js +120 -0
- package/dist/registry/fingerprint.d.ts +20 -0
- package/dist/registry/fingerprint.d.ts.map +1 -0
- package/dist/registry/fingerprint.js +150 -0
- package/dist/registry/parity.d.ts +26 -0
- package/dist/registry/parity.d.ts.map +1 -0
- package/dist/registry/parity.js +41 -0
- package/dist/registry/pricing.d.ts +19 -0
- package/dist/registry/pricing.d.ts.map +1 -0
- package/dist/registry/pricing.js +69 -0
- package/dist/registry/rows.d.ts +16 -0
- package/dist/registry/rows.d.ts.map +1 -0
- package/dist/registry/rows.js +57 -0
- package/dist/registry/seed.d.ts +43 -0
- package/dist/registry/seed.d.ts.map +1 -0
- package/dist/registry/seed.js +97 -0
- package/dist/registry/select.d.ts +68 -0
- package/dist/registry/select.d.ts.map +1 -0
- package/dist/registry/select.js +141 -0
- package/dist/registry/staleness.d.ts +18 -0
- package/dist/registry/staleness.d.ts.map +1 -0
- package/dist/registry/staleness.js +39 -0
- package/dist/routes/draft.d.ts +83 -0
- package/dist/routes/draft.d.ts.map +1 -0
- package/dist/routes/draft.js +135 -0
- package/dist/routes/loader.d.ts +26 -0
- package/dist/routes/loader.d.ts.map +1 -0
- package/dist/routes/loader.js +436 -0
- package/dist/routes/manifest.d.ts +24 -0
- package/dist/routes/manifest.d.ts.map +1 -0
- package/dist/routes/manifest.js +118 -0
- package/dist/routes/serveability.d.ts +52 -0
- package/dist/routes/serveability.d.ts.map +1 -0
- package/dist/routes/serveability.js +73 -0
- package/dist/routes/template.d.ts +18 -0
- package/dist/routes/template.d.ts.map +1 -0
- package/dist/routes/template.js +94 -0
- package/dist/run/cost.d.ts +26 -0
- package/dist/run/cost.d.ts.map +1 -0
- package/dist/run/cost.js +41 -0
- package/dist/run/envelope.d.ts +21 -0
- package/dist/run/envelope.d.ts.map +1 -0
- package/dist/run/envelope.js +45 -0
- package/dist/run/grounding.d.ts +56 -0
- package/dist/run/grounding.d.ts.map +1 -0
- package/dist/run/grounding.js +89 -0
- package/dist/run/repair.d.ts +36 -0
- package/dist/run/repair.d.ts.map +1 -0
- package/dist/run/repair.js +61 -0
- package/dist/run/resolve.d.ts +26 -0
- package/dist/run/resolve.d.ts.map +1 -0
- package/dist/run/resolve.js +81 -0
- package/dist/run/retry.d.ts +19 -0
- package/dist/run/retry.d.ts.map +1 -0
- package/dist/run/retry.js +50 -0
- package/dist/run/run.d.ts +41 -0
- package/dist/run/run.d.ts.map +1 -0
- package/dist/run/run.js +0 -0
- package/dist/run/validate.d.ts +15 -0
- package/dist/run/validate.d.ts.map +1 -0
- package/dist/run/validate.js +56 -0
- package/dist/savings/emit.d.ts +25 -0
- package/dist/savings/emit.d.ts.map +1 -0
- package/dist/savings/emit.js +75 -0
- package/dist/savings/odometer.d.ts +32 -0
- package/dist/savings/odometer.d.ts.map +1 -0
- package/dist/savings/odometer.js +70 -0
- package/dist/savings/swap-execute.d.ts +44 -0
- package/dist/savings/swap-execute.d.ts.map +1 -0
- package/dist/savings/swap-execute.js +87 -0
- package/dist/savings/swaps-client.d.ts +41 -0
- package/dist/savings/swaps-client.d.ts.map +1 -0
- package/dist/savings/swaps-client.js +81 -0
- package/dist/telemetry/capture.d.ts +28 -0
- package/dist/telemetry/capture.d.ts.map +1 -0
- package/dist/telemetry/capture.js +74 -0
- package/dist/telemetry/exporter.d.ts +62 -0
- package/dist/telemetry/exporter.d.ts.map +1 -0
- package/dist/telemetry/exporter.js +233 -0
- package/dist/telemetry/ingest-protocol.d.ts +161 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -0
- package/dist/telemetry/ingest-protocol.js +163 -0
- package/dist/telemetry/sinks.d.ts +54 -0
- package/dist/telemetry/sinks.d.ts.map +1 -0
- package/dist/telemetry/sinks.js +89 -0
- package/dist/telemetry/store.d.ts +38 -0
- package/dist/telemetry/store.d.ts.map +1 -0
- package/dist/telemetry/store.js +332 -0
- package/dist/telemetry/supabase-exporter.d.ts +64 -0
- package/dist/telemetry/supabase-exporter.d.ts.map +1 -0
- package/dist/telemetry/supabase-exporter.js +211 -0
- package/dist/tenancy/api-key-format.d.ts +67 -0
- package/dist/tenancy/api-key-format.d.ts.map +1 -0
- package/dist/tenancy/api-key-format.js +91 -0
- package/dist/tenancy/constants.d.ts +23 -0
- package/dist/tenancy/constants.d.ts.map +1 -0
- package/dist/tenancy/constants.js +25 -0
- package/dist/tenancy/scrub.d.ts +47 -0
- package/dist/tenancy/scrub.d.ts.map +1 -0
- package/dist/tenancy/scrub.js +85 -0
- package/dist/types/failures-teaching.d.ts +56 -0
- package/dist/types/failures-teaching.d.ts.map +1 -0
- package/dist/types/failures-teaching.js +177 -0
- package/dist/types/failures.d.ts +27 -0
- package/dist/types/failures.d.ts.map +1 -0
- package/dist/types/failures.js +41 -0
- package/dist/types/provider.d.ts +79 -0
- package/dist/types/provider.d.ts.map +1 -0
- package/dist/types/provider.js +20 -0
- package/dist/types/route.d.ts +127 -0
- package/dist/types/route.d.ts.map +1 -0
- package/dist/types/route.js +6 -0
- package/dist/types/telemetry.d.ts +89 -0
- package/dist/types/telemetry.d.ts.map +1 -0
- package/dist/types/telemetry.js +6 -0
- package/dist/watch/budget.d.ts +19 -0
- package/dist/watch/budget.d.ts.map +1 -0
- package/dist/watch/budget.js +35 -0
- package/dist/watch/evaluate.d.ts +66 -0
- package/dist/watch/evaluate.d.ts.map +1 -0
- package/dist/watch/evaluate.js +94 -0
- package/dist/watch/proposals.d.ts +73 -0
- package/dist/watch/proposals.d.ts.map +1 -0
- package/dist/watch/proposals.js +169 -0
- package/dist/watch/run-record.d.ts +64 -0
- package/dist/watch/run-record.d.ts.map +1 -0
- package/dist/watch/run-record.js +86 -0
- package/dist/watch/run-reported.d.ts +20 -0
- package/dist/watch/run-reported.d.ts.map +1 -0
- package/dist/watch/run-reported.js +53 -0
- package/dist/watch/run.d.ts +40 -0
- package/dist/watch/run.d.ts.map +1 -0
- package/dist/watch/run.js +44 -0
- package/dist/watch/state-store.d.ts +48 -0
- package/dist/watch/state-store.d.ts.map +1 -0
- package/dist/watch/state-store.js +86 -0
- package/dist/watch/state.d.ts +21 -0
- package/dist/watch/state.d.ts.map +1 -0
- package/dist/watch/state.js +93 -0
- package/package.json +50 -14
- package/index.js +0 -3
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Model selection — the capability-first question, answered once.
|
|
4
|
+
*
|
|
5
|
+
* "Which models can serve strict JSON under $1/M?" is asked in three places:
|
|
6
|
+
* the console's query box, the MCP oracle's `query_registry` tool, and the
|
|
7
|
+
* docs. Phase 5 plan §10 names the risk by name — oracle answers drifting
|
|
8
|
+
* from console answers — so the grammar and the predicates live HERE and
|
|
9
|
+
* both surfaces call in. If this file changes, every surface changes.
|
|
10
|
+
*
|
|
11
|
+
* Dependency-free on purpose: the console imports this source directly (its
|
|
12
|
+
* tsconfig path alias) and must not drag SQLite or the adapters into a
|
|
13
|
+
* browser bundle.
|
|
14
|
+
*
|
|
15
|
+
* Bars are the registry's own, not new judgments: 0.9 conformance (the
|
|
16
|
+
* discrepancy detector's bar) and 0.5 grounding (facts.ts's GROUNDED_BAR).
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.PREDICATE_GLOSS = exports.ALL_CAPABILITY_PREDICATES = void 0;
|
|
20
|
+
exports.hasCapability = hasCapability;
|
|
21
|
+
exports.parseCapabilityQuery = parseCapabilityQuery;
|
|
22
|
+
exports.selectModels = selectModels;
|
|
23
|
+
exports.explainEmptySelection = explainEmptySelection;
|
|
24
|
+
exports.ALL_CAPABILITY_PREDICATES = [
|
|
25
|
+
"structured-native",
|
|
26
|
+
"structured-coached",
|
|
27
|
+
"grounding",
|
|
28
|
+
"caching",
|
|
29
|
+
];
|
|
30
|
+
/** Plain-language gloss per predicate — the explainability layer travels
|
|
31
|
+
* WITH the answer (console-explainability.md §1), it is not a separate doc. */
|
|
32
|
+
exports.PREDICATE_GLOSS = {
|
|
33
|
+
"structured-native": "the provider enforces your JSON Schema on its side — probed native rung rate above zero",
|
|
34
|
+
"structured-coached": "output matched the schema at least 90% of probed samples, whether or not the provider enforced it natively",
|
|
35
|
+
grounding: "at least half of probed answers cited real retrieved sources rather than answering from memory",
|
|
36
|
+
caching: "repeat calls actually reported cached input tokens — a measured discount, not a claim",
|
|
37
|
+
};
|
|
38
|
+
const CONFORMANCE_BAR = 0.9;
|
|
39
|
+
const GROUNDED_BAR = 0.5;
|
|
40
|
+
function hasCapability(row, cap) {
|
|
41
|
+
switch (cap) {
|
|
42
|
+
case "structured-native":
|
|
43
|
+
return (row.nativeRungRate ?? 0) > 0;
|
|
44
|
+
case "structured-coached":
|
|
45
|
+
return (row.conformRate ?? 0) >= CONFORMANCE_BAR;
|
|
46
|
+
case "grounding":
|
|
47
|
+
return (row.groundedRate ?? 0) >= GROUNDED_BAR;
|
|
48
|
+
case "caching":
|
|
49
|
+
return (row.cacheHitRate ?? 0) > 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Question scaffolding. A search box gets keywords; the MCP oracle gets a
|
|
53
|
+
* whole sentence ("which models can serve strict JSON under $1/M?"), and
|
|
54
|
+
* without this every one of those words would become a substring filter on
|
|
55
|
+
* the model key and match nothing. Dropping them is strictly safer than
|
|
56
|
+
* matching them: no model key contains "which". */
|
|
57
|
+
const STOP_WORDS = new Set([
|
|
58
|
+
"a", "about", "an", "and", "any", "are", "avail", "available", "be", "best", "can", "cheapest", "do",
|
|
59
|
+
"does", "find", "for", "give", "has", "have", "how", "i", "is", "it", "list", "me", "model",
|
|
60
|
+
"models", "need", "of", "or", "provider", "providers", "serve", "serves", "show", "some",
|
|
61
|
+
"support", "supports", "that", "the", "their", "them", "there", "these", "they", "this",
|
|
62
|
+
"those", "to", "use", "using", "want", "what", "whats", "which", "who", "will", "with",
|
|
63
|
+
]);
|
|
64
|
+
/** Punctuation a question drags in — stripped so "json?" is still "json". */
|
|
65
|
+
function normalizeWord(word) {
|
|
66
|
+
return word.replace(/^[^a-z0-9./_-]+|[^a-z0-9./_-]+$/g, "");
|
|
67
|
+
}
|
|
68
|
+
const CAP_WORDS = [
|
|
69
|
+
[["json", "strict", "structured", "schema"], "structured-native"],
|
|
70
|
+
[["grounded", "grounding", "search", "web"], "grounding"],
|
|
71
|
+
[["cache", "caching", "cached"], "caching"],
|
|
72
|
+
];
|
|
73
|
+
/** The tiny grammar behind the query box and the oracle's free-text input:
|
|
74
|
+
* capability words + an "under $N/M" price cap; everything else is a
|
|
75
|
+
* substring match on the model key. Small on purpose — a question we cannot
|
|
76
|
+
* parse should fall through to text, not guess. */
|
|
77
|
+
function parseCapabilityQuery(query) {
|
|
78
|
+
const caps = [];
|
|
79
|
+
const text = [];
|
|
80
|
+
const priceMatch = query.match(/under\s+\$?(\d+(?:\.\d+)?)\s*\/?\s*m/i);
|
|
81
|
+
const maxInputUsdPerMtok = priceMatch ? Number(priceMatch[1]) : null;
|
|
82
|
+
const cleaned = query.replace(/under\s+\$?\d+(?:\.\d+)?\s*\/?\s*m\w*/i, "");
|
|
83
|
+
for (const raw of cleaned.toLowerCase().split(/\s+/)) {
|
|
84
|
+
const word = normalizeWord(raw);
|
|
85
|
+
if (word === "" || STOP_WORDS.has(word))
|
|
86
|
+
continue;
|
|
87
|
+
const match = CAP_WORDS.find(([words]) => words.includes(word));
|
|
88
|
+
if (match === undefined) {
|
|
89
|
+
text.push(word);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!caps.includes(match[1]))
|
|
93
|
+
caps.push(match[1]);
|
|
94
|
+
}
|
|
95
|
+
return { caps, maxInputUsdPerMtok, text };
|
|
96
|
+
}
|
|
97
|
+
function selectModels(rows, criteria = {}) {
|
|
98
|
+
const caps = criteria.caps ?? [];
|
|
99
|
+
const text = criteria.text ?? [];
|
|
100
|
+
let out = rows.filter((row) => {
|
|
101
|
+
if (criteria.provider != null && criteria.provider !== "" && row.provider !== criteria.provider) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
if (criteria.discrepanciesOnly === true && row.discrepancies === 0)
|
|
105
|
+
return false;
|
|
106
|
+
if (caps.some((cap) => !hasCapability(row, cap)))
|
|
107
|
+
return false;
|
|
108
|
+
if (criteria.maxInputUsdPerMtok != null) {
|
|
109
|
+
// A model with no published input price cannot be proven under a cap —
|
|
110
|
+
// absence is not "cheap enough".
|
|
111
|
+
if (row.inputUsdPerMtok === null || row.inputUsdPerMtok > criteria.maxInputUsdPerMtok) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (text.length > 0 && !text.every((w) => row.key.toLowerCase().includes(w)))
|
|
116
|
+
return false;
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
if (criteria.sortByEffectiveCost !== false) {
|
|
120
|
+
out = [...out].sort((a, b) => (a.effectiveUsdPer1k ?? Number.POSITIVE_INFINITY) -
|
|
121
|
+
(b.effectiveUsdPer1k ?? Number.POSITIVE_INFINITY));
|
|
122
|
+
}
|
|
123
|
+
if (criteria.limit !== undefined && criteria.limit > 0)
|
|
124
|
+
out = out.slice(0, criteria.limit);
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
/** Why a question found nothing — the difference between "no such model" and
|
|
128
|
+
* "we have not probed one yet" is the whole point of the demand meter. */
|
|
129
|
+
function explainEmptySelection(criteria) {
|
|
130
|
+
const parts = [];
|
|
131
|
+
for (const cap of criteria.caps ?? [])
|
|
132
|
+
parts.push(exports.PREDICATE_GLOSS[cap]);
|
|
133
|
+
if (criteria.maxInputUsdPerMtok != null) {
|
|
134
|
+
parts.push(`input price at or under $${criteria.maxInputUsdPerMtok}/M tokens`);
|
|
135
|
+
}
|
|
136
|
+
const asked = parts.length > 0 ? parts.join("; and ") : "the requested filters";
|
|
137
|
+
return (`No probed model in the registry satisfies ${asked}. ` +
|
|
138
|
+
"This means no model we have MEASURED qualifies — not that none exists. " +
|
|
139
|
+
"Unprobed models are absent from these answers by design; open a probe request " +
|
|
140
|
+
"if you need a specific model covered.");
|
|
141
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Probe-data freshness — addendum A2 (TrustedRouter pattern 3): never serve
|
|
3
|
+
* stale-as-fresh. The SLO is 7-day-on-change / monthly; without per-entry
|
|
4
|
+
* change-detection timestamps the enforceable bound is the monthly one, so
|
|
5
|
+
* staleness = newest probed as_of older than 31 days. Stale data stays
|
|
6
|
+
* visible — bannered and dated, never hidden.
|
|
7
|
+
*/
|
|
8
|
+
export declare const PROBE_FRESHNESS_SLO_DAYS = 31;
|
|
9
|
+
export interface ProbeFreshness {
|
|
10
|
+
readonly stale: boolean;
|
|
11
|
+
/** Newest probed as_of across the corpus — the banner's date. */
|
|
12
|
+
readonly newestAsOf: string | null;
|
|
13
|
+
readonly daysOld: number | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function probeFreshness(asOfDates: ReadonlyArray<string | null | undefined>, now?: Date): ProbeFreshness;
|
|
16
|
+
/** The banner sentence (brand guide §7: plain, dated, never hidden). */
|
|
17
|
+
export declare function stalenessSentence(freshness: ProbeFreshness): string;
|
|
18
|
+
//# sourceMappingURL=staleness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleness.d.ts","sourceRoot":"","sources":["../../src/registry/staleness.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,iEAAiE;IACjE,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,aAAa,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EACnD,GAAG,GAAE,IAAiB,GACrB,cAAc,CAgBhB;AAED,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,cAAc,GAAG,MAAM,CASnE"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Probe-data freshness — addendum A2 (TrustedRouter pattern 3): never serve
|
|
4
|
+
* stale-as-fresh. The SLO is 7-day-on-change / monthly; without per-entry
|
|
5
|
+
* change-detection timestamps the enforceable bound is the monthly one, so
|
|
6
|
+
* staleness = newest probed as_of older than 31 days. Stale data stays
|
|
7
|
+
* visible — bannered and dated, never hidden.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.PROBE_FRESHNESS_SLO_DAYS = void 0;
|
|
11
|
+
exports.probeFreshness = probeFreshness;
|
|
12
|
+
exports.stalenessSentence = stalenessSentence;
|
|
13
|
+
exports.PROBE_FRESHNESS_SLO_DAYS = 31;
|
|
14
|
+
function probeFreshness(asOfDates, now = new Date()) {
|
|
15
|
+
const parsed = asOfDates
|
|
16
|
+
.filter((d) => typeof d === "string" && d !== "")
|
|
17
|
+
.map((d) => new Date(d).getTime())
|
|
18
|
+
.filter((t) => Number.isFinite(t));
|
|
19
|
+
if (parsed.length === 0) {
|
|
20
|
+
// No probe data at all is its own kind of stale — banner with no date.
|
|
21
|
+
return { stale: true, newestAsOf: null, daysOld: null };
|
|
22
|
+
}
|
|
23
|
+
const newest = Math.max(...parsed);
|
|
24
|
+
const daysOld = Math.floor((now.getTime() - newest) / 86400000);
|
|
25
|
+
return {
|
|
26
|
+
stale: daysOld > exports.PROBE_FRESHNESS_SLO_DAYS,
|
|
27
|
+
newestAsOf: new Date(newest).toISOString(),
|
|
28
|
+
daysOld,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** The banner sentence (brand guide §7: plain, dated, never hidden). */
|
|
32
|
+
function stalenessSentence(freshness) {
|
|
33
|
+
if (freshness.newestAsOf === null) {
|
|
34
|
+
return "No probe data yet — nothing on this page has been verified.";
|
|
35
|
+
}
|
|
36
|
+
return (`This probe data exceeded its freshness window (${exports.PROBE_FRESHNESS_SLO_DAYS} days): ` +
|
|
37
|
+
`newest result ${freshness.newestAsOf.slice(0, 10)}, ${freshness.daysOld} days old. ` +
|
|
38
|
+
"The data below is still shown, clearly dated — a rerun is due.");
|
|
39
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-bundle drafting — the shared emitter behind BOTH `modelrig init`
|
|
3
|
+
* (WS2) and the oracle's `create_route` act tool (WS1).
|
|
4
|
+
*
|
|
5
|
+
* A route bundle is three files, not one: the YAML, its JSON Schema, and its
|
|
6
|
+
* system prompt. A "draft" that emitted only the YAML would not load, so
|
|
7
|
+
* this builds the whole set.
|
|
8
|
+
*
|
|
9
|
+
* PURE: it returns file contents and writes nothing. Every caller that
|
|
10
|
+
* touches the disk does so through `writeRouteDraft`, which refuses to
|
|
11
|
+
* overwrite — drafts are proposals for a human to read, never edits.
|
|
12
|
+
*
|
|
13
|
+
* The YAML is hand-emitted rather than serialized by the `yaml` package on
|
|
14
|
+
* purpose: drafts are read by humans first, and the comments explaining WHERE
|
|
15
|
+
* each candidate came from are the most valuable lines in the file.
|
|
16
|
+
*/
|
|
17
|
+
export interface DraftCandidate {
|
|
18
|
+
readonly provider: string;
|
|
19
|
+
readonly model: string;
|
|
20
|
+
/** Why this candidate is here — rendered as a trailing comment. */
|
|
21
|
+
readonly note?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface RouteDraftSpec {
|
|
24
|
+
/** Route handle, e.g. "research.growth_scenarios". */
|
|
25
|
+
readonly route: string;
|
|
26
|
+
readonly candidates: readonly DraftCandidate[];
|
|
27
|
+
/** Incumbent first is the caller's job; this records which one it was so
|
|
28
|
+
* the draft can say so out loud. */
|
|
29
|
+
readonly incumbent?: {
|
|
30
|
+
readonly provider: string;
|
|
31
|
+
readonly model: string;
|
|
32
|
+
} | null;
|
|
33
|
+
readonly systemPrompt: string;
|
|
34
|
+
readonly variables: readonly string[];
|
|
35
|
+
/** JSON Schema for the output; null drafts an unstructured route. */
|
|
36
|
+
readonly outputSchema: object | null;
|
|
37
|
+
readonly require?: readonly string[];
|
|
38
|
+
readonly prefer?: readonly string[];
|
|
39
|
+
readonly policy?: {
|
|
40
|
+
readonly timeoutMs?: number;
|
|
41
|
+
readonly tier?: string;
|
|
42
|
+
readonly json?: string;
|
|
43
|
+
readonly retries?: Readonly<Record<string, number>>;
|
|
44
|
+
};
|
|
45
|
+
readonly capture?: boolean;
|
|
46
|
+
/** Provenance lines rendered as a header comment — "found at src/x.ts:42".
|
|
47
|
+
* SD8: customer-zero provenance is labeled, never hardcoded behavior. */
|
|
48
|
+
readonly provenance?: readonly string[];
|
|
49
|
+
}
|
|
50
|
+
export interface DraftFile {
|
|
51
|
+
/** Path relative to the drafts directory. */
|
|
52
|
+
readonly path: string;
|
|
53
|
+
readonly contents: string;
|
|
54
|
+
}
|
|
55
|
+
export interface RouteDraft {
|
|
56
|
+
readonly route: string;
|
|
57
|
+
readonly files: readonly DraftFile[];
|
|
58
|
+
}
|
|
59
|
+
export declare function buildRouteDraft(spec: RouteDraftSpec): RouteDraft;
|
|
60
|
+
export interface WriteDraftResult {
|
|
61
|
+
readonly written: readonly string[];
|
|
62
|
+
/** Files that already existed and were left alone. */
|
|
63
|
+
readonly skipped: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Write a draft into `draftsDir`. Two rules, both deliberate:
|
|
67
|
+
* - It writes ONLY under the drafts directory. Nothing here can touch the
|
|
68
|
+
* caller's source, their live routes, or the control plane (spec §4:
|
|
69
|
+
* drafts-only is permanent this phase).
|
|
70
|
+
* - It never overwrites. A second run reports what it skipped instead of
|
|
71
|
+
* silently discarding edits a human made to a draft.
|
|
72
|
+
*/
|
|
73
|
+
export declare function writeRouteDraft(draft: RouteDraft, draftsDir: string, fs: {
|
|
74
|
+
existsSync: (p: string) => boolean;
|
|
75
|
+
mkdirSync: (p: string, o: {
|
|
76
|
+
recursive: boolean;
|
|
77
|
+
}) => void;
|
|
78
|
+
writeFileSync: (p: string, c: string, e: "utf8") => void;
|
|
79
|
+
}, path: {
|
|
80
|
+
join: (...parts: string[]) => string;
|
|
81
|
+
dirname: (p: string) => string;
|
|
82
|
+
}): WriteDraftResult;
|
|
83
|
+
//# sourceMappingURL=draft.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../../src/routes/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,CAAC;IAC/C;wCACoC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE;QAChB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;KACrD,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;6EACyE;IACzE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,SAAS;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;CACtC;AAmBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAgGhE;AAMD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE;IACF,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1D,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1D,EACD,IAAI,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,GAC7E,gBAAgB,CAclB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Route-bundle drafting — the shared emitter behind BOTH `modelrig init`
|
|
4
|
+
* (WS2) and the oracle's `create_route` act tool (WS1).
|
|
5
|
+
*
|
|
6
|
+
* A route bundle is three files, not one: the YAML, its JSON Schema, and its
|
|
7
|
+
* system prompt. A "draft" that emitted only the YAML would not load, so
|
|
8
|
+
* this builds the whole set.
|
|
9
|
+
*
|
|
10
|
+
* PURE: it returns file contents and writes nothing. Every caller that
|
|
11
|
+
* touches the disk does so through `writeRouteDraft`, which refuses to
|
|
12
|
+
* overwrite — drafts are proposals for a human to read, never edits.
|
|
13
|
+
*
|
|
14
|
+
* The YAML is hand-emitted rather than serialized by the `yaml` package on
|
|
15
|
+
* purpose: drafts are read by humans first, and the comments explaining WHERE
|
|
16
|
+
* each candidate came from are the most valuable lines in the file.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.buildRouteDraft = buildRouteDraft;
|
|
20
|
+
exports.writeRouteDraft = writeRouteDraft;
|
|
21
|
+
/** YAML string quoting: quote unless the value is unambiguously a plain
|
|
22
|
+
* scalar. Model ids carry dots, colons and dashes — the cases that bite. */
|
|
23
|
+
function yamlString(value) {
|
|
24
|
+
if (/^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(value))
|
|
25
|
+
return value;
|
|
26
|
+
return JSON.stringify(value);
|
|
27
|
+
}
|
|
28
|
+
function slug(route) {
|
|
29
|
+
return route.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
30
|
+
}
|
|
31
|
+
const DEFAULT_RETRIES = {
|
|
32
|
+
content_invalid: 2,
|
|
33
|
+
network: 4,
|
|
34
|
+
capacity_shed: 3,
|
|
35
|
+
};
|
|
36
|
+
function buildRouteDraft(spec) {
|
|
37
|
+
if (spec.route.trim() === "")
|
|
38
|
+
throw new Error("a route draft needs a route name");
|
|
39
|
+
if (spec.candidates.length === 0) {
|
|
40
|
+
throw new Error(`route draft "${spec.route}" has no candidates — a route with nothing allowed to serve it ` +
|
|
41
|
+
"cannot run. Name at least one provider/model.");
|
|
42
|
+
}
|
|
43
|
+
const name = slug(spec.route);
|
|
44
|
+
const files = [];
|
|
45
|
+
const schemaRef = spec.outputSchema === null ? null : `./schemas/${name}.schema.json`;
|
|
46
|
+
const promptRef = `./prompts/${name}.system.md`;
|
|
47
|
+
const lines = [
|
|
48
|
+
"# ModelRig route bundle — DRAFT. Nothing in your app calls this yet.",
|
|
49
|
+
"# Read it, edit it, move it up one directory into modelrig/routes/, then",
|
|
50
|
+
"# switch the call site to rig.run(\"" + spec.route + "\", …).",
|
|
51
|
+
];
|
|
52
|
+
if (spec.provenance !== undefined && spec.provenance.length > 0) {
|
|
53
|
+
lines.push("#");
|
|
54
|
+
for (const line of spec.provenance)
|
|
55
|
+
lines.push(`# ${line}`);
|
|
56
|
+
}
|
|
57
|
+
lines.push("#", "# Candidates are ordered incumbent-first: the model you already use leads,", "# so the first run reproduces today's behavior. The cheaper probed", "# alternatives below it are what a bake-off would put to the test.", "#", "# `prefer` is empty ON PURPOSE. Adding `cost` re-sorts these candidates by", "# price, which would hand the first run to the cheapest model rather than", "# to your incumbent — the opposite of a like-for-like migration. Prove a", "# cheaper model with a bake-off, then switch to it deliberately.", `route: ${yamlString(spec.route)}`, "version: 1");
|
|
58
|
+
if (schemaRef !== null)
|
|
59
|
+
lines.push(`schema: ${schemaRef}`);
|
|
60
|
+
lines.push("candidates:");
|
|
61
|
+
for (const candidate of spec.candidates) {
|
|
62
|
+
lines.push(` - provider: ${yamlString(candidate.provider)}`);
|
|
63
|
+
lines.push(` model: ${yamlString(candidate.model)}` +
|
|
64
|
+
(candidate.note !== undefined ? ` # ${candidate.note}` : ""));
|
|
65
|
+
}
|
|
66
|
+
const require = spec.require ?? (spec.outputSchema === null ? [] : ["schema_conformant"]);
|
|
67
|
+
if (require.length > 0) {
|
|
68
|
+
lines.push("require:");
|
|
69
|
+
for (const r of require)
|
|
70
|
+
lines.push(` - ${yamlString(r)}`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
lines.push("require: []");
|
|
74
|
+
}
|
|
75
|
+
// Default EMPTY, not [cost]: `prefer: [cost]` re-sorts candidates by price
|
|
76
|
+
// at resolve time, so the incumbent-first ordering above would be silently
|
|
77
|
+
// overridden and the first run would NOT reproduce today's behavior.
|
|
78
|
+
lines.push(`prefer: [${(spec.prefer ?? []).map(yamlString).join(", ")}]`);
|
|
79
|
+
lines.push("prompt:");
|
|
80
|
+
lines.push(` system: ${promptRef}`);
|
|
81
|
+
lines.push(` variables: [${spec.variables.map(yamlString).join(", ")}]` +
|
|
82
|
+
(spec.variables.length === 0 ? " # no {{variables}} detected — the prompt is static" : ""));
|
|
83
|
+
const policy = spec.policy ?? {};
|
|
84
|
+
const retries = policy.retries ?? DEFAULT_RETRIES;
|
|
85
|
+
lines.push("policy:");
|
|
86
|
+
lines.push(` retries: { ${Object.entries(retries)
|
|
87
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
88
|
+
.join(", ")} }`);
|
|
89
|
+
lines.push(` timeout_ms: ${policy.timeoutMs ?? 120000}`);
|
|
90
|
+
lines.push(` tier: ${yamlString(policy.tier ?? "standard")}`);
|
|
91
|
+
// json only means something for a schema'd route; the loader defaults it,
|
|
92
|
+
// so an unstructured draft says nothing rather than saying something wrong.
|
|
93
|
+
if (spec.outputSchema !== null || policy.json !== undefined) {
|
|
94
|
+
lines.push(` json: ${yamlString(policy.json ?? "native")}`);
|
|
95
|
+
}
|
|
96
|
+
if (spec.capture === true) {
|
|
97
|
+
lines.push("# capture: replay inputs are stored LOCALLY only — never exported.");
|
|
98
|
+
lines.push("capture: true");
|
|
99
|
+
}
|
|
100
|
+
files.push({ path: `${name}.yaml`, contents: `${lines.join("\n")}\n` });
|
|
101
|
+
if (spec.outputSchema !== null) {
|
|
102
|
+
files.push({
|
|
103
|
+
path: `schemas/${name}.schema.json`,
|
|
104
|
+
contents: `${JSON.stringify(spec.outputSchema, null, 2)}\n`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
files.push({ path: `prompts/${name}.system.md`, contents: ensureTrailingNewline(spec.systemPrompt) });
|
|
108
|
+
return { route: spec.route, files };
|
|
109
|
+
}
|
|
110
|
+
function ensureTrailingNewline(text) {
|
|
111
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Write a draft into `draftsDir`. Two rules, both deliberate:
|
|
115
|
+
* - It writes ONLY under the drafts directory. Nothing here can touch the
|
|
116
|
+
* caller's source, their live routes, or the control plane (spec §4:
|
|
117
|
+
* drafts-only is permanent this phase).
|
|
118
|
+
* - It never overwrites. A second run reports what it skipped instead of
|
|
119
|
+
* silently discarding edits a human made to a draft.
|
|
120
|
+
*/
|
|
121
|
+
function writeRouteDraft(draft, draftsDir, fs, path) {
|
|
122
|
+
const written = [];
|
|
123
|
+
const skipped = [];
|
|
124
|
+
for (const file of draft.files) {
|
|
125
|
+
const full = path.join(draftsDir, file.path);
|
|
126
|
+
if (fs.existsSync(full)) {
|
|
127
|
+
skipped.push(full);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
131
|
+
fs.writeFileSync(full, file.contents, "utf8");
|
|
132
|
+
written.push(full);
|
|
133
|
+
}
|
|
134
|
+
return { written, skipped };
|
|
135
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routes-as-code loader — spec §3, plan step 3.
|
|
3
|
+
* Scans a routes directory for *.yaml bundles, validates structurally,
|
|
4
|
+
* resolves schema/prompt paths relative to the bundle file, freezes.
|
|
5
|
+
*/
|
|
6
|
+
import type { RouteBundle } from "../types/route";
|
|
7
|
+
/**
|
|
8
|
+
* Structured config error thrown for any invalid bundle (never a raw Error).
|
|
9
|
+
* Load-time problems are deliberately NOT RigFailures — the failure taxonomy
|
|
10
|
+
* classifies inference attempts; a malformed bundle means the rig was never
|
|
11
|
+
* runnable for that route.
|
|
12
|
+
*/
|
|
13
|
+
export declare class RouteConfigError extends Error {
|
|
14
|
+
readonly file: string | null;
|
|
15
|
+
readonly problems: readonly string[];
|
|
16
|
+
constructor(message: string, opts?: {
|
|
17
|
+
file?: string;
|
|
18
|
+
problems?: readonly string[];
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export interface LoadedRoutes {
|
|
22
|
+
get(route: string): RouteBundle;
|
|
23
|
+
list(): readonly RouteBundle[];
|
|
24
|
+
}
|
|
25
|
+
export declare function loadRoutes(routesDir: string): LoadedRoutes;
|
|
26
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/routes/loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAKV,WAAW,EAGZ,MAAM,gBAAgB,CAAC;AAIxB;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;gBAEzB,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAO;CAOxF;AAiCD,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC;IAChC,IAAI,IAAI,SAAS,WAAW,EAAE,CAAC;CAChC;AAkXD,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CA2C1D"}
|