@otto-code/brain 0.7.5

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 (150) hide show
  1. package/bin/otto-brain +2 -0
  2. package/dist/bench/context-corpus.d.ts +58 -0
  3. package/dist/bench/context-corpus.js +210 -0
  4. package/dist/bench/corpus.d.ts +31 -0
  5. package/dist/bench/corpus.js +379 -0
  6. package/dist/bench/curated-repos.d.ts +35 -0
  7. package/dist/bench/curated-repos.js +41 -0
  8. package/dist/bench/health.d.ts +50 -0
  9. package/dist/bench/health.js +112 -0
  10. package/dist/bench/index.d.ts +103 -0
  11. package/dist/bench/index.js +209 -0
  12. package/dist/bench/mine.d.ts +32 -0
  13. package/dist/bench/mine.js +101 -0
  14. package/dist/bench/repo-task.d.ts +93 -0
  15. package/dist/bench/repo-task.js +380 -0
  16. package/dist/bench/repo.d.ts +125 -0
  17. package/dist/bench/repo.js +195 -0
  18. package/dist/bench/rescore.d.ts +41 -0
  19. package/dist/bench/rescore.js +127 -0
  20. package/dist/bench/tasks.d.ts +124 -0
  21. package/dist/bench/tasks.js +1249 -0
  22. package/dist/bench/verify.d.ts +142 -0
  23. package/dist/bench/verify.js +350 -0
  24. package/dist/cli.d.ts +13 -0
  25. package/dist/cli.js +72 -0
  26. package/dist/commands/bench.d.ts +20 -0
  27. package/dist/commands/bench.js +222 -0
  28. package/dist/commands/calibrate.d.ts +19 -0
  29. package/dist/commands/calibrate.js +75 -0
  30. package/dist/commands/catalog.d.ts +32 -0
  31. package/dist/commands/catalog.js +48 -0
  32. package/dist/commands/config.d.ts +13 -0
  33. package/dist/commands/config.js +90 -0
  34. package/dist/commands/lifecycle.d.ts +29 -0
  35. package/dist/commands/lifecycle.js +194 -0
  36. package/dist/commands/pull.d.ts +22 -0
  37. package/dist/commands/pull.js +135 -0
  38. package/dist/commands/report.d.ts +10 -0
  39. package/dist/commands/report.js +34 -0
  40. package/dist/commands/rescore.d.ts +11 -0
  41. package/dist/commands/rescore.js +48 -0
  42. package/dist/commands/runtime.d.ts +21 -0
  43. package/dist/commands/runtime.js +62 -0
  44. package/dist/commands/scan.d.ts +24 -0
  45. package/dist/commands/scan.js +55 -0
  46. package/dist/commands/search.d.ts +31 -0
  47. package/dist/commands/search.js +135 -0
  48. package/dist/commands/share.d.ts +26 -0
  49. package/dist/commands/share.js +148 -0
  50. package/dist/commands/sweep.d.ts +21 -0
  51. package/dist/commands/sweep.js +69 -0
  52. package/dist/commands/ui.d.ts +9 -0
  53. package/dist/commands/ui.js +28 -0
  54. package/dist/config/env.d.ts +10 -0
  55. package/dist/config/env.js +48 -0
  56. package/dist/config/index.d.ts +9 -0
  57. package/dist/config/index.js +9 -0
  58. package/dist/config/otto-home.d.ts +2 -0
  59. package/dist/config/otto-home.js +24 -0
  60. package/dist/config/paths.d.ts +20 -0
  61. package/dist/config/paths.js +33 -0
  62. package/dist/config/private-files.d.ts +3 -0
  63. package/dist/config/private-files.js +32 -0
  64. package/dist/config/profiles.d.ts +34 -0
  65. package/dist/config/profiles.js +116 -0
  66. package/dist/config/schema.d.ts +1035 -0
  67. package/dist/config/schema.js +191 -0
  68. package/dist/config/store.d.ts +11 -0
  69. package/dist/config/store.js +73 -0
  70. package/dist/gguf.d.ts +61 -0
  71. package/dist/gguf.js +231 -0
  72. package/dist/gpu.d.ts +12 -0
  73. package/dist/gpu.js +49 -0
  74. package/dist/index.d.ts +14 -0
  75. package/dist/index.js +13 -0
  76. package/dist/main.d.ts +2 -0
  77. package/dist/main.js +5 -0
  78. package/dist/models/dirs.d.ts +9 -0
  79. package/dist/models/dirs.js +21 -0
  80. package/dist/models/download.d.ts +31 -0
  81. package/dist/models/download.js +105 -0
  82. package/dist/models/enrich.d.ts +36 -0
  83. package/dist/models/enrich.js +68 -0
  84. package/dist/models/hf.d.ts +62 -0
  85. package/dist/models/hf.js +114 -0
  86. package/dist/models/index.d.ts +15 -0
  87. package/dist/models/index.js +49 -0
  88. package/dist/models/manage.d.ts +27 -0
  89. package/dist/models/manage.js +112 -0
  90. package/dist/models/pick.d.ts +3 -0
  91. package/dist/models/pick.js +36 -0
  92. package/dist/models/scan.d.ts +13 -0
  93. package/dist/models/scan.js +185 -0
  94. package/dist/ops/archive.d.ts +34 -0
  95. package/dist/ops/archive.js +105 -0
  96. package/dist/ops/calibrate.d.ts +49 -0
  97. package/dist/ops/calibrate.js +74 -0
  98. package/dist/ops/report.d.ts +26 -0
  99. package/dist/ops/report.js +532 -0
  100. package/dist/ops/results.d.ts +207 -0
  101. package/dist/ops/results.js +250 -0
  102. package/dist/ops/sweep.d.ts +77 -0
  103. package/dist/ops/sweep.js +126 -0
  104. package/dist/output/index.d.ts +5 -0
  105. package/dist/output/index.js +5 -0
  106. package/dist/output/render.d.ts +4 -0
  107. package/dist/output/render.js +96 -0
  108. package/dist/output/types.d.ts +52 -0
  109. package/dist/output/types.js +9 -0
  110. package/dist/output/with-output.d.ts +3 -0
  111. package/dist/output/with-output.js +42 -0
  112. package/dist/run.d.ts +5 -0
  113. package/dist/run.js +19 -0
  114. package/dist/runtime/args.d.ts +21 -0
  115. package/dist/runtime/args.js +71 -0
  116. package/dist/runtime/index.d.ts +13 -0
  117. package/dist/runtime/index.js +41 -0
  118. package/dist/runtime/lmstudio.d.ts +10 -0
  119. package/dist/runtime/lmstudio.js +88 -0
  120. package/dist/runtime/managed.d.ts +26 -0
  121. package/dist/runtime/managed.js +148 -0
  122. package/dist/service/model-selector.d.ts +36 -0
  123. package/dist/service/model-selector.js +123 -0
  124. package/dist/service/pid-lock.d.ts +18 -0
  125. package/dist/service/pid-lock.js +50 -0
  126. package/dist/service/router.d.ts +144 -0
  127. package/dist/service/router.js +746 -0
  128. package/dist/service/scheduler.d.ts +74 -0
  129. package/dist/service/scheduler.js +110 -0
  130. package/dist/service/serve.d.ts +41 -0
  131. package/dist/service/serve.js +278 -0
  132. package/dist/service/supervisor.d.ts +67 -0
  133. package/dist/service/supervisor.js +222 -0
  134. package/dist/service/tailscale.d.ts +19 -0
  135. package/dist/service/tailscale.js +62 -0
  136. package/dist/service/tls.d.ts +77 -0
  137. package/dist/service/tls.js +192 -0
  138. package/dist/sysmon.d.ts +50 -0
  139. package/dist/sysmon.js +103 -0
  140. package/dist/tui/app.d.ts +208 -0
  141. package/dist/tui/app.js +1801 -0
  142. package/dist/tui/screen.d.ts +72 -0
  143. package/dist/tui/screen.js +176 -0
  144. package/dist/types.d.ts +76 -0
  145. package/dist/types.js +7 -0
  146. package/dist/version.d.ts +2 -0
  147. package/dist/version.js +8 -0
  148. package/dist/vram.d.ts +67 -0
  149. package/dist/vram.js +132 -0
  150. package/package.json +58 -0
@@ -0,0 +1,207 @@
1
+ import type { GpuInfo, Model, ModelFeatures } from "../types.js";
2
+ import type { Profile } from "../config/schema.js";
3
+ declare const RESULTS_DIR: string;
4
+ /** Aggregate of a numeric health series (nvidia-smi samples during a run). */
5
+ export interface AggStat {
6
+ avg: number;
7
+ min: number;
8
+ max: number;
9
+ }
10
+ /** System health summary stored alongside a run's score. */
11
+ export interface SystemHealth {
12
+ samples: number;
13
+ gpuUtilPct: AggStat | null;
14
+ tempC: AggStat | null;
15
+ powerW: AggStat | null;
16
+ clockMHz: AggStat | null;
17
+ vramUsedMiB: AggStat | null;
18
+ cpuPct: AggStat | null;
19
+ ramUsedBytes: AggStat | null;
20
+ thermalThrottle: boolean;
21
+ powerThrottle: boolean;
22
+ }
23
+ /** One depth-scaling measurement point (from the context-depth task detail). */
24
+ export interface DepthPoint {
25
+ promptTokens?: number;
26
+ ttftSeconds?: number;
27
+ generatePerSecond?: number;
28
+ }
29
+ /** One graded task inside a live benchmark report (input to `save`). */
30
+ export interface BenchTaskResult {
31
+ id: string;
32
+ category: string;
33
+ weight: number;
34
+ score: number;
35
+ summary: string;
36
+ seconds: number;
37
+ detail?: unknown;
38
+ error?: string | null;
39
+ }
40
+ /** A completed benchmark report, as produced by the bench suite. */
41
+ export interface BenchReport {
42
+ overall: number;
43
+ grade: string;
44
+ seconds: number;
45
+ executedCode: boolean;
46
+ results: BenchTaskResult[];
47
+ vramBytes?: number | null;
48
+ loadSeconds?: number | null;
49
+ archiveId?: string | null;
50
+ system?: SystemHealth | null;
51
+ }
52
+ /** The model identity persisted with a run. */
53
+ export interface RecordModel {
54
+ id: string | null;
55
+ displayName: string;
56
+ quant: string | null;
57
+ arch: string | null;
58
+ sizeBytes: number | null;
59
+ publisher: string | null;
60
+ features: ModelFeatures | null;
61
+ }
62
+ /** The subset of a profile persisted with a run. */
63
+ export interface RecordProfile {
64
+ contextSize: number;
65
+ cacheTypeK: string;
66
+ cacheTypeV: string;
67
+ reasoningBudget: number;
68
+ vision: boolean;
69
+ flashAttention: boolean;
70
+ }
71
+ /** The GPU identity persisted with a run. */
72
+ export interface RecordGpu {
73
+ name: string;
74
+ totalBytes: number;
75
+ driver: string;
76
+ }
77
+ /** One task's score persisted with a run. */
78
+ export interface RecordTask {
79
+ id: string;
80
+ category: string;
81
+ weight: number;
82
+ score: number;
83
+ summary: string;
84
+ seconds: number;
85
+ detail: unknown;
86
+ error: string | null;
87
+ }
88
+ /** One stored benchmark run — the shared shape used across results and report. */
89
+ export interface RunRecord {
90
+ schema: number;
91
+ ranAt: string;
92
+ model: RecordModel;
93
+ profile: RecordProfile | null;
94
+ configKey: string;
95
+ archiveId: string | null;
96
+ gpu: RecordGpu | null;
97
+ system: SystemHealth | null;
98
+ runtime: string | null;
99
+ overall: number;
100
+ grade: string;
101
+ seconds: number;
102
+ executedCode: boolean;
103
+ vramBytes: number | null;
104
+ loadSeconds: number | null;
105
+ tasks: RecordTask[];
106
+ file?: string;
107
+ runIndex?: number;
108
+ }
109
+ /** Arguments to `save`. */
110
+ export interface SaveOptions {
111
+ model: Model | null;
112
+ profile: Profile | null;
113
+ report: BenchReport;
114
+ gpu?: GpuInfo | null;
115
+ runtime?: string | null;
116
+ archiveId?: string | null;
117
+ system?: SystemHealth | null;
118
+ timestamp?: Date;
119
+ }
120
+ /** Result of `save`: the written file path plus the persisted record. */
121
+ export interface SaveResult {
122
+ file: string;
123
+ record: RunRecord;
124
+ }
125
+ /** count / mean / sample-std / min / max of a numeric series. */
126
+ export interface Stats {
127
+ count: number;
128
+ mean: number;
129
+ std: number;
130
+ min: number;
131
+ max: number;
132
+ }
133
+ /** A model+config grouping of runs. */
134
+ export interface Group {
135
+ key: string;
136
+ model: RecordModel;
137
+ configKey: string;
138
+ runs: RunRecord[];
139
+ count: number;
140
+ }
141
+ /** Per-task variance stats within a group (category plus optional stats). */
142
+ export interface VarianceTask {
143
+ category: string;
144
+ count?: number;
145
+ mean?: number;
146
+ std?: number;
147
+ min?: number;
148
+ max?: number;
149
+ }
150
+ /** Per-config spread of overall and per-task scores across repeated runs. */
151
+ export interface VarianceRow {
152
+ key: string;
153
+ model: RecordModel;
154
+ configKey: string;
155
+ count: number;
156
+ overall: Stats | null;
157
+ tasks: Record<string, VarianceTask>;
158
+ }
159
+ /** One ranked entry per model, scored by the mean of its runs. */
160
+ export interface RankedModel {
161
+ id: string | null;
162
+ displayName: string;
163
+ overall: number;
164
+ runs: number;
165
+ std: number;
166
+ grade: string;
167
+ rank?: number;
168
+ }
169
+ /** A task column: an id paired with its display category. */
170
+ export interface TaskColumn {
171
+ id: string;
172
+ category: string;
173
+ }
174
+ declare function slugify(text: string): string;
175
+ /** Stable identity for "same model, same settings", so reruns can be grouped. */
176
+ declare function configKey(profile: Profile | null): string;
177
+ declare function save({ model, profile, report, gpu, runtime, archiveId, system, timestamp, }: SaveOptions): SaveResult;
178
+ /** Every stored run, newest first. */
179
+ declare function loadAll(): RunRecord[];
180
+ /**
181
+ * Collapse history to the most recent run per model+config, which is what you
182
+ * want when comparing: earlier runs of the same setup are superseded.
183
+ */
184
+ declare function latestPerConfig(records?: RunRecord[]): RunRecord[];
185
+ /**
186
+ * Group every run by model+config (all runs kept, not just the latest), and
187
+ * number them oldest→newest so a run can be plotted against run number. Wall
188
+ * time is deliberately not the axis — only how many times we have measured it.
189
+ */
190
+ declare function grouped(records?: RunRecord[]): Group[];
191
+ /** count / mean / sample-std / min / max of a numeric series. */
192
+ declare function stats(values: Array<number | undefined>): Stats | null;
193
+ /**
194
+ * Per-config spread of overall and per-task scores across repeated runs — the
195
+ * measure of whether the benchmark method is itself consistent.
196
+ */
197
+ declare function variance(records?: RunRecord[]): VarianceRow[];
198
+ /**
199
+ * One ranked entry per model, scored by the MEAN of all its runs (so a single
200
+ * noisy or throttled run cannot set a model's rank), best first. `runs` and
201
+ * `std` travel with it so the app can show how well-backed a ranking is.
202
+ */
203
+ declare function rankModels(records?: RunRecord[]): RankedModel[];
204
+ /** All task ids seen across a set of records, in a stable order. */
205
+ declare function taskColumns(records: RunRecord[]): TaskColumn[];
206
+ export { RESULTS_DIR, save, loadAll, latestPerConfig, grouped, variance, stats, rankModels, taskColumns, configKey, slugify, };
207
+ //# sourceMappingURL=results.d.ts.map
@@ -0,0 +1,250 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * Persistent benchmark history.
6
+ *
7
+ * One JSON file per run, so results accumulate across sessions and can be
8
+ * compared and charted later. Runs record the configuration they were measured
9
+ * under, because a score is meaningless without the quant, context size and
10
+ * reasoning budget that produced it.
11
+ */
12
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
13
+ const ROOT = path.resolve(HERE, "..", "..");
14
+ const RESULTS_DIR = path.join(ROOT, "results");
15
+ function slugify(text) {
16
+ return String(text)
17
+ .toLowerCase()
18
+ .replace(/[^a-z0-9]+/g, "-")
19
+ .replace(/^-|-$/g, "")
20
+ .slice(0, 80);
21
+ }
22
+ /** Stable identity for "same model, same settings", so reruns can be grouped. */
23
+ function configKey(profile) {
24
+ if (!profile)
25
+ return "unknown";
26
+ return [
27
+ `ctx${profile.contextSize}`,
28
+ `kv${profile.cacheTypeK}-${profile.cacheTypeV}`,
29
+ `rb${profile.reasoningBudget}`,
30
+ profile.vision ? "vision" : "novision",
31
+ ].join("_");
32
+ }
33
+ function save({ model, profile, report, gpu = null, runtime = null, archiveId = null, system = null, timestamp = new Date(), }) {
34
+ fs.mkdirSync(RESULTS_DIR, { recursive: true });
35
+ const record = {
36
+ schema: 1,
37
+ ranAt: timestamp.toISOString(),
38
+ model: {
39
+ id: model?.id ?? null,
40
+ displayName: model?.displayName ?? "unknown",
41
+ quant: model?.quant ?? null,
42
+ arch: model?.metadata?.arch ?? null,
43
+ sizeBytes: model?.sizeBytes ?? null,
44
+ publisher: model?.publisher ?? null,
45
+ features: model?.features ?? null,
46
+ },
47
+ profile: profile
48
+ ? {
49
+ contextSize: profile.contextSize,
50
+ cacheTypeK: profile.cacheTypeK,
51
+ cacheTypeV: profile.cacheTypeV,
52
+ reasoningBudget: profile.reasoningBudget,
53
+ vision: profile.vision,
54
+ flashAttention: profile.flashAttention,
55
+ }
56
+ : null,
57
+ configKey: configKey(profile),
58
+ // Links this result to its stored transcripts so `rescore` can re-grade it.
59
+ archiveId: archiveId ?? report.archiveId ?? null,
60
+ gpu: gpu ? { name: gpu.name, totalBytes: gpu.totalBytes, driver: gpu.driver } : null,
61
+ // System health sampled during the run, so a score can be read against the
62
+ // machine state that produced it (throttling, power cap, VRAM pressure).
63
+ system: system ?? report.system ?? null,
64
+ runtime,
65
+ overall: report.overall,
66
+ grade: report.grade,
67
+ seconds: report.seconds,
68
+ executedCode: report.executedCode,
69
+ vramBytes: report.vramBytes ?? null,
70
+ loadSeconds: report.loadSeconds ?? null,
71
+ tasks: report.results.map((r) => ({
72
+ id: r.id,
73
+ category: r.category,
74
+ weight: r.weight,
75
+ score: r.score,
76
+ summary: r.summary,
77
+ seconds: r.seconds,
78
+ detail: r.detail ?? null,
79
+ error: r.error ?? null,
80
+ })),
81
+ };
82
+ const stamp = record.ranAt.replace(/[:.]/g, "-");
83
+ const file = path.join(RESULTS_DIR, `${stamp}_${slugify(record.model.displayName)}.json`);
84
+ fs.writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`);
85
+ return { file, record };
86
+ }
87
+ /** Every stored run, newest first. */
88
+ function loadAll() {
89
+ if (!fs.existsSync(RESULTS_DIR))
90
+ return [];
91
+ return fs
92
+ .readdirSync(RESULTS_DIR)
93
+ .filter((f) => f.endsWith(".json"))
94
+ .map((f) => {
95
+ try {
96
+ const record = JSON.parse(fs.readFileSync(path.join(RESULTS_DIR, f), "utf8"));
97
+ return { ...record, file: f };
98
+ }
99
+ catch {
100
+ return null;
101
+ }
102
+ })
103
+ .filter((r) => Boolean(r))
104
+ .sort((a, b) => (a.ranAt < b.ranAt ? 1 : -1));
105
+ }
106
+ /**
107
+ * Collapse history to the most recent run per model+config, which is what you
108
+ * want when comparing: earlier runs of the same setup are superseded.
109
+ */
110
+ function latestPerConfig(records = loadAll()) {
111
+ const best = new Map();
112
+ for (const record of records) {
113
+ const key = `${record.model.displayName}::${record.configKey}`;
114
+ if (!best.has(key))
115
+ best.set(key, record);
116
+ }
117
+ return [...best.values()].sort((a, b) => b.overall - a.overall);
118
+ }
119
+ /**
120
+ * Group every run by model+config (all runs kept, not just the latest), and
121
+ * number them oldest→newest so a run can be plotted against run number. Wall
122
+ * time is deliberately not the axis — only how many times we have measured it.
123
+ */
124
+ function grouped(records = loadAll()) {
125
+ const groups = new Map();
126
+ for (const record of records) {
127
+ const key = `${record.model.displayName}::${record.configKey}`;
128
+ let group = groups.get(key);
129
+ if (!group) {
130
+ group = { key, model: record.model, configKey: record.configKey, runs: [], count: 0 };
131
+ groups.set(key, group);
132
+ }
133
+ group.runs.push(record);
134
+ }
135
+ for (const group of groups.values()) {
136
+ // loadAll is newest-first; number oldest→newest for a left-to-right x-axis.
137
+ [...group.runs].reverse().forEach((run, i) => {
138
+ run.runIndex = i + 1;
139
+ });
140
+ group.count = group.runs.length;
141
+ }
142
+ return [...groups.values()];
143
+ }
144
+ /** count / mean / sample-std / min / max of a numeric series. */
145
+ function stats(values) {
146
+ const nums = values.filter((v) => typeof v === "number" && Number.isFinite(v));
147
+ if (!nums.length)
148
+ return null;
149
+ const mean = nums.reduce((a, b) => a + b, 0) / nums.length;
150
+ const varc = nums.length > 1 ? nums.reduce((a, b) => a + (b - mean) ** 2, 0) / (nums.length - 1) : 0;
151
+ return {
152
+ count: nums.length,
153
+ mean,
154
+ std: Math.sqrt(varc),
155
+ min: Math.min(...nums),
156
+ max: Math.max(...nums),
157
+ };
158
+ }
159
+ /**
160
+ * Per-config spread of overall and per-task scores across repeated runs — the
161
+ * measure of whether the benchmark method is itself consistent.
162
+ */
163
+ function variance(records = loadAll()) {
164
+ return grouped(records)
165
+ .map((group) => {
166
+ const overall = stats(group.runs.map((r) => r.overall));
167
+ const taskMeta = new Map();
168
+ for (const run of group.runs) {
169
+ for (const task of run.tasks)
170
+ if (!taskMeta.has(task.id))
171
+ taskMeta.set(task.id, task.category);
172
+ }
173
+ const tasks = {};
174
+ for (const [id, category] of taskMeta) {
175
+ tasks[id] = {
176
+ category,
177
+ ...stats(group.runs.map((r) => r.tasks.find((t) => t.id === id)?.score)),
178
+ };
179
+ }
180
+ return {
181
+ key: group.key,
182
+ model: group.model,
183
+ configKey: group.configKey,
184
+ count: group.count,
185
+ overall,
186
+ tasks,
187
+ };
188
+ })
189
+ .sort((a, b) => (b.overall?.mean ?? 0) - (a.overall?.mean ?? 0));
190
+ }
191
+ function gradeFor(score) {
192
+ if (score >= 0.9)
193
+ return "excellent";
194
+ if (score >= 0.75)
195
+ return "strong";
196
+ if (score >= 0.55)
197
+ return "usable";
198
+ if (score >= 0.35)
199
+ return "weak";
200
+ return "unusable";
201
+ }
202
+ /**
203
+ * One ranked entry per model, scored by the MEAN of all its runs (so a single
204
+ * noisy or throttled run cannot set a model's rank), best first. `runs` and
205
+ * `std` travel with it so the app can show how well-backed a ranking is.
206
+ */
207
+ function rankModels(records = loadAll()) {
208
+ const byModel = new Map();
209
+ for (const record of records) {
210
+ const key = record.model.id || record.model.displayName;
211
+ if (!byModel.has(key)) {
212
+ byModel.set(key, {
213
+ id: record.model.id ?? null,
214
+ displayName: record.model.displayName,
215
+ scores: [],
216
+ });
217
+ }
218
+ byModel.get(key)?.scores.push(record.overall);
219
+ }
220
+ const ranked = [...byModel.values()]
221
+ .map((m) => {
222
+ const s = stats(m.scores) ?? { count: 0, mean: 0, std: 0, min: 0, max: 0 };
223
+ return {
224
+ id: m.id,
225
+ displayName: m.displayName,
226
+ overall: s.mean,
227
+ runs: s.count,
228
+ std: s.std,
229
+ grade: gradeFor(s.mean),
230
+ };
231
+ })
232
+ .sort((a, b) => b.overall - a.overall);
233
+ ranked.forEach((m, i) => {
234
+ m.rank = i + 1;
235
+ });
236
+ return ranked;
237
+ }
238
+ /** All task ids seen across a set of records, in a stable order. */
239
+ function taskColumns(records) {
240
+ const seen = new Map();
241
+ for (const record of records) {
242
+ for (const task of record.tasks) {
243
+ if (!seen.has(task.id))
244
+ seen.set(task.id, task.category);
245
+ }
246
+ }
247
+ return [...seen.entries()].map(([id, category]) => ({ id, category }));
248
+ }
249
+ export { RESULTS_DIR, save, loadAll, latestPerConfig, grouped, variance, stats, rankModels, taskColumns, configKey, slugify, };
250
+ //# sourceMappingURL=results.js.map
@@ -0,0 +1,77 @@
1
+ import { Supervisor } from "../service/supervisor.js";
2
+ import type { Model, Runtime } from "../types.js";
3
+ import type { Profile } from "../config/schema.js";
4
+ /**
5
+ * Find the reasoning budget that delivers the most useful output for a model.
6
+ *
7
+ * Thinking models default to an unrestricted budget (-1) and will happily burn
8
+ * an entire token allowance reasoning, returning no content at all. The right
9
+ * cap is model-specific, so measure it: run one long-horizon task per candidate
10
+ * budget and score by delivered content per second.
11
+ */
12
+ export declare const DEFAULT_BUDGETS: number[];
13
+ export declare const LONG_TASK: string;
14
+ export declare const EXPECTED_FILES: string[];
15
+ /** The measured outcome of a single generation trial. */
16
+ export interface Trial {
17
+ finishReason: string | null;
18
+ outputTokens: number | null;
19
+ contentChars: number;
20
+ reasoningChars: number;
21
+ filesDelivered: number;
22
+ elapsedSeconds: number;
23
+ tokensPerSecond: number | null;
24
+ contentPerSecond: number;
25
+ }
26
+ interface RunTrialOptions {
27
+ supervisor: Supervisor;
28
+ maxTokens: number;
29
+ temperature: number;
30
+ }
31
+ export declare function runTrial({ supervisor, maxTokens, temperature, }: RunTrialOptions): Promise<Trial>;
32
+ /** Progress event emitted while sweeping reasoning budgets. */
33
+ export interface SweepProgress {
34
+ phase: "loading" | "generating" | "done" | "failed";
35
+ budget: number;
36
+ error?: string;
37
+ finishReason?: string | null;
38
+ outputTokens?: number | null;
39
+ contentChars?: number;
40
+ reasoningChars?: number;
41
+ filesDelivered?: number;
42
+ elapsedSeconds?: number;
43
+ tokensPerSecond?: number | null;
44
+ contentPerSecond?: number;
45
+ }
46
+ /** A single budget's trial, plus its error (null on success). */
47
+ export interface SweepResult {
48
+ budget: number;
49
+ error: string | null;
50
+ finishReason?: string | null;
51
+ outputTokens?: number | null;
52
+ contentChars: number;
53
+ reasoningChars?: number;
54
+ filesDelivered: number;
55
+ elapsedSeconds?: number;
56
+ tokensPerSecond?: number | null;
57
+ contentPerSecond: number;
58
+ }
59
+ export interface SweepReport {
60
+ results: SweepResult[];
61
+ recommended: number | null;
62
+ ranked: SweepResult[];
63
+ sweptAt: string;
64
+ }
65
+ export interface SweepOptions {
66
+ runtime: Runtime;
67
+ model: Model;
68
+ profile: Profile;
69
+ budgets?: number[];
70
+ maxTokens?: number;
71
+ temperature?: number;
72
+ internalPort?: number;
73
+ onProgress?: (event: SweepProgress) => void;
74
+ }
75
+ export declare function sweep({ runtime, model, profile, budgets, maxTokens, temperature, internalPort, onProgress, }: SweepOptions): Promise<SweepReport>;
76
+ export {};
77
+ //# sourceMappingURL=sweep.d.ts.map
@@ -0,0 +1,126 @@
1
+ import { request } from "node:http";
2
+ import { DEFAULT_INTERNAL_PORT, Supervisor } from "../service/supervisor.js";
3
+ /**
4
+ * Find the reasoning budget that delivers the most useful output for a model.
5
+ *
6
+ * Thinking models default to an unrestricted budget (-1) and will happily burn
7
+ * an entire token allowance reasoning, returning no content at all. The right
8
+ * cap is model-specific, so measure it: run one long-horizon task per candidate
9
+ * budget and score by delivered content per second.
10
+ */
11
+ export const DEFAULT_BUDGETS = [0, 512, 1536, 3072, -1];
12
+ export const LONG_TASK = "Write a complete Python implementation of a thread-safe LRU cache with TTL " +
13
+ "expiry. Produce FOUR separate complete files, each fully implemented with no " +
14
+ "placeholders or elisions:\n" +
15
+ "1. lru.py - the cache with get/put/delete/clear, OrderedDict-based, RLock, " +
16
+ "per-entry TTL, and a background sweeper thread\n" +
17
+ "2. metrics.py - hit/miss/eviction counters with a snapshot() method\n" +
18
+ "3. test_lru.py - at least 12 unittest cases covering eviction order, TTL " +
19
+ "expiry, concurrent access, and edge cases\n" +
20
+ "4. README.md - full usage documentation with examples\n" +
21
+ "Write every file out in full. Do not abbreviate anything.";
22
+ export const EXPECTED_FILES = ["lru.py", "metrics.py", "test_lru.py", "README.md"];
23
+ function postJson({ host, port, path: urlPath, payload, timeoutMs = 900000, }) {
24
+ return new Promise((resolve, reject) => {
25
+ const body = JSON.stringify(payload);
26
+ const req = request({
27
+ host,
28
+ port,
29
+ path: urlPath,
30
+ method: "POST",
31
+ headers: { "content-type": "application/json", "content-length": Buffer.byteLength(body) },
32
+ timeout: timeoutMs,
33
+ }, (res) => {
34
+ let text = "";
35
+ res.on("data", (c) => (text += c));
36
+ res.on("end", () => {
37
+ try {
38
+ resolve(JSON.parse(text));
39
+ }
40
+ catch (error) {
41
+ reject(error);
42
+ }
43
+ });
44
+ });
45
+ req.on("timeout", () => {
46
+ req.destroy();
47
+ reject(new Error("request timed out"));
48
+ });
49
+ req.on("error", reject);
50
+ req.end(body);
51
+ });
52
+ }
53
+ export async function runTrial({ supervisor, maxTokens, temperature, }) {
54
+ const started = Date.now();
55
+ const result = (await postJson({
56
+ host: supervisor.host,
57
+ port: supervisor.internalPort,
58
+ path: "/v1/chat/completions",
59
+ payload: {
60
+ messages: [{ role: "user", content: LONG_TASK }],
61
+ max_tokens: maxTokens,
62
+ temperature,
63
+ top_k: 20,
64
+ top_p: 0.95,
65
+ },
66
+ }));
67
+ const elapsedSeconds = (Date.now() - started) / 1000;
68
+ const choice = result.choices?.[0];
69
+ const message = choice?.message;
70
+ const content = message?.content || "";
71
+ const reasoning = message?.reasoning_content || "";
72
+ return {
73
+ finishReason: choice?.finish_reason ?? null,
74
+ outputTokens: result.usage?.completion_tokens ?? null,
75
+ contentChars: content.length,
76
+ reasoningChars: reasoning.length,
77
+ filesDelivered: EXPECTED_FILES.filter((name) => content.includes(name)).length,
78
+ elapsedSeconds,
79
+ tokensPerSecond: result.timings?.predicted_per_second ?? null,
80
+ // The metric that matters: useful output per unit of wall time.
81
+ contentPerSecond: elapsedSeconds > 0 ? content.length / elapsedSeconds : 0,
82
+ };
83
+ }
84
+ export async function sweep({ runtime, model, profile, budgets = DEFAULT_BUDGETS, maxTokens = 8192, temperature = 0.7, internalPort = DEFAULT_INTERNAL_PORT + 2, onProgress = () => { }, }) {
85
+ const results = [];
86
+ for (const budget of budgets) {
87
+ const supervisor = new Supervisor({ runtime, internalPort });
88
+ onProgress({ phase: "loading", budget });
89
+ try {
90
+ await supervisor.start(model, { ...profile, reasoningBudget: budget });
91
+ onProgress({ phase: "generating", budget });
92
+ const trial = await runTrial({ supervisor, maxTokens, temperature });
93
+ results.push({ budget, ...trial, error: null });
94
+ onProgress({ phase: "done", budget, ...trial });
95
+ }
96
+ catch (error) {
97
+ const message = error instanceof Error ? error.message : String(error);
98
+ results.push({
99
+ budget,
100
+ error: message,
101
+ contentChars: 0,
102
+ contentPerSecond: 0,
103
+ filesDelivered: 0,
104
+ });
105
+ onProgress({ phase: "failed", budget, error: message });
106
+ }
107
+ finally {
108
+ await supervisor.stop();
109
+ await new Promise((resolve) => setTimeout(resolve, 2500));
110
+ }
111
+ }
112
+ // Prefer runs that delivered every file; break ties on content per second.
113
+ const viable = results.filter((r) => !r.error && r.contentChars > 0);
114
+ const ranked = [...viable].sort((a, b) => {
115
+ if (b.filesDelivered !== a.filesDelivered)
116
+ return b.filesDelivered - a.filesDelivered;
117
+ return b.contentPerSecond - a.contentPerSecond;
118
+ });
119
+ return {
120
+ results,
121
+ recommended: ranked.length ? ranked[0].budget : null,
122
+ ranked,
123
+ sweptAt: new Date().toISOString(),
124
+ };
125
+ }
126
+ //# sourceMappingURL=sweep.js.map
@@ -0,0 +1,5 @@
1
+ /** Barrel for the output layer. */
2
+ export * from "./types.js";
3
+ export { renderResult, renderError } from "./render.js";
4
+ export { withOutput } from "./with-output.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ /** Barrel for the output layer. */
2
+ export * from "./types.js";
3
+ export { renderResult, renderError } from "./render.js";
4
+ export { withOutput } from "./with-output.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,4 @@
1
+ import type { AnyCommandResult, CommandErrorShape, OutputOptions } from "./types.js";
2
+ export declare function renderResult<T>(result: AnyCommandResult<T>, options: OutputOptions): string;
3
+ export declare function renderError(error: CommandErrorShape, options: OutputOptions): string;
4
+ //# sourceMappingURL=render.d.ts.map