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.
Files changed (220) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +115 -8
  3. package/dist/adapters/anthropic.d.ts +46 -0
  4. package/dist/adapters/anthropic.d.ts.map +1 -0
  5. package/dist/adapters/anthropic.js +202 -0
  6. package/dist/adapters/deepseek.d.ts +41 -0
  7. package/dist/adapters/deepseek.d.ts.map +1 -0
  8. package/dist/adapters/deepseek.js +163 -0
  9. package/dist/adapters/gemini.d.ts +44 -0
  10. package/dist/adapters/gemini.d.ts.map +1 -0
  11. package/dist/adapters/gemini.js +222 -0
  12. package/dist/adapters/grok.d.ts +44 -0
  13. package/dist/adapters/grok.d.ts.map +1 -0
  14. package/dist/adapters/grok.js +168 -0
  15. package/dist/adapters/openai-compat.d.ts +74 -0
  16. package/dist/adapters/openai-compat.d.ts.map +1 -0
  17. package/dist/adapters/openai-compat.js +187 -0
  18. package/dist/adapters/openai.d.ts +49 -0
  19. package/dist/adapters/openai.d.ts.map +1 -0
  20. package/dist/adapters/openai.js +222 -0
  21. package/dist/bakeoff/engine.d.ts +52 -0
  22. package/dist/bakeoff/engine.d.ts.map +1 -0
  23. package/dist/bakeoff/engine.js +163 -0
  24. package/dist/bakeoff/replay.d.ts +52 -0
  25. package/dist/bakeoff/replay.d.ts.map +1 -0
  26. package/dist/bakeoff/replay.js +94 -0
  27. package/dist/bakeoff/samples.d.ts +62 -0
  28. package/dist/bakeoff/samples.d.ts.map +1 -0
  29. package/dist/bakeoff/samples.js +128 -0
  30. package/dist/bakeoff/stats.d.ts +13 -0
  31. package/dist/bakeoff/stats.d.ts.map +1 -0
  32. package/dist/bakeoff/stats.js +48 -0
  33. package/dist/bakeoff/types.d.ts +37 -0
  34. package/dist/bakeoff/types.d.ts.map +1 -0
  35. package/dist/bakeoff/types.js +10 -0
  36. package/dist/cli.d.ts +27 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +572 -0
  39. package/dist/config.d.ts +104 -0
  40. package/dist/config.d.ts.map +1 -0
  41. package/dist/config.js +148 -0
  42. package/dist/feedback/coverage.d.ts +32 -0
  43. package/dist/feedback/coverage.d.ts.map +1 -0
  44. package/dist/feedback/coverage.js +95 -0
  45. package/dist/feedback/feedback.d.ts +35 -0
  46. package/dist/feedback/feedback.d.ts.map +1 -0
  47. package/dist/feedback/feedback.js +39 -0
  48. package/dist/index.d.ts +155 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +448 -0
  51. package/dist/init/finder.d.ts +79 -0
  52. package/dist/init/finder.d.ts.map +1 -0
  53. package/dist/init/finder.js +297 -0
  54. package/dist/init/run.d.ts +99 -0
  55. package/dist/init/run.d.ts.map +1 -0
  56. package/dist/init/run.js +281 -0
  57. package/dist/init/signatures.d.ts +39 -0
  58. package/dist/init/signatures.d.ts.map +1 -0
  59. package/dist/init/signatures.js +116 -0
  60. package/dist/matrix/aggregate.d.ts +89 -0
  61. package/dist/matrix/aggregate.d.ts.map +1 -0
  62. package/dist/matrix/aggregate.js +186 -0
  63. package/dist/query/grammar.d.ts +35 -0
  64. package/dist/query/grammar.d.ts.map +1 -0
  65. package/dist/query/grammar.js +279 -0
  66. package/dist/query/run.d.ts +8 -0
  67. package/dist/query/run.d.ts.map +1 -0
  68. package/dist/query/run.js +21 -0
  69. package/dist/registry/build.d.ts +126 -0
  70. package/dist/registry/build.d.ts.map +1 -0
  71. package/dist/registry/build.js +192 -0
  72. package/dist/registry/call-notes.d.ts +11 -0
  73. package/dist/registry/call-notes.d.ts.map +1 -0
  74. package/dist/registry/call-notes.js +72 -0
  75. package/dist/registry/claims.d.ts +63 -0
  76. package/dist/registry/claims.d.ts.map +1 -0
  77. package/dist/registry/claims.js +107 -0
  78. package/dist/registry/facts.d.ts +32 -0
  79. package/dist/registry/facts.d.ts.map +1 -0
  80. package/dist/registry/facts.js +120 -0
  81. package/dist/registry/fingerprint.d.ts +20 -0
  82. package/dist/registry/fingerprint.d.ts.map +1 -0
  83. package/dist/registry/fingerprint.js +150 -0
  84. package/dist/registry/parity.d.ts +26 -0
  85. package/dist/registry/parity.d.ts.map +1 -0
  86. package/dist/registry/parity.js +41 -0
  87. package/dist/registry/pricing.d.ts +19 -0
  88. package/dist/registry/pricing.d.ts.map +1 -0
  89. package/dist/registry/pricing.js +69 -0
  90. package/dist/registry/rows.d.ts +16 -0
  91. package/dist/registry/rows.d.ts.map +1 -0
  92. package/dist/registry/rows.js +57 -0
  93. package/dist/registry/seed.d.ts +43 -0
  94. package/dist/registry/seed.d.ts.map +1 -0
  95. package/dist/registry/seed.js +97 -0
  96. package/dist/registry/select.d.ts +68 -0
  97. package/dist/registry/select.d.ts.map +1 -0
  98. package/dist/registry/select.js +141 -0
  99. package/dist/registry/staleness.d.ts +18 -0
  100. package/dist/registry/staleness.d.ts.map +1 -0
  101. package/dist/registry/staleness.js +39 -0
  102. package/dist/routes/draft.d.ts +83 -0
  103. package/dist/routes/draft.d.ts.map +1 -0
  104. package/dist/routes/draft.js +135 -0
  105. package/dist/routes/loader.d.ts +26 -0
  106. package/dist/routes/loader.d.ts.map +1 -0
  107. package/dist/routes/loader.js +436 -0
  108. package/dist/routes/manifest.d.ts +24 -0
  109. package/dist/routes/manifest.d.ts.map +1 -0
  110. package/dist/routes/manifest.js +118 -0
  111. package/dist/routes/serveability.d.ts +52 -0
  112. package/dist/routes/serveability.d.ts.map +1 -0
  113. package/dist/routes/serveability.js +73 -0
  114. package/dist/routes/template.d.ts +18 -0
  115. package/dist/routes/template.d.ts.map +1 -0
  116. package/dist/routes/template.js +94 -0
  117. package/dist/run/cost.d.ts +26 -0
  118. package/dist/run/cost.d.ts.map +1 -0
  119. package/dist/run/cost.js +41 -0
  120. package/dist/run/envelope.d.ts +21 -0
  121. package/dist/run/envelope.d.ts.map +1 -0
  122. package/dist/run/envelope.js +45 -0
  123. package/dist/run/grounding.d.ts +56 -0
  124. package/dist/run/grounding.d.ts.map +1 -0
  125. package/dist/run/grounding.js +89 -0
  126. package/dist/run/repair.d.ts +36 -0
  127. package/dist/run/repair.d.ts.map +1 -0
  128. package/dist/run/repair.js +61 -0
  129. package/dist/run/resolve.d.ts +26 -0
  130. package/dist/run/resolve.d.ts.map +1 -0
  131. package/dist/run/resolve.js +81 -0
  132. package/dist/run/retry.d.ts +19 -0
  133. package/dist/run/retry.d.ts.map +1 -0
  134. package/dist/run/retry.js +50 -0
  135. package/dist/run/run.d.ts +41 -0
  136. package/dist/run/run.d.ts.map +1 -0
  137. package/dist/run/run.js +0 -0
  138. package/dist/run/validate.d.ts +15 -0
  139. package/dist/run/validate.d.ts.map +1 -0
  140. package/dist/run/validate.js +56 -0
  141. package/dist/savings/emit.d.ts +25 -0
  142. package/dist/savings/emit.d.ts.map +1 -0
  143. package/dist/savings/emit.js +75 -0
  144. package/dist/savings/odometer.d.ts +32 -0
  145. package/dist/savings/odometer.d.ts.map +1 -0
  146. package/dist/savings/odometer.js +70 -0
  147. package/dist/savings/swap-execute.d.ts +44 -0
  148. package/dist/savings/swap-execute.d.ts.map +1 -0
  149. package/dist/savings/swap-execute.js +87 -0
  150. package/dist/savings/swaps-client.d.ts +41 -0
  151. package/dist/savings/swaps-client.d.ts.map +1 -0
  152. package/dist/savings/swaps-client.js +81 -0
  153. package/dist/telemetry/capture.d.ts +28 -0
  154. package/dist/telemetry/capture.d.ts.map +1 -0
  155. package/dist/telemetry/capture.js +74 -0
  156. package/dist/telemetry/exporter.d.ts +62 -0
  157. package/dist/telemetry/exporter.d.ts.map +1 -0
  158. package/dist/telemetry/exporter.js +233 -0
  159. package/dist/telemetry/ingest-protocol.d.ts +161 -0
  160. package/dist/telemetry/ingest-protocol.d.ts.map +1 -0
  161. package/dist/telemetry/ingest-protocol.js +163 -0
  162. package/dist/telemetry/sinks.d.ts +54 -0
  163. package/dist/telemetry/sinks.d.ts.map +1 -0
  164. package/dist/telemetry/sinks.js +89 -0
  165. package/dist/telemetry/store.d.ts +38 -0
  166. package/dist/telemetry/store.d.ts.map +1 -0
  167. package/dist/telemetry/store.js +332 -0
  168. package/dist/telemetry/supabase-exporter.d.ts +64 -0
  169. package/dist/telemetry/supabase-exporter.d.ts.map +1 -0
  170. package/dist/telemetry/supabase-exporter.js +211 -0
  171. package/dist/tenancy/api-key-format.d.ts +67 -0
  172. package/dist/tenancy/api-key-format.d.ts.map +1 -0
  173. package/dist/tenancy/api-key-format.js +91 -0
  174. package/dist/tenancy/constants.d.ts +23 -0
  175. package/dist/tenancy/constants.d.ts.map +1 -0
  176. package/dist/tenancy/constants.js +25 -0
  177. package/dist/tenancy/scrub.d.ts +47 -0
  178. package/dist/tenancy/scrub.d.ts.map +1 -0
  179. package/dist/tenancy/scrub.js +85 -0
  180. package/dist/types/failures-teaching.d.ts +56 -0
  181. package/dist/types/failures-teaching.d.ts.map +1 -0
  182. package/dist/types/failures-teaching.js +177 -0
  183. package/dist/types/failures.d.ts +27 -0
  184. package/dist/types/failures.d.ts.map +1 -0
  185. package/dist/types/failures.js +41 -0
  186. package/dist/types/provider.d.ts +79 -0
  187. package/dist/types/provider.d.ts.map +1 -0
  188. package/dist/types/provider.js +20 -0
  189. package/dist/types/route.d.ts +127 -0
  190. package/dist/types/route.d.ts.map +1 -0
  191. package/dist/types/route.js +6 -0
  192. package/dist/types/telemetry.d.ts +89 -0
  193. package/dist/types/telemetry.d.ts.map +1 -0
  194. package/dist/types/telemetry.js +6 -0
  195. package/dist/watch/budget.d.ts +19 -0
  196. package/dist/watch/budget.d.ts.map +1 -0
  197. package/dist/watch/budget.js +35 -0
  198. package/dist/watch/evaluate.d.ts +66 -0
  199. package/dist/watch/evaluate.d.ts.map +1 -0
  200. package/dist/watch/evaluate.js +94 -0
  201. package/dist/watch/proposals.d.ts +73 -0
  202. package/dist/watch/proposals.d.ts.map +1 -0
  203. package/dist/watch/proposals.js +169 -0
  204. package/dist/watch/run-record.d.ts +64 -0
  205. package/dist/watch/run-record.d.ts.map +1 -0
  206. package/dist/watch/run-record.js +86 -0
  207. package/dist/watch/run-reported.d.ts +20 -0
  208. package/dist/watch/run-reported.d.ts.map +1 -0
  209. package/dist/watch/run-reported.js +53 -0
  210. package/dist/watch/run.d.ts +40 -0
  211. package/dist/watch/run.d.ts.map +1 -0
  212. package/dist/watch/run.js +44 -0
  213. package/dist/watch/state-store.d.ts +48 -0
  214. package/dist/watch/state-store.d.ts.map +1 -0
  215. package/dist/watch/state-store.js +86 -0
  216. package/dist/watch/state.d.ts +21 -0
  217. package/dist/watch/state.d.ts.map +1 -0
  218. package/dist/watch/state.js +93 -0
  219. package/package.json +50 -14
  220. package/index.js +0 -3
@@ -0,0 +1,281 @@
1
+ "use strict";
2
+ /**
3
+ * `modelrig init` v0 — spec WS2.
4
+ *
5
+ * Finds provider call sites in a repo, drafts a route bundle per site, and
6
+ * writes a MIGRATION.md mapping call site → route → the `rig.run` line that
7
+ * would replace it.
8
+ *
9
+ * THE HARD RULE, restated because it is the whole design: init NEVER edits
10
+ * the target's source. It writes under `modelrig/routes/drafts/` and nothing
11
+ * else. The diff is the human's to apply — spec §4 puts writing-to-source
12
+ * permanently out of scope for this phase, and the drafts-only property is
13
+ * tested, not just documented.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.routeNameFor = routeNameFor;
17
+ exports.capabilitiesFor = capabilitiesFor;
18
+ exports.buildCostOutlook = buildCostOutlook;
19
+ exports.buildMigrationMarkdown = buildMigrationMarkdown;
20
+ exports.runInit = runInit;
21
+ const draft_1 = require("../routes/draft");
22
+ const seed_1 = require("../registry/seed");
23
+ const finder_1 = require("./finder");
24
+ /** Route name from a call site: `<file stem>.<line>` namespaced by its
25
+ * directory, e.g. "services.asset_classifier_42". Deterministic so a second
26
+ * run produces the same names and therefore skips the same drafts. */
27
+ function routeNameFor(site, basename) {
28
+ const parts = site.file.split("/").filter((p) => p !== "" && p !== "." && p !== "src");
29
+ const fileStem = basename(parts[parts.length - 1] ?? "route").replace(/\.[jt]sx?$/, "");
30
+ const dir = parts.length > 1 ? parts[parts.length - 2] : "app";
31
+ const normalize = (s) => s.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_|_$/g, "").toLowerCase();
32
+ return `${normalize(dir)}.${normalize(fileStem)}_${site.line}`;
33
+ }
34
+ /** What the site's structured-output usage implies about capability needs.
35
+ * Conservative: a json_schema request implies native enforcement is wanted;
36
+ * bare JSON mode only implies conformance. */
37
+ function capabilitiesFor(site) {
38
+ if (site.schemaHint === "json-schema" || site.schemaHint === "zod")
39
+ return ["structured-native"];
40
+ if (site.schemaHint === "json-mode")
41
+ return ["structured-coached"];
42
+ return [];
43
+ }
44
+ function placeholderPrompt(site) {
45
+ return (`<!-- DRAFT prompt for ${site.file}:${site.line}.\n` +
46
+ " No inline system prompt was found at that call site — it is most likely built in a\n" +
47
+ " variable or read from a file. Paste the real prompt here, and wrap any interpolated\n" +
48
+ " value in a doubled-brace placeholder (the same syntax the route loader scans for)\n" +
49
+ " so the route can declare it as a variable. -->\n\n" +
50
+ "TODO: paste the system prompt from " +
51
+ `${site.file}:${site.line}\n`);
52
+ }
53
+ /** {{variables}} referenced by a prompt — the loader requires them declared. */
54
+ function variablesIn(prompt) {
55
+ const found = new Set();
56
+ for (const match of prompt.matchAll(/\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g))
57
+ found.add(match[1]);
58
+ return [...found];
59
+ }
60
+ /** Price the incumbent against the cheapest probed challenger, honestly. */
61
+ function buildCostOutlook(incumbentKey, challengerKeys, rows) {
62
+ const effectiveOf = (key) => rows.find((r) => r.key === key)?.effectiveUsdPer1k ?? null;
63
+ const incumbent = incumbentKey === null ? null : effectiveOf(incumbentKey);
64
+ const challengers = challengerKeys
65
+ .map((key) => ({ model: key, effectiveUsdPer1k: effectiveOf(key) }))
66
+ .filter((c) => c.effectiveUsdPer1k !== null)
67
+ .sort((a, b) => a.effectiveUsdPer1k - b.effectiveUsdPer1k);
68
+ const best = challengers[0] ?? null;
69
+ if (best === null) {
70
+ return {
71
+ incumbentEffectiveUsdPer1k: incumbent,
72
+ bestChallenger: null,
73
+ indicativeSavingsPct: null,
74
+ note: "No probed challenger with a cost figure — nothing to compare against yet.",
75
+ };
76
+ }
77
+ if (incumbent === null) {
78
+ return {
79
+ incumbentEffectiveUsdPer1k: null,
80
+ bestChallenger: best,
81
+ indicativeSavingsPct: null,
82
+ note: incumbentKey === null
83
+ ? "The model at this call site could not be read, so there is no baseline to compare."
84
+ : `${incumbentKey} is not in the probed registry, so there is no measured baseline to ` +
85
+ "compare against. A bake-off measures both arms on your own inputs and settles it.",
86
+ };
87
+ }
88
+ const pct = ((incumbent - best.effectiveUsdPer1k) / incumbent) * 100;
89
+ return {
90
+ incumbentEffectiveUsdPer1k: incumbent,
91
+ bestChallenger: best,
92
+ indicativeSavingsPct: pct,
93
+ note: pct > 0
94
+ ? "Indicative only — measured on a generic schema task, not your traffic. A bake-off on " +
95
+ "your own captured inputs is the number that should decide anything."
96
+ : "The incumbent is already at or below the cheapest probed alternative on this measure.",
97
+ };
98
+ }
99
+ function replacementSnippet(route, variables) {
100
+ const input = variables.length === 0
101
+ ? "{ /* the values your prompt needs */ }"
102
+ : `{ ${variables.map((v) => `${v}`).join(", ")} }`;
103
+ return (`const { output } = await rig.run(${JSON.stringify(route)}, {\n` +
104
+ ` input: ${input},\n` +
105
+ ` tags: { run_id: runId },\n` +
106
+ ` budget: { envelope: runId },\n` +
107
+ `});`);
108
+ }
109
+ function buildMigrationMarkdown(drafts, result) {
110
+ const lines = [
111
+ "# ModelRig migration map",
112
+ "",
113
+ `Found **${result.sitesFound} provider call site(s)** across ${result.filesScanned} scanned files.`,
114
+ "",
115
+ "**Nothing in your repo has changed.** `modelrig init` writes drafts and a map; applying them",
116
+ "is your call, one call site at a time. A sensible order: pick the site you understand best,",
117
+ "move its draft up into `modelrig/routes/`, swap the call, run it once, and compare.",
118
+ "",
119
+ "Every draft lists your current model FIRST, so the route reproduces today's behavior before",
120
+ "you change anything. The cheaper candidates beneath it are challengers a bake-off can test —",
121
+ "they never serve until you say so.",
122
+ "",
123
+ ];
124
+ if (result.filesSkipped.length > 0) {
125
+ lines.push(`> ${result.filesSkipped.length} file(s) were skipped (too large or unreadable) and were NOT`, "> searched: " + result.filesSkipped.slice(0, 10).join(", ") + ".", "");
126
+ }
127
+ if (drafts.length === 0) {
128
+ lines.push("## No call sites found", "", "That means no direct SDK call or provider-host fetch matched in the scanned files. If you", "know there are model calls here, they may be wrapped in a helper that hides the SDK — point", "init at the file that actually calls the provider, or draft a route by hand:", "", "```bash", "modelrig init path/to/that/directory", "```", "");
129
+ return `${lines.join("\n")}\n`;
130
+ }
131
+ // The cost opportunity, up front — shown, not taken. Parity first is a
132
+ // sequencing decision, not a decision to hide the money.
133
+ const priced = drafts.filter((d) => d.costOutlook.indicativeSavingsPct !== null);
134
+ if (priced.length > 0) {
135
+ const best = [...priced].sort((a, b) => (b.costOutlook.indicativeSavingsPct ?? 0) - (a.costOutlook.indicativeSavingsPct ?? 0))[0];
136
+ lines.push("## What this could save", "", `Of ${drafts.length} drafted route(s), **${priced.length}** have both a probed incumbent and a`, "cheaper probed alternative. The largest indicative gap:", "", `- \`${best.route}\` — ${best.site.model} at $${(best.costOutlook.incumbentEffectiveUsdPer1k ?? 0).toFixed(4)} ` +
137
+ `vs ${best.costOutlook.bestChallenger?.model} at $${(best.costOutlook.bestChallenger?.effectiveUsdPer1k ?? 0).toFixed(4)} ` +
138
+ `per 1,000 conformant outputs — **~${(best.costOutlook.indicativeSavingsPct ?? 0).toFixed(0)}% cheaper**.`, "", "**These drafts do not take that saving.** Each one routes to your current model so the", "first run reproduces today's behavior — otherwise a changed output leaves you unable to", "tell a migration bug from a model difference. Prove the cheaper model separately, on your", "own traffic:", "", "```bash", `modelrig bakeoff --route ${best.route} --variants <challenger> --replay-last 50`, "```", "", "Those numbers come from probes on a generic schema task, not from your inputs. The", "bake-off is the one that should decide anything.", "");
139
+ }
140
+ lines.push("## Call site → route", "", "| call site | provider | model today | drafted route | indicative saving |", "|---|---|---|---|---|");
141
+ for (const d of drafts) {
142
+ const pct = d.costOutlook.indicativeSavingsPct;
143
+ const saving = pct === null ? "—" : pct > 0 ? `~${pct.toFixed(0)}%` : "none";
144
+ lines.push(`| \`${d.site.file}:${d.site.line}\` | ${d.site.provider} | ` +
145
+ `${d.site.model ?? `_(from \`${d.site.modelExpression ?? "a variable"}\`)_`} | ` +
146
+ `\`${d.route}\` | ${saving} |`);
147
+ }
148
+ lines.push("");
149
+ for (const d of drafts) {
150
+ lines.push(`### \`${d.route}\``, "", `From \`${d.site.file}:${d.site.line}\` (${d.site.provider}, matched by ${d.site.matchedBy === "sdk" ? "SDK call shape" : "provider API host"}).`, "", "Replace the call with:", "", "```ts", d.replacement, "```", "");
151
+ if (d.site.notes.length > 0) {
152
+ lines.push("**Before this draft will run:**", "");
153
+ for (const note of d.site.notes)
154
+ lines.push(`- ${note}`);
155
+ lines.push("");
156
+ }
157
+ lines.push("Candidates were chosen like this:", "");
158
+ for (const reason of d.candidateReasoning)
159
+ lines.push(`- ${reason}`);
160
+ lines.push("");
161
+ const outlook = d.costOutlook;
162
+ if (outlook.indicativeSavingsPct !== null && outlook.bestChallenger !== null) {
163
+ lines.push(`**Cost:** ${d.site.model} measures $${(outlook.incumbentEffectiveUsdPer1k ?? 0).toFixed(4)} per 1,000 ` +
164
+ `conformant outputs; ${outlook.bestChallenger.model} measures ` +
165
+ `$${outlook.bestChallenger.effectiveUsdPer1k.toFixed(4)} — indicatively ` +
166
+ `**${outlook.indicativeSavingsPct.toFixed(0)}% cheaper**. ${outlook.note}`, "");
167
+ }
168
+ else {
169
+ lines.push(`**Cost:** ${outlook.note}`, "");
170
+ }
171
+ }
172
+ lines.push("## After you move a draft", "", "```bash", "modelrig serve # boot with the new route loaded", "modelrig bakeoff --route <route> --variants <name> --replay-last-n 50", "```", "", "The bake-off replays YOUR inputs through a challenger and reports the effective cost of", "conformance for each. Nothing switches without `modelrig swap execute <id> --approve`.", "");
173
+ return `${lines.join("\n")}\n`;
174
+ }
175
+ function runInit(deps, options = {}) {
176
+ const scan = (0, finder_1.scanRepoForCallSites)(deps.root, deps.fs, deps.path, options);
177
+ const registryRows = options.registryRows ?? [];
178
+ const drafts = [];
179
+ const usedNames = new Set();
180
+ for (const site of scan.sites) {
181
+ let route = routeNameFor(site, deps.path.basename);
182
+ while (usedNames.has(route))
183
+ route = `${route}_x`;
184
+ usedNames.add(route);
185
+ const prompt = site.systemPrompt ?? placeholderPrompt(site);
186
+ const variables = variablesIn(prompt);
187
+ const capabilities = capabilitiesFor(site);
188
+ const incumbent = site.model === null ? null : `${site.provider}/${site.model}`;
189
+ const seeded = (0, seed_1.seedCandidates)(registryRows, {
190
+ capabilities,
191
+ incumbent,
192
+ maxInputUsdPerMtok: null,
193
+ explicit: null,
194
+ ...(options.challengerCount !== undefined ? { challengerCount: options.challengerCount } : {}),
195
+ });
196
+ // A site whose model we could not read and that seeded nothing would
197
+ // produce an unloadable draft — record a placeholder candidate and say so.
198
+ const candidates = seeded.candidates.length > 0
199
+ ? seeded.candidates
200
+ : [
201
+ {
202
+ provider: site.provider,
203
+ model: "TODO-set-the-model",
204
+ note: "could not read the model at the call site — set it before running",
205
+ },
206
+ ];
207
+ const draft = (0, draft_1.buildRouteDraft)({
208
+ route,
209
+ candidates,
210
+ systemPrompt: prompt,
211
+ variables,
212
+ outputSchema: site.wantsStructuredOutput
213
+ ? {
214
+ type: "object",
215
+ description: `TODO: paste the real output schema for ${site.file}:${site.line}. The call site ` +
216
+ `asked for structured output (${site.schemaHint ?? "schema requested"}), so the ` +
217
+ "route should enforce the same shape.",
218
+ }
219
+ : null,
220
+ provenance: [
221
+ `found at ${site.file}:${site.line} — ${site.provider} call, matched by ${site.matchedBy === "sdk" ? "SDK call shape" : "provider API host"}`,
222
+ "drafted by `modelrig init`; nothing in your source was changed",
223
+ ],
224
+ ...(site.params.temperature !== undefined || site.params.maxTokens !== undefined
225
+ ? {
226
+ policy: {
227
+ ...(site.params.timeoutMs !== undefined ? { timeoutMs: site.params.timeoutMs } : {}),
228
+ },
229
+ }
230
+ : {}),
231
+ });
232
+ drafts.push({
233
+ route,
234
+ site,
235
+ draft,
236
+ candidateReasoning: seeded.reasoning,
237
+ replacement: replacementSnippet(route, variables),
238
+ costOutlook: buildCostOutlook(incumbent, candidates
239
+ .filter((c) => `${c.provider}/${c.model}` !== incumbent)
240
+ .map((c) => `${c.provider}/${c.model}`), registryRows),
241
+ });
242
+ }
243
+ const migrationMarkdown = buildMigrationMarkdown(drafts, {
244
+ sitesFound: scan.sites.length,
245
+ filesScanned: scan.filesScanned,
246
+ filesSkipped: scan.filesSkipped,
247
+ });
248
+ if (options.dryRun === true) {
249
+ return {
250
+ drafts,
251
+ sitesFound: scan.sites.length,
252
+ filesScanned: scan.filesScanned,
253
+ filesSkipped: scan.filesSkipped,
254
+ written: [],
255
+ skipped: [],
256
+ migrationMarkdown,
257
+ };
258
+ }
259
+ const written = [];
260
+ const skipped = [];
261
+ for (const d of drafts) {
262
+ const result = (0, draft_1.writeRouteDraft)(d.draft, deps.draftsDir, deps.fs, deps.path);
263
+ written.push(...result.written);
264
+ skipped.push(...result.skipped);
265
+ }
266
+ const migrationPath = deps.path.join(deps.draftsDir, "MIGRATION.md");
267
+ // MIGRATION.md is regenerated every run — it is a report, not a draft
268
+ // someone edits, and a stale map is worse than none.
269
+ deps.fs.mkdirSync(deps.path.dirname(migrationPath), { recursive: true });
270
+ deps.fs.writeFileSync(migrationPath, migrationMarkdown, "utf8");
271
+ written.push(migrationPath);
272
+ return {
273
+ drafts,
274
+ sitesFound: scan.sites.length,
275
+ filesScanned: scan.filesScanned,
276
+ filesSkipped: scan.filesSkipped,
277
+ written,
278
+ skipped,
279
+ migrationMarkdown,
280
+ };
281
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Provider call signatures — the table `modelrig init` scans for.
3
+ *
4
+ * GENERIC BY CONSTRUCTION (spec WS2 / SD8). There is not one line in this
5
+ * file about InferWealth, its pipeline, or its 16 steps: customer zero is
6
+ * the acceptance FIXTURE, never a special case. Any repo calling these SDKs
7
+ * the ordinary way gets the same treatment.
8
+ *
9
+ * Adding a provider means adding a row here — no new code path.
10
+ */
11
+ export interface ProviderSignature {
12
+ /** Provider id as ModelRig knows it (matches registry model keys). */
13
+ readonly provider: string;
14
+ /** npm package names whose import marks this SDK as present. */
15
+ readonly packages: readonly string[];
16
+ /** Call expressions that mean "a model is being called". */
17
+ readonly callPatterns: readonly RegExp[];
18
+ /** API hosts, for the fetch()-instead-of-SDK fallback. */
19
+ readonly hosts: readonly string[];
20
+ /** Keys holding the model id inside the call's options object. */
21
+ readonly modelKeys: readonly string[];
22
+ /** Keys holding the system prompt. */
23
+ readonly systemKeys: readonly string[];
24
+ /** Keys whose presence means the caller asked for structured output. */
25
+ readonly schemaKeys: readonly string[];
26
+ }
27
+ /** `g` flags are deliberate: the finder scans whole files. Each RegExp is
28
+ * re-created per scan by `freshPattern` so lastIndex never leaks between
29
+ * files. */
30
+ export declare const PROVIDER_SIGNATURES: readonly ProviderSignature[];
31
+ /** Every option key any provider uses for a model id — used when a call
32
+ * matched by host rather than by SDK shape. */
33
+ export declare const ALL_MODEL_KEYS: readonly string[];
34
+ /** Generation parameters worth carrying into a drafted route's policy. */
35
+ export declare const PARAM_KEYS: Readonly<Record<string, string>>;
36
+ /** A fresh, zero-lastIndex copy — global regexes are stateful. */
37
+ export declare function freshPattern(pattern: RegExp): RegExp;
38
+ export declare function signatureFor(provider: string): ProviderSignature | null;
39
+ //# sourceMappingURL=signatures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signatures.d.ts","sourceRoot":"","sources":["../../src/init/signatures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,4DAA4D;IAC5D,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,kEAAkE;IAClE,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED;;YAEY;AACZ,eAAO,MAAM,mBAAmB,EAAE,SAAS,iBAAiB,EAyE3D,CAAC;AAEF;+CAC+C;AAC/C,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAE3C,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUvD,CAAC;AAEF,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAEvE"}
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ /**
3
+ * Provider call signatures — the table `modelrig init` scans for.
4
+ *
5
+ * GENERIC BY CONSTRUCTION (spec WS2 / SD8). There is not one line in this
6
+ * file about InferWealth, its pipeline, or its 16 steps: customer zero is
7
+ * the acceptance FIXTURE, never a special case. Any repo calling these SDKs
8
+ * the ordinary way gets the same treatment.
9
+ *
10
+ * Adding a provider means adding a row here — no new code path.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.PARAM_KEYS = exports.ALL_MODEL_KEYS = exports.PROVIDER_SIGNATURES = void 0;
14
+ exports.freshPattern = freshPattern;
15
+ exports.signatureFor = signatureFor;
16
+ /** `g` flags are deliberate: the finder scans whole files. Each RegExp is
17
+ * re-created per scan by `freshPattern` so lastIndex never leaks between
18
+ * files. */
19
+ exports.PROVIDER_SIGNATURES = [
20
+ {
21
+ provider: "openai",
22
+ packages: ["openai", "@ai-sdk/openai"],
23
+ callPatterns: [
24
+ /\.chat\.completions\.create\s*\(/g,
25
+ /\.responses\.create\s*\(/g,
26
+ /\.completions\.create\s*\(/g,
27
+ ],
28
+ hosts: ["api.openai.com"],
29
+ modelKeys: ["model"],
30
+ systemKeys: ["system", "instructions"],
31
+ schemaKeys: ["response_format", "text", "json_schema"],
32
+ },
33
+ {
34
+ provider: "anthropic",
35
+ packages: ["@anthropic-ai/sdk", "@ai-sdk/anthropic"],
36
+ callPatterns: [/\.messages\.create\s*\(/g, /\.messages\.stream\s*\(/g],
37
+ hosts: ["api.anthropic.com"],
38
+ modelKeys: ["model"],
39
+ systemKeys: ["system"],
40
+ schemaKeys: ["tools", "tool_choice", "output_schema"],
41
+ },
42
+ {
43
+ provider: "gemini",
44
+ packages: ["@google/genai", "@google/generative-ai", "@ai-sdk/google"],
45
+ callPatterns: [
46
+ /\.models\.generateContent\s*\(/g,
47
+ /\.models\.generateContentStream\s*\(/g,
48
+ /\.generateContent\s*\(/g,
49
+ ],
50
+ hosts: ["generativelanguage.googleapis.com"],
51
+ modelKeys: ["model"],
52
+ systemKeys: ["systemInstruction"],
53
+ schemaKeys: ["responseSchema", "responseJsonSchema", "responseMimeType"],
54
+ },
55
+ {
56
+ provider: "deepseek",
57
+ // DeepSeek ships an OpenAI-compatible API; the host is the tell.
58
+ packages: [],
59
+ callPatterns: [],
60
+ hosts: ["api.deepseek.com"],
61
+ modelKeys: ["model"],
62
+ systemKeys: ["system"],
63
+ schemaKeys: ["response_format"],
64
+ },
65
+ {
66
+ provider: "grok",
67
+ packages: [],
68
+ callPatterns: [],
69
+ hosts: ["api.x.ai"],
70
+ modelKeys: ["model"],
71
+ systemKeys: ["system"],
72
+ schemaKeys: ["response_format"],
73
+ },
74
+ {
75
+ provider: "deepinfra",
76
+ packages: [],
77
+ callPatterns: [],
78
+ hosts: ["api.deepinfra.com"],
79
+ modelKeys: ["model"],
80
+ systemKeys: ["system"],
81
+ schemaKeys: ["response_format"],
82
+ },
83
+ {
84
+ provider: "fireworks",
85
+ packages: [],
86
+ callPatterns: [],
87
+ hosts: ["api.fireworks.ai"],
88
+ modelKeys: ["model"],
89
+ systemKeys: ["system"],
90
+ schemaKeys: ["response_format"],
91
+ },
92
+ ];
93
+ /** Every option key any provider uses for a model id — used when a call
94
+ * matched by host rather than by SDK shape. */
95
+ exports.ALL_MODEL_KEYS = [
96
+ ...new Set(exports.PROVIDER_SIGNATURES.flatMap((s) => s.modelKeys)),
97
+ ];
98
+ /** Generation parameters worth carrying into a drafted route's policy. */
99
+ exports.PARAM_KEYS = {
100
+ temperature: "temperature",
101
+ top_p: "topP",
102
+ topP: "topP",
103
+ max_tokens: "maxTokens",
104
+ maxTokens: "maxTokens",
105
+ max_output_tokens: "maxTokens",
106
+ maxOutputTokens: "maxTokens",
107
+ timeout: "timeoutMs",
108
+ timeoutMs: "timeoutMs",
109
+ };
110
+ /** A fresh, zero-lastIndex copy — global regexes are stateful. */
111
+ function freshPattern(pattern) {
112
+ return new RegExp(pattern.source, pattern.flags);
113
+ }
114
+ function signatureFor(provider) {
115
+ return exports.PROVIDER_SIGNATURES.find((s) => s.provider === provider) ?? null;
116
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Matrix aggregation job — Phase 4 WS6 (plan §4). Pure fold: telemetry
3
+ * rows → priors cells keyed (fingerprint_cell × model), each carrying
4
+ * outcome stats. Writes go to priors_cells ONLY (deny-all RLS, no UI, no
5
+ * cross-account use — standing decision 7; the gate stays off).
6
+ *
7
+ * Contribution discipline (spec WS6): the flag decides whether a cell may
8
+ * ever aggregate beyond its own account — respected here in code even
9
+ * though today's deployment is single-tenant. Non-contributing sources
10
+ * produce "account"-scoped cells, full stop.
11
+ */
12
+ import type { TelemetryRow } from "../types/telemetry";
13
+ /** A2 (eval-methodology §8): one model's paired record against ONE
14
+ * opponent within a cell — win/loss/tie from this model's perspective,
15
+ * plus how many distinct source accounts contributed pairs. */
16
+ export interface PairwiseRecord {
17
+ readonly wins: number;
18
+ readonly losses: number;
19
+ readonly ties: number;
20
+ readonly samples: number;
21
+ readonly sources: number;
22
+ }
23
+ export interface PriorsCellStats {
24
+ readonly samples: number;
25
+ readonly conform_rate: number;
26
+ readonly repair_rate: number;
27
+ /** Mean cost over PRICED samples only — unpriced $0 rows never dilute. */
28
+ readonly mean_cost_usd: number | null;
29
+ readonly scope: "account" | "community";
30
+ /** A2: paired outcomes keyed by opponent model_key — the Bradley-Terry
31
+ * raw material. Absent when this (cell, model) has no paired replays. */
32
+ readonly pairwise?: Readonly<Record<string, PairwiseRecord>>;
33
+ }
34
+ export interface PriorsCell {
35
+ readonly cellKey: string;
36
+ readonly modelKey: string;
37
+ readonly stats: PriorsCellStats;
38
+ }
39
+ export interface AggregateOptions {
40
+ /** The source's rig-manifest contribution flag. */
41
+ readonly contribution: boolean;
42
+ /**
43
+ * The ORG's telemetry posture — C6 review ruling §7.1a, and the first
44
+ * consumer this column has ever had.
45
+ *
46
+ * The ruling is careful about what posture does NOT do: it never gates
47
+ * telemetry ingest. Metering rows are the billing substrate (2%-of-list
48
+ * computes from them), so a posture that stopped telemetry would be a
49
+ * self-inflicted billing hole, and it is not what a customer chooses when
50
+ * they choose pure-router. What they choose is that their traffic does not
51
+ * become community knowledge — which is decided here.
52
+ *
53
+ * Enforced IN ADDITION to `contribution`, deliberately. The flag is a rig
54
+ * manifest setting, editable in a repo by anyone with commit access; the
55
+ * posture is an account setting chosen at signup and stored on the org row.
56
+ * Two independent facts, either of which is a veto.
57
+ *
58
+ * OMITTED means `pure_router`. The dangerous default is the permissive one,
59
+ * so it is not the default: a caller written before this option existed must
60
+ * not silently start contributing.
61
+ */
62
+ readonly telemetryPosture?: "optimized" | "pure_router";
63
+ }
64
+ /**
65
+ * Whether these rows may become community knowledge.
66
+ *
67
+ * One function so the two-veto rule has a single home and a name, rather than
68
+ * living as a `&&` inside a ternary inside a map.
69
+ */
70
+ export declare function aggregationScope(opts: AggregateOptions): "account" | "community";
71
+ /** cell → model → opponent → record. */
72
+ export type PairwiseByModel = Map<string, Map<string, Map<string, PairwiseRecord>>>;
73
+ /** A2: fold paired replay outcomes from telemetry. A pair exists where the
74
+ * SAME captureId was replayed through two different models within a cell.
75
+ * Sample outcome = did ANY attempt of the latest replay run for that
76
+ * (capture, model) conform — sample-level, retry-inclusive, matching the
77
+ * bake-off's own conformance notion. Single-source deployment folds
78
+ * sources: 1; cross-account distinct counts arrive with the community job. */
79
+ export declare function aggregatePairwiseFromTelemetry(rows: readonly TelemetryRow[]): PairwiseByModel;
80
+ export declare function aggregatePriors(rows: readonly TelemetryRow[], opts: AggregateOptions): PriorsCell[];
81
+ export interface PriorsWriterConfig {
82
+ readonly supabaseUrl: string;
83
+ readonly serviceRoleKey: string;
84
+ readonly fetchFn?: typeof fetch;
85
+ }
86
+ /** Upsert cells into priors_cells (service role — the ONLY principal that
87
+ * can touch the table; RLS denies everyone else including anon). */
88
+ export declare function writePriorsCells(cells: readonly PriorsCell[], config: PriorsWriterConfig): Promise<void>;
89
+ //# sourceMappingURL=aggregate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../src/matrix/aggregate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;+DAE+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,CAAC;IACxC;6EACyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC;CACzD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,GAAG,WAAW,CAGhF;AAED,wCAAwC;AACxC,MAAM,MAAM,eAAe,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;AAQpF;;;;;8EAK8E;AAC9E,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,SAAS,YAAY,EAAE,GAAG,eAAe,CAoF7F;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,IAAI,EAAE,gBAAgB,GACrB,UAAU,EAAE,CA+Cd;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED;oEACoE;AACpE,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,SAAS,UAAU,EAAE,EAC5B,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,IAAI,CAAC,CA0Bf"}