@otto-code/brain 0.8.8 → 0.8.10

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 (45) hide show
  1. package/dist/commands/bench.js +19 -5
  2. package/dist/commands/catalog.d.ts +1 -0
  3. package/dist/commands/catalog.js +1 -0
  4. package/dist/commands/pull.js +14 -33
  5. package/dist/commands/repo-download.d.ts +14 -0
  6. package/dist/commands/repo-download.js +29 -0
  7. package/dist/commands/search.js +6 -14
  8. package/dist/config/builtin-hosting-profiles.d.ts +8 -0
  9. package/dist/config/builtin-hosting-profiles.js +32 -0
  10. package/dist/config/hosting-profiles.d.ts +33 -0
  11. package/dist/config/hosting-profiles.js +71 -0
  12. package/dist/config/index.d.ts +1 -0
  13. package/dist/config/index.js +1 -0
  14. package/dist/config/paths.d.ts +2 -0
  15. package/dist/config/paths.js +1 -0
  16. package/dist/config/profile-edit.d.ts +4 -2
  17. package/dist/config/profile-edit.js +94 -4
  18. package/dist/config/profiles.js +25 -2
  19. package/dist/config/schema.d.ts +494 -24
  20. package/dist/config/schema.js +55 -0
  21. package/dist/config/store.js +12 -2
  22. package/dist/gguf.d.ts +1 -0
  23. package/dist/gguf.js +1 -0
  24. package/dist/models/download.js +5 -0
  25. package/dist/models/enrich.d.ts +6 -0
  26. package/dist/models/enrich.js +50 -2
  27. package/dist/ops/calibrate.d.ts +4 -1
  28. package/dist/ops/calibrate.js +10 -7
  29. package/dist/ops/sweep.d.ts +3 -1
  30. package/dist/ops/sweep.js +3 -3
  31. package/dist/runtime/args.d.ts +2 -2
  32. package/dist/runtime/args.js +13 -1
  33. package/dist/runtime/index.d.ts +7 -0
  34. package/dist/runtime/index.js +10 -0
  35. package/dist/service/host-api.d.ts +17 -1
  36. package/dist/service/host-api.js +196 -13
  37. package/dist/service/router.d.ts +18 -0
  38. package/dist/service/router.js +89 -4
  39. package/dist/service/serve.js +184 -12
  40. package/dist/service/supervisor.d.ts +32 -4
  41. package/dist/service/supervisor.js +30 -6
  42. package/dist/tui/app.js +1 -1
  43. package/dist/types.d.ts +4 -0
  44. package/dist/vram.js +3 -3
  45. package/package.json +1 -1
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * `otto brain bench` - score a model's agentic coding ability on this machine.
3
3
  * Either benchmarks an endpoint that is already serving (`--endpoint`) or loads
4
- * each requested model itself. A long streaming run, so it prints a formatted
5
- * report directly rather than going through the output layer.
4
+ * each requested model itself. Locally loaded runs use the router scheduler, so
5
+ * their requests receive the same profile system addendum as normal serving. A
6
+ * long streaming run, so it prints a formatted report directly rather than going
7
+ * through the output layer.
6
8
  */
7
9
  import http from "node:http";
8
10
  import path from "node:path";
9
- import { forModel, getCalibration, loadBrainConfig, loadProfilesStore } from "../config/index.js";
11
+ import { forModel, getCalibration, loadBrainConfig, loadProfilesStore, resolveBrainPaths, } from "../config/index.js";
10
12
  import { query as queryGpu } from "../gpu.js";
11
13
  import { pickModel, scanModels } from "../models/index.js";
12
14
  import { CommandError } from "../output/types.js";
@@ -190,9 +192,21 @@ async function runBenchSuite(options, _command) {
190
192
  profile = fit.profile;
191
193
  }
192
194
  process.stdout.write(`\n${"=".repeat(74)}\n${model.displayName}\n${"=".repeat(74)}\n`);
193
- const supervisor = new Supervisor({ runtime, internalPort: port });
195
+ const supervisor = new Supervisor({
196
+ runtime,
197
+ internalPort: port,
198
+ paths: resolveBrainPaths(),
199
+ getProfilesStore: () => store,
200
+ });
194
201
  const telemetry = new Telemetry();
195
- const server = http.createServer(createRouter({ supervisor, telemetry, getCatalog: () => catalog }));
202
+ const server = http.createServer(createRouter({
203
+ supervisor,
204
+ telemetry,
205
+ getCatalog: () => catalog,
206
+ loadModel: async (target) => {
207
+ await supervisor.start(target, forModel(store, target, config.defaults));
208
+ },
209
+ }));
196
210
  await new Promise((resolve, reject) => {
197
211
  server.once("error", reject);
198
212
  server.listen(port + 1, "127.0.0.1", resolve);
@@ -13,6 +13,7 @@ import type { AnyCommandResult, OutputSchema } from "../output/index.js";
13
13
  export interface CatalogRow {
14
14
  id: string;
15
15
  name: string;
16
+ family: string | null;
16
17
  installed: boolean;
17
18
  publisher: string;
18
19
  repo: string;
@@ -29,6 +29,7 @@ export async function runCatalogCommand(_options, _command) {
29
29
  const rows = catalog.models.map((model) => ({
30
30
  id: model.id,
31
31
  name: model.name,
32
+ family: model.family ?? null,
32
33
  installed: installedCatalogIds.has(model.id),
33
34
  publisher: model.publisher ?? "",
34
35
  repo: model.hfRepo,
@@ -1,8 +1,9 @@
1
1
  import { loadBrainConfig, loadCatalog } from "../config/index.js";
2
- import { downloadRepoFiles, bundleDownloadPlan, listRepoQuants, managedModelsDir, pullModel, resolveHfToken, } from "../models/index.js";
2
+ import { bundleDownloadPlan, listRepoQuants, managedModelsDir, pullModel, resolveHfToken, } from "../models/index.js";
3
3
  import { CommandError } from "../output/types.js";
4
4
  import { formatBytes } from "../models/scan.js";
5
5
  import { withActivity } from "../service/activity.js";
6
+ import { downloadRepoFilesWithProgress } from "./repo-download.js";
6
7
  const pullSchema = {
7
8
  idField: "model",
8
9
  columns: [
@@ -67,30 +68,16 @@ export async function runPullCommand(modelArg, options, _command) {
67
68
  });
68
69
  }
69
70
  const plan = bundleDownloadPlan(model, options.component ?? [], choice?.files, choice?.sizeBytes);
70
- let lastPct = -1;
71
- const written = await withActivity("download", { target: model.name }, (activity) => downloadRepoFiles({
71
+ const progressLabel = `${model.name}${choice ? ` ${choice.quant}` : ""}`;
72
+ const written = await downloadRepoFilesWithProgress({
73
+ activityTarget: model.name,
74
+ progressLabel,
75
+ totalBytes: plan.totalBytes,
72
76
  repo: plan.repo,
73
77
  files: plan.files,
74
78
  destRoot: managedModelsDir(config),
75
79
  token,
76
- onProgress: (progress) => {
77
- activity.update(plan.totalBytes ? progress.receivedBytes / plan.totalBytes : null);
78
- // The daemon owns the UI job record and only sees this child's
79
- // stdout/stderr. Activity also feeds the host status, but emitting
80
- // here is what keeps the Library's bundle progress ring live.
81
- const pct = plan.totalBytes
82
- ? Math.floor((progress.receivedBytes / plan.totalBytes) * 100)
83
- : 0;
84
- // Network chunks do not land exactly on 5% boundaries. Emit every
85
- // new integer so the daemon receives continuous progress instead of
86
- // often seeing only the initial and final updates.
87
- if (pct > lastPct) {
88
- lastPct = pct;
89
- process.stderr.write(` ${model.name}${choice ? ` ${choice.quant}` : ""}: ${pct}%\r`);
90
- }
91
- },
92
- }));
93
- process.stderr.write("\n");
80
+ });
94
81
  return {
95
82
  type: "single",
96
83
  data: {
@@ -130,22 +117,15 @@ export async function runPullCommand(modelArg, options, _command) {
130
117
  }
131
118
  const files = [...choice.files, ...(mmproj ? mmproj.files : [])];
132
119
  const total = choice.sizeBytes + (mmproj?.sizeBytes ?? 0);
133
- let lastPct = -1;
134
- const written = await withActivity("download", { target: `${model.name} (${choice.quant})` }, (activity) => downloadRepoFiles({
120
+ const written = await downloadRepoFilesWithProgress({
121
+ activityTarget: `${model.name} (${choice.quant})`,
122
+ progressLabel: `${model.name} ${choice.quant}`,
123
+ totalBytes: total,
135
124
  repo: model.hfRepo,
136
125
  files,
137
126
  destRoot: managedModelsDir(config),
138
127
  token,
139
- onProgress: (p) => {
140
- activity.update(total ? p.receivedBytes / total : null);
141
- const pct = total ? Math.floor((p.receivedBytes / total) * 100) : 0;
142
- if (pct > lastPct) {
143
- lastPct = pct;
144
- process.stderr.write(` ${model.name} ${choice.quant}: ${pct}%\r`);
145
- }
146
- },
147
- }));
148
- process.stderr.write("\n");
128
+ });
149
129
  return {
150
130
  type: "single",
151
131
  data: {
@@ -177,6 +157,7 @@ export async function runPullCommand(modelArg, options, _command) {
177
157
  }
178
158
  },
179
159
  }));
160
+ process.stderr.write(` ${model.name}: 100%\r`);
180
161
  process.stderr.write("\n");
181
162
  return {
182
163
  type: "single",
@@ -0,0 +1,14 @@
1
+ /** Shared CLI progress and activity reporting for Hugging Face repo downloads. */
2
+ import { type DownloadFilesOptions } from "../models/index.js";
3
+ export interface RepoDownloadOptions extends DownloadFilesOptions {
4
+ activityTarget: string;
5
+ progressLabel: string;
6
+ totalBytes: number | null;
7
+ }
8
+ /**
9
+ * Download a repo selection while keeping the daemon job ring and host-status
10
+ * activity record in lockstep. Repository size metadata is advisory, so a
11
+ * successful transfer is the only authoritative completion signal.
12
+ */
13
+ export declare function downloadRepoFilesWithProgress({ activityTarget, progressLabel, totalBytes, ...options }: RepoDownloadOptions): Promise<string[]>;
14
+ //# sourceMappingURL=repo-download.d.ts.map
@@ -0,0 +1,29 @@
1
+ /** Shared CLI progress and activity reporting for Hugging Face repo downloads. */
2
+ import { downloadRepoFiles, } from "../models/index.js";
3
+ import { withActivity } from "../service/activity.js";
4
+ /**
5
+ * Download a repo selection while keeping the daemon job ring and host-status
6
+ * activity record in lockstep. Repository size metadata is advisory, so a
7
+ * successful transfer is the only authoritative completion signal.
8
+ */
9
+ export async function downloadRepoFilesWithProgress({ activityTarget, progressLabel, totalBytes, ...options }) {
10
+ let lastPct = -1;
11
+ const written = await withActivity("download", { target: activityTarget }, (activity) => downloadRepoFiles({
12
+ ...options,
13
+ onProgress: (progress) => {
14
+ activity.update(totalBytes ? progress.receivedBytes / totalBytes : null);
15
+ const pct = totalBytes ? Math.floor((progress.receivedBytes / totalBytes) * 100) : 0;
16
+ // A download chunk can jump straight over a five-percent boundary.
17
+ // Report each new integer percent so the UI ring never stalls until
18
+ // completion simply because no chunk hit an exact multiple of five.
19
+ if (pct > lastPct) {
20
+ lastPct = pct;
21
+ process.stderr.write(` ${progressLabel}: ${pct}%\r`);
22
+ }
23
+ },
24
+ }));
25
+ process.stderr.write(` ${progressLabel}: 100%\r`);
26
+ process.stderr.write("\n");
27
+ return written;
28
+ }
29
+ //# sourceMappingURL=repo-download.js.map
@@ -1,7 +1,8 @@
1
1
  import { loadBrainConfig } from "../config/index.js";
2
- import { downloadRepoFiles, listRepoQuants, managedModelsDir, repoOfModel, resolveHfToken, scanModels, searchModels, } from "../models/index.js";
2
+ import { listRepoQuants, managedModelsDir, repoOfModel, resolveHfToken, scanModels, searchModels, } from "../models/index.js";
3
3
  import { formatBytes } from "../models/scan.js";
4
4
  import { CommandError } from "../output/types.js";
5
+ import { downloadRepoFilesWithProgress } from "./repo-download.js";
5
6
  /** What is already on disk, so search + quant listings can mark installed rows. */
6
7
  function installedIndex(config) {
7
8
  const repos = new Set();
@@ -132,24 +133,15 @@ export async function runAddCommand(repo, options, _command) {
132
133
  (!options.primaryOnly && options.component === undefined));
133
134
  const files = [...choice.files, ...(includeProjector ? mmproj.files : [])];
134
135
  const total = choice.sizeBytes + (includeProjector ? mmproj.sizeBytes : 0);
135
- let lastPct = -1;
136
- const written = await downloadRepoFiles({
136
+ const written = await downloadRepoFilesWithProgress({
137
+ activityTarget: `${repo} (${choice.quant})`,
138
+ progressLabel: `${repo} ${choice.quant}`,
139
+ totalBytes: total,
137
140
  repo,
138
141
  files,
139
142
  destRoot: managedModelsDir(config),
140
143
  token,
141
- onProgress: (p) => {
142
- const pct = total ? Math.floor((p.receivedBytes / total) * 100) : 0;
143
- // A download chunk can jump straight over a five-percent boundary.
144
- // Report each new integer percent so the UI ring never stalls until
145
- // completion simply because no chunk hit an exact multiple of five.
146
- if (pct > lastPct) {
147
- lastPct = pct;
148
- process.stderr.write(` ${repo} ${choice.quant}: ${pct}%\r`);
149
- }
150
- },
151
144
  });
152
- process.stderr.write("\n");
153
145
  return {
154
146
  type: "single",
155
147
  data: {
@@ -0,0 +1,8 @@
1
+ import type { ProfilesStore } from "./schema.js";
2
+ /**
3
+ * Product-owned ids are upgraded wholesale, while records with unknown ids remain
4
+ * user-owned. Do not merge profile fields: stale template bytes or prose must not
5
+ * survive an Otto upgrade.
6
+ */
7
+ export declare function seedBuiltinHostingProfiles(store: ProfilesStore): boolean;
8
+ //# sourceMappingURL=builtin-hosting-profiles.d.ts.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Curated Brain-owned hosting profiles. Source: Apache-2.0
3
+ * https://huggingface.co/peculiar-ragdoll/Qwen-Sharp-Chat-Templates
4
+ */
5
+ import { Buffer } from "node:buffer";
6
+ const QWEN_SHARP_TEMPLATE_BASE64 = "eyUtIHNldCB0ZW1wbGF0ZV92ZXJzaW9uID0gInF3ZW4zLjYtZnJvZ2dlcmljLXYyMS4zIiAlfQp7JS0gc2V0IF90b29sX2Zvcm1hdCA9IHRvb2xfY2FsbF9mb3JtYXQgaWYgdG9vbF9jYWxsX2Zvcm1hdCBpcyBkZWZpbmVkIGVsc2UgJ3htbCcgJX0KeyUtIHNldCBpbWFnZV9jb3VudCA9IG5hbWVzcGFjZSh2YWx1ZT0wKSAlfQp7JS0gc2V0IHZpZGVvX2NvdW50ID0gbmFtZXNwYWNlKHZhbHVlPTApICV9CnslLSBzZXQgYWRkX3Zpc2lvbl9pZCA9IGFkZF92aXNpb25faWQgaWYgYWRkX3Zpc2lvbl9pZCBpcyBkZWZpbmVkIGVsc2UgZmFsc2UgJX0KeyUtIHNldCBlbmFibGVfdGhpbmtpbmcgPSBlbmFibGVfdGhpbmtpbmcgaWYgZW5hYmxlX3RoaW5raW5nIGlzIGRlZmluZWQgZWxzZSB0cnVlICV9CnslLSBzZXQgYXV0b19kaXNhYmxlX3RoaW5raW5nX3dpdGhfdG9vbHMgPSBhdXRvX2Rpc2FibGVfdGhpbmtpbmdfd2l0aF90b29scyBpZiBhdXRvX2Rpc2FibGVfdGhpbmtpbmdfd2l0aF90b29scyBpcyBkZWZpbmVkIGVsc2UgZmFsc2UgJX0KeyUtIHNldCBfcHJlc2VydmVfdGhpbmtpbmcgPSBwcmVzZXJ2ZV90aGlua2luZyBpZiBwcmVzZXJ2ZV90aGlua2luZyBpcyBkZWZpbmVkIGVsc2UgdHJ1ZSAlfQp7JS0gc2V0IG1heF90b29sX2FyZ19jaGFycyA9IG1heF90b29sX2FyZ19jaGFycyBpZiBtYXhfdG9vbF9hcmdfY2hhcnMgaXMgZGVmaW5lZCBlbHNlIDAgJX0KeyUtIHNldCBtYXhfdG9vbF9yZXNwb25zZV9jaGFycyA9IG1heF90b29sX3Jlc3BvbnNlX2NoYXJzIGlmIG1heF90b29sX3Jlc3BvbnNlX2NoYXJzIGlzIGRlZmluZWQgZWxzZSAwICV9CnslLSBzZXQgX2hhc190b29scyA9ICh0b29scyBpcyBkZWZpbmVkIGFuZCB0b29scyBhbmQgdG9vbHMgaXMgaXRlcmFibGUgYW5kIHRvb2xzIGlzIG5vdCBtYXBwaW5nKSAlfQp7JS0gc2V0IG5zX3N0YXRlID0gbmFtZXNwYWNlKHRoaW5raW5nPWVuYWJsZV90aGlua2luZykgJX0KeyUtIGlmIGF1dG9fZGlzYWJsZV90aGlua2luZ193aXRoX3Rvb2xzIGFuZCBfaGFzX3Rvb2xzICV9CiAgICB7JS0gc2V0IG5zX3N0YXRlLnRoaW5raW5nID0gZmFsc2UgJX0KeyUtIGVuZGlmICV9CnslLSBtYWNybyByZW5kZXJfY29udGVudChjb250ZW50LCBkb192aXNpb25fY291bnQsIGlzX3N5c3RlbV9jb250ZW50PWZhbHNlKSAlfQogICAgeyUtIGlmIGNvbnRlbnQgaXMgc3RyaW5nICV9CiAgICAgICAge3stIGNvbnRlbnQgfX0KICAgIHslLSBlbGlmIGNvbnRlbnQgaXMgaXRlcmFibGUgYW5kIGNvbnRlbnQgaXMgbm90IG1hcHBpbmcgJX0KICAgICAgICB7JS0gZm9yIGl0ZW0gaW4gY29udGVudCAlfQogICAgICAgICAgICB7JS0gaWYgaXRlbSBpcyBtYXBwaW5nICV9CiAgICAgICAgICAgICAgICB7JS0gaWYgaXRlbS50eXBlID09ICdpbWFnZScgb3IgJ2ltYWdlJyBpbiBpdGVtIG9yICdpbWFnZV91cmwnIGluIGl0ZW0gJX0KICAgICAgICAgICAgICAgICAgICB7JS0gaWYgaXNfc3lzdGVtX2NvbnRlbnQgJX0KICAgICAgICAgICAgICAgICAgICAgICAge3stIHJhaXNlX2V4Y2VwdGlvbignU3lzdGVtIG1lc3NhZ2UgY2Fubm90IGNvbnRhaW4gaW1hZ2VzLicpIH19CiAgICAgICAgICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIGlmIGRvX3Zpc2lvbl9jb3VudCAlfQogICAgICAgICAgICAgICAgICAgICAgICB7JS0gc2V0IGltYWdlX2NvdW50LnZhbHVlID0gaW1hZ2VfY291bnQudmFsdWUgKyAxICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIGlmIGFkZF92aXNpb25faWQgJX0KICAgICAgICAgICAgICAgICAgICAgICAge3stICdQaWN0dXJlICcgfiBpbWFnZV9jb3VudC52YWx1ZSB+ICc6ICcgfX0KICAgICAgICAgICAgICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICAgICAgICAgICAgICB7ey0gJzx8dmlzaW9uX3N0YXJ0fD48fGltYWdlX3BhZHw+PHx2aXNpb25fZW5kfD4nIH19CiAgICAgICAgICAgICAgICB7JS0gZWxpZiBpdGVtLnR5cGUgPT0gJ3ZpZGVvJyBvciAndmlkZW8nIGluIGl0ZW0gJX0KICAgICAgICAgICAgICAgICAgICB7JS0gaWYgaXNfc3lzdGVtX2NvbnRlbnQgJX0KICAgICAgICAgICAgICAgICAgICAgICAge3stIHJhaXNlX2V4Y2VwdGlvbignU3lzdGVtIG1lc3NhZ2UgY2Fubm90IGNvbnRhaW4gdmlkZW9zLicpIH19CiAgICAgICAgICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIGlmIGRvX3Zpc2lvbl9jb3VudCAlfQogICAgICAgICAgICAgICAgICAgICAgICB7JS0gc2V0IHZpZGVvX2NvdW50LnZhbHVlID0gdmlkZW9fY291bnQudmFsdWUgKyAxICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIGlmIGFkZF92aXNpb25faWQgJX0KICAgICAgICAgICAgICAgICAgICAgICAge3stICdWaWRlbyAnIH4gdmlkZW9fY291bnQudmFsdWUgfiAnOiAnIH19CiAgICAgICAgICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgICAgICAgICAgICAge3stICc8fHZpc2lvbl9zdGFydHw+PHx2aWRlb19wYWR8Pjx8dmlzaW9uX2VuZHw+JyB9fQogICAgICAgICAgICAgICAgeyUtIGVsaWYgJ3RleHQnIGluIGl0ZW0gJX0KICAgICAgICAgICAgICAgICAgICB7ey0gaXRlbS50ZXh0IH19CiAgICAgICAgICAgICAgICB7JS0gZWxzZSAlfQogICAgICAgICAgICAgICAgICAgIHt7LSByYWlzZV9leGNlcHRpb24oJ1VuZXhwZWN0ZWQgaXRlbSB0eXBlIGluIGNvbnRlbnQuJykgfX0KICAgICAgICAgICAgICAgIHslLSBlbmRpZiAlfQogICAgICAgICAgICB7JS0gZWxzZSAlfQogICAgICAgICAgICAgICAge3stIGl0ZW0gfCBzdHJpbmcgfX0KICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgeyUtIGVuZGZvciAlfQogICAgeyUtIGVsaWYgY29udGVudCBpcyBub25lIG9yIGNvbnRlbnQgaXMgdW5kZWZpbmVkICV9CiAgICAgICAge3stICcnIH19CiAgICB7JS0gZWxzZSAlfQogICAgICAgIHt7LSByYWlzZV9leGNlcHRpb24oJ1VuZXhwZWN0ZWQgY29udGVudCB0eXBlLicpIH19CiAgICB7JS0gZW5kaWYgJX0KeyUtIGVuZG1hY3JvICV9CnslLSBpZiBub3QgbWVzc2FnZXMgJX0KICAgIHt7LSByYWlzZV9leGNlcHRpb24oJ05vIG1lc3NhZ2VzIHByb3ZpZGVkLicpIH19CnslLSBlbmRpZiAlfQp7JS0gc2V0IF9maXJzdF9yb2xlID0gbWVzc2FnZXNbMF0ucm9sZSAlfQp7JS0gaWYgX2ZpcnN0X3JvbGUgPT0gJ3N5c3RlbScgb3IgX2ZpcnN0X3JvbGUgPT0gJ2RldmVsb3BlcicgJX0KICAgIHslLSBzZXQgX3N5c19tc2cgPSBtZXNzYWdlc1swXSAlfQogICAgeyUtIHNldCBfbXNncyA9IG1lc3NhZ2VzWzE6XSAlfQp7JS0gZWxzZSAlfQogICAgeyUtIHNldCBfc3lzX21zZyA9IG5vbmUgJX0KICAgIHslLSBzZXQgX21zZ3MgPSBtZXNzYWdlcyAlfQp7JS0gZW5kaWYgJX0KeyUtIHNldCBfc2MgPSAnJyAlfQp7JS0gaWYgX3N5c19tc2cgaXMgbm90IG5vbmUgJX0KICAgIHslLSBzZXQgX3NjID0gcmVuZGVyX2NvbnRlbnQoX3N5c19tc2cuY29udGVudCwgZmFsc2UsIHRydWUpIHwgdHJpbSAlfQogICAgeyUtIGlmICc8fHRoaW5rX29mZnw+JyBpbiBfc2MgJX0KICAgICAgICB7JS0gc2V0IG5zX3N0YXRlLnRoaW5raW5nID0gZmFsc2UgJX0KICAgICAgICB7JS0gc2V0IF9zYyA9IF9zYy5zcGxpdCgnPHx0aGlua19vZmZ8PicpIHwgam9pbignJykgfCB0cmltICV9CiAgICB7JS0gZWxpZiAnPHx0aGlua19vbnw+JyBpbiBfc2MgJX0KICAgICAgICB7JS0gc2V0IG5zX3N0YXRlLnRoaW5raW5nID0gdHJ1ZSAlfQogICAgICAgIHslLSBzZXQgX3NjID0gX3NjLnNwbGl0KCc8fHRoaW5rX29ufD4nKSB8IGpvaW4oJycpIHwgdHJpbSAlfQogICAgeyUtIGVuZGlmICV9CnslLSBlbmRpZiAlfQp7JS0gc2V0IF90ZXJzZSAlfQpBbnN3ZXIgZGlyZWN0bHksIGFmdGVyIHRoaW5raW5nLiBMZWFkIHdpdGggdGhlIGFuc3dlciwgdGhlbiBvbmx5IHdoYXQgaXQgbmVlZHMgdG8gYmUgY29ycmVjdCBhbmQgdXNhYmxlLgpOZXZlcjogb3BlbiB3aXRoIHByZWFtYmxlIG9yIHBsZWFzYW50cmllczsgcmVzdGF0ZSB0aGUgcXVlc3Rpb247IGFkZCBmaWxsZXIgdHJhbnNpdGlvbnM7IGhlZGdlIHdpdGggbmljZXRpZXM7IG9yIHJlcGVhdCBhIHBvaW50IHlvdSd2ZSBhbHJlYWR5IG1hZGUuCkFsd2F5czoga2VlcCBlc3NlbnRpYWwgc3RlcHMsIGNhdmVhdHMsIHVuY2VydGFpbnRpZXMsIGFuZCBzcGVjaWZpY3Mg4oCUIG5ldmVyIGRyb3AgY29ycmVjdG5lc3Mgb3IgYSBuZWVkZWQgd2FybmluZyBmb3IgYnJldml0eS4gS2VlcCB0aGUgZmluYWwgYW5zd2VyIGxlYW4uIFVzZSB0aGUgbGVhc3Qgc3RydWN0dXJlIHRoYXQgY29udmV5cyBpdCAocGxhaW4gcHJvc2Ugd2hlbiBzaG9ydDsgbGlzdHMgb3IgY29kZSBvbmx5IHdoZW4gdGhleSBlYXJuIHRoZWlyIHBsYWNlKS4gSWYgZ2VudWluZWx5IHVuY2VydGFpbiwgc2F5IHNvIGFuZCBleHBsYWluIHdoeSDigJQgbmV2ZXIgb21pdCB1bmNlcnRhaW50eSBmb3IgdGhlIHNha2Ugb2YgYnJldml0eS4KSWYgYSB1c2VyIHJlcXVlc3QgaXMgZ2VudWluZWx5IGFtYmlndW91cywgYXNrIGEgc2hhcnAgcXVlc3Rpb24sIGRvbid0IGd1ZXNzLgp7JS0gZW5kc2V0ICV9CnslLSBpZiBub3QgX3NjICV9CiAgICB7JS0gc2V0IF9zYyA9IF90ZXJzZSB8IHRyaW0gJX0KeyUtIGVsc2UgJX0KICAgIHslLSBzZXQgX3NjID0gKF9zYyB8IHRyaW0pIH4gJ1xuXG4nIH4gKF90ZXJzZSB8IHRyaW0pICV9CnslLSBlbmRpZiAlfQp7JS0gaWYgX2hhc190b29scyAlfQogICAge3stICc8fGltX3N0YXJ0fD5zeXN0ZW1cbicgfX0KICAgIHt7LSAnIyBUb29sc1xuXG5Zb3UgaGF2ZSBhY2Nlc3MgdG8gdGhlIGZvbGxvd2luZyBmdW5jdGlvbnM6XG5cbjx0b29scz4nIH19CiAgICB7JS0gZm9yIHRvb2wgaW4gdG9vbHMgJX0KICAgICAgICB7ey0gJ1xuJyB9fQogICAgICAgIHt7LSB0b29sIHwgdG9qc29uIH19CiAgICB7JS0gZW5kZm9yICV9CiAgICB7ey0gJ1xuPC90b29scz4nIH19CiAgICB7JS0gc2V0IHRvb2xfaW5zdHJ1Y3Rpb25zICV9CklmIHlvdSBjaG9vc2UgdG8gY2FsbCBhIGZ1bmN0aW9uIE9OTFkgcmVwbHkgaW4gdGhlIGZvbGxvd2luZyBmb3JtYXQgd2l0aCBOTyBzdWZmaXg6Cgp7JS0gaWYgX3Rvb2xfZm9ybWF0ID09ICdqc29uJyAlfQo8dGhpbms+CkJyaWVmIGV4cGxhbmF0aW9uIG9mIHRvb2wgY2FsbAo8L3RoaW5rPgo8dG9vbF9jYWxsPgp7Im5hbWUiOiAiZXhhbXBsZV9mdW5jdGlvbl9uYW1lIiwgImFyZ3VtZW50cyI6IHsiZXhhbXBsZV9wYXJhbWV0ZXJfMSI6ICJ2YWx1ZV8xIiwgImV4YW1wbGVfcGFyYW1ldGVyXzIiOiAiVGhpcyBpcyB0aGUgdmFsdWUgZm9yIHRoZSBzZWNvbmQgcGFyYW1ldGVyIn19CjwvdG9vbF9jYWxsPgp7JS0gZWxzZSAlfQo8dGhpbms+CkJyaWVmIGV4cGxhbmF0aW9uIG9mIHRvb2wgY2FsbAo8L3RoaW5rPgo8dG9vbF9jYWxsPgo8ZnVuY3Rpb249ZXhhbXBsZV9mdW5jdGlvbl9uYW1lPgo8cGFyYW1ldGVyPWV4YW1wbGVfcGFyYW1ldGVyXzE+CnZhbHVlXzEKPC9wYXJhbWV0ZXI+CjxwYXJhbWV0ZXI9ZXhhbXBsZV9wYXJhbWV0ZXJfMj4KVGhpcyBpcyB0aGUgdmFsdWUgZm9yIHRoZSBzZWNvbmQgcGFyYW1ldGVyCnRoYXQgY2FuIHNwYW4KbXVsdGlwbGUgbGluZXMKPC9wYXJhbWV0ZXI+CjwvZnVuY3Rpb24+CjwvdG9vbF9jYWxsPgp7JS0gZW5kaWYgJX0KCjxJTVBPUlRBTlQ+ClJlbWluZGVyOgotIFlvdSBjYW4gdXNlIHRoZSA8dGhpbms+PC90aGluaz4gYmxvY2sgdG8gcGxhbiB5b3VyIG5leHQgdG9vbCBjYWxsIE9SIHRvIHN5bnRoZXNpemUgZGF0YSBhbmQgZm9ybXVsYXRlIHlvdXIgZmluYWwgcmVzcG9uc2UgdG8gdGhlIHVzZXIuCi0gQUxMIGV4cGxhbmF0aW9uIGFuZCByZWFzb25pbmcgTVVTVCBiZSBwbGFjZWQgc3RyaWN0bHkgaW5zaWRlIHRoZSA8dGhpbms+PC90aGluaz4gYmxvY2suCnslLSBpZiBfdG9vbF9mb3JtYXQgPT0gJ2pzb24nICV9Ci0gRnVuY3Rpb24gY2FsbHMgTVVTVCBmb2xsb3cgdGhlIHNwZWNpZmllZCBmb3JtYXQ6IGEgc2luZ2xlIEpTT04gb2JqZWN0IHdpdGggIm5hbWUiIGFuZCAiYXJndW1lbnRzIiBrZXlzIGluc2lkZSA8dG9vbF9jYWxsPjwvdG9vbF9jYWxsPiBYTUwgdGFncy4KeyUtIGVsc2UgJX0KLSBGdW5jdGlvbiBjYWxscyBNVVNUIGZvbGxvdyB0aGUgc3BlY2lmaWVkIGZvcm1hdDogYW4gaW5uZXIgPGZ1bmN0aW9uPS4uLj48L2Z1bmN0aW9uPiBibG9jayBtdXN0IGJlIG5lc3RlZCB3aXRoaW4gPHRvb2xfY2FsbD48L3Rvb2xfY2FsbD4gWE1MIHRhZ3MuCnslLSBlbmRpZiAlfQotIElmIHlvdSBjaG9vc2UgdG8gY2FsbCBhIHRvb2wsIHlvdSBNVVNUIG91dHB1dCB0aGUgPHRvb2xfY2FsbD4gYmxvY2sgSU1NRURJQVRFTFkgYWZ0ZXIgdGhpbmtpbmcsIHdpdGggTk8gY29udmVyc2F0aW9uYWwgdGV4dCBiZWZvcmUgaXQuCnslLSBpZiBfdG9vbF9mb3JtYXQgPT0gJ2pzb24nICV9Ci0gVGhlIDx0b29sX2NhbGw+IHRhZyBNVVNUIGJlIGF0IHRoZSB2ZXJ5IGJlZ2lubmluZyBvZiBhIG5ldyBsaW5lLCB3aXRoIE5PIHNwYWNlcyBvciBpbmRlbnRhdGlvbiBiZWZvcmUgaXQuCnslLSBlbHNlICV9Ci0gVGhlIDx0b29sX2NhbGw+IGFuZCA8ZnVuY3Rpb24+IHRhZ3MgTVVTVCBiZSBhdCB0aGUgdmVyeSBiZWdpbm5pbmcgb2YgYSBuZXcgbGluZSwgd2l0aCBOTyBzcGFjZXMgb3IgaW5kZW50YXRpb24gYmVmb3JlIHRoZW0uCnslLSBlbmRpZiAlfQotIFRvIGNhbGwgbXVsdGlwbGUgZnVuY3Rpb25zLCBvdXRwdXQgYSBzZXBhcmF0ZSwgY29tcGxldGVseSBjbG9zZWQgPHRvb2xfY2FsbD48L3Rvb2xfY2FsbD4gYmxvY2sgZm9yIEVBQ0ggZnVuY3Rpb24uIERvIE5PVCBuZXN0IDx0b29sX2NhbGw+IGJsb2Nrcy4KLSBJZiB5b3UgaGF2ZSBhbGwgbmVjZXNzYXJ5IGRhdGEsIHByb3ZpZGUgeW91ciBmaW5hbCBhbnN3ZXIgZGlyZWN0bHkgdG8gdGhlIHVzZXIgd2l0aG91dCBhbnkgdG9vbCBjYWxsLgo8L0lNUE9SVEFOVD4KICAgIHslLSBlbmRzZXQgJX0KICAgIHt7LSAnXG5cbicgfiB0b29sX2luc3RydWN0aW9ucyB8IHRyaW0gfX0KICAgIHslLSBpZiBfc2MgJX0KICAgICAgICB7ey0gJ1xuXG4nICsgX3NjIH19CiAgICB7JS0gZW5kaWYgJX0KICAgIHt7LSAnPHxpbV9lbmR8PlxuJyB9fQp7JS0gZWxzZSAlfQogICAgeyUtIGlmIF9zYyAlfQogICAgICAgIHt7LSAnPHxpbV9zdGFydHw+c3lzdGVtXG4nICsgX3NjICsgJzx8aW1fZW5kfD5cbicgfX0KICAgIHslLSBlbmRpZiAlfQp7JS0gZW5kaWYgJX0KeyUtIHNldCBfbGFzdF9pZHggPSBfbXNncyB8IGxlbmd0aCAtIDEgJX0KeyUtIHNldCBucyA9IG5hbWVzcGFjZShtdWx0aV9zdGVwX3Rvb2w9dHJ1ZSwgbGFzdF9xdWVyeV9pbmRleD1fbGFzdF9pZHgpICV9CnslLSBmb3IgbWVzc2FnZSBpbiBfbXNnc1s6Oi0xXSAlfQogICAgeyUtIHNldCBpbmRleCA9IChfbXNncyB8IGxlbmd0aCAtIDEpIC0gbG9vcC5pbmRleDAgJX0KICAgIHslLSBpZiBucy5tdWx0aV9zdGVwX3Rvb2wgYW5kIG1lc3NhZ2Uucm9sZSA9PSAndXNlcicgJX0KICAgICAgICB7JS0gc2V0IF9yYyA9IHJlbmRlcl9jb250ZW50KG1lc3NhZ2UuY29udGVudCwgZmFsc2UpIHwgdHJpbSAlfQogICAgICAgIHslLSBpZiBub3QgKF9yYy5zdGFydHN3aXRoKCc8dG9vbF9yZXNwb25zZT4nKSBhbmQgX3JjLmVuZHN3aXRoKCc8L3Rvb2xfcmVzcG9uc2U+JykpICV9CiAgICAgICAgICAgIHslLSBzZXQgbnMubXVsdGlfc3RlcF90b29sID0gZmFsc2UgJX0KICAgICAgICAgICAgeyUtIHNldCBucy5sYXN0X3F1ZXJ5X2luZGV4ID0gaW5kZXggJX0KICAgICAgICB7JS0gZW5kaWYgJX0KICAgIHslLSBlbmRpZiAlfQp7JS0gZW5kZm9yICV9CnslLSBpZiBucy5tdWx0aV9zdGVwX3Rvb2wgJX0KICAgIHslLSBpZiBfbGFzdF9pZHggPiA1MCAlfQogICAgICAgIHslLSBzZXQgbnMubGFzdF9xdWVyeV9pbmRleCA9IF9sYXN0X2lkeCAlfQogICAgeyUtIGVsc2UgJX0KICAgICAgICB7JS0gc2V0IG5zLmxhc3RfcXVlcnlfaW5kZXggPSAwICV9CiAgICB7JS0gZW5kaWYgJX0KeyUtIGVuZGlmICV9CnslLSBzZXQgbnMyID0gbmFtZXNwYWNlKHByZXZfcm9sZT0nJywgY29uc2VjdXRpdmVfZmFpbHVyZXM9MCkgJX0KeyUtIGZvciBtZXNzYWdlIGluIF9tc2dzICV9CiAgICB7JS0gc2V0IGlzX3N5c3RlbSA9IChtZXNzYWdlLnJvbGUgPT0gInN5c3RlbSIgb3IgbWVzc2FnZS5yb2xlID09ICJkZXZlbG9wZXIiKSAlfQogICAgeyUtIHNldCBjb250ZW50ID0gcmVuZGVyX2NvbnRlbnQobWVzc2FnZS5jb250ZW50LCB0cnVlLCBpc19zeXN0ZW0pIHwgdHJpbSAlfQogICAgeyUtIGlmIGlzX3N5c3RlbSBvciBtZXNzYWdlLnJvbGUgPT0gJ3VzZXInICV9CiAgICAgICAgeyUtIGlmICc8fHRoaW5rX29mZnw+JyBpbiBjb250ZW50ICV9CiAgICAgICAgICAgIHslLSBzZXQgbnNfc3RhdGUudGhpbmtpbmcgPSBmYWxzZSAlfQogICAgICAgICAgICB7JS0gc2V0IGNvbnRlbnQgPSBjb250ZW50LnNwbGl0KCc8fHRoaW5rX29mZnw+JykgfCBqb2luKCcnKSB8IHRyaW0gJX0KICAgICAgICB7JS0gZWxpZiAnPHx0aGlua19vbnw+JyBpbiBjb250ZW50ICV9CiAgICAgICAgICAgIHslLSBzZXQgbnNfc3RhdGUudGhpbmtpbmcgPSB0cnVlICV9CiAgICAgICAgICAgIHslLSBzZXQgY29udGVudCA9IGNvbnRlbnQuc3BsaXQoJzx8dGhpbmtfb258PicpIHwgam9pbignJykgfCB0cmltICV9CiAgICAgICAgeyUtIGVuZGlmICV9CiAgICB7JS0gZW5kaWYgJX0KICAgIHslLSBpZiBpc19zeXN0ZW0gJX0KICAgICAgICB7ey0gJzx8aW1fc3RhcnR8PnN5c3RlbVxuJyArIGNvbnRlbnQgKyAnPHxpbV9lbmR8PlxuJyB9fQogICAgeyUtIGVsaWYgbWVzc2FnZS5yb2xlID09ICd1c2VyJyAlfQogICAgICAgIHslLSBzZXQgbnMyLmNvbnNlY3V0aXZlX2ZhaWx1cmVzID0gMCAlfQogICAgICAgIHt7LSAnPHxpbV9zdGFydHw+dXNlclxuJyArIGNvbnRlbnQgKyAnPHxpbV9lbmR8PlxuJyB9fQogICAgeyUtIGVsaWYgbWVzc2FnZS5yb2xlID09ICdhc3Npc3RhbnQnICV9CiAgICAgICAgeyUtIHNldCByZWFzb25pbmdfY29udGVudCA9ICcnICV9CiAgICAgICAgeyUtIGlmIG1lc3NhZ2UucmVhc29uaW5nX2NvbnRlbnQgaXMgZGVmaW5lZCBhbmQgbWVzc2FnZS5yZWFzb25pbmdfY29udGVudCBpcyBub3Qgbm9uZSAlfQogICAgICAgICAgICB7JS0gaWYgbWVzc2FnZS5yZWFzb25pbmdfY29udGVudCBpcyBzdHJpbmcgJX0KICAgICAgICAgICAgICAgIHslLSBzZXQgcmVhc29uaW5nX2NvbnRlbnQgPSBtZXNzYWdlLnJlYXNvbmluZ19jb250ZW50ICV9CiAgICAgICAgICAgIHslLSBlbHNlICV9CiAgICAgICAgICAgICAgICB7JS0gc2V0IHJlYXNvbmluZ19jb250ZW50ID0gbWVzc2FnZS5yZWFzb25pbmdfY29udGVudCB8IHN0cmluZyAlfQogICAgICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICB7JS0gZWxpZiBtZXNzYWdlLnRoaW5raW5nIGlzIGRlZmluZWQgYW5kIG1lc3NhZ2UudGhpbmtpbmcgaXMgbm90IG5vbmUgJX0KICAgICAgICAgICAgeyUtIGlmIG1lc3NhZ2UudGhpbmtpbmcgaXMgc3RyaW5nICV9CiAgICAgICAgICAgICAgICB7JS0gc2V0IHJlYXNvbmluZ19jb250ZW50ID0gbWVzc2FnZS50aGlua2luZyAlfQogICAgICAgICAgICB7JS0gZWxzZSAlfQogICAgICAgICAgICAgICAgeyUtIHNldCByZWFzb25pbmdfY29udGVudCA9IG1lc3NhZ2UudGhpbmtpbmcgfCBzdHJpbmcgJX0KICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgeyUtIGVsc2UgJX0KICAgICAgICAgICAgeyUtIHNldCBfdGhpbmtfZW5kID0gJycgJX0KICAgICAgICAgICAgeyUtIGlmIGNvbnRlbnQuc3RhcnRzd2l0aCgnPC90aGluaz4nKSAlfQogICAgICAgICAgICAgICAgeyUtIHNldCBfdGhpbmtfZW5kID0gJzwvdGhpbms+JyAlfQogICAgICAgICAgICB7JS0gZWxpZiBjb250ZW50LnN0YXJ0c3dpdGgoJzwvdGhpbmtpbmc+JykgJX0KICAgICAgICAgICAgICAgIHslLSBzZXQgX3RoaW5rX2VuZCA9ICc8L3RoaW5raW5nPicgJX0KICAgICAgICAgICAgeyUtIGVsaWYgJ1xuPC90aGluaz4nIGluIGNvbnRlbnQgJX0KICAgICAgICAgICAgICAgIHslLSBzZXQgX3RoaW5rX2VuZCA9ICdcbjwvdGhpbms+JyAlfQogICAgICAgICAgICB7JS0gZWxpZiAnXG48L3RoaW5raW5nPicgaW4gY29udGVudCAlfQogICAgICAgICAgICAgICAgeyUtIHNldCBfdGhpbmtfZW5kID0gJ1xuPC90aGlua2luZz4nICV9CiAgICAgICAgICAgIHslLSBlbGlmICdcbjwvIHRoaW5rPicgaW4gY29udGVudCAlfQogICAgICAgICAgICAgICAgeyUtIHNldCBfdGhpbmtfZW5kID0gJ1xuPC8gdGhpbms+JyAlfQogICAgICAgICAgICB7JS0gZWxpZiAnXG48L3RoaW5rID4nIGluIGNvbnRlbnQgJX0KICAgICAgICAgICAgICAgIHslLSBzZXQgX3RoaW5rX2VuZCA9ICdcbjwvdGhpbmsgPicgJX0KICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgICAgIHslLSBpZiBfdGhpbmtfZW5kICV9CiAgICAgICAgICAgICAgICB7JS0gaWYgJ3RoaW5raW5nJyBpbiBfdGhpbmtfZW5kICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIHNldCBfdGhpbmtfc3RhcnQgPSAnPHRoaW5raW5nPicgJX0KICAgICAgICAgICAgICAgIHslLSBlbHNlICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIHNldCBfdGhpbmtfc3RhcnQgPSAnPHRoaW5rPicgJX0KICAgICAgICAgICAgICAgIHslLSBlbmRpZiAlfQogICAgICAgICAgICAgICAgeyUtIHNldCByZWFzb25pbmdfY29udGVudCA9IGNvbnRlbnQuc3BsaXQoX3RoaW5rX2VuZClbMF0ucnN0cmlwKCdcbicpICV9CiAgICAgICAgICAgICAgICB7JS0gaWYgX3RoaW5rX3N0YXJ0IGluIHJlYXNvbmluZ19jb250ZW50ICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIHNldCByZWFzb25pbmdfY29udGVudCA9IHJlYXNvbmluZ19jb250ZW50LnNwbGl0KF90aGlua19zdGFydClbLTFdLmxzdHJpcCgnXG4nKSAlfQogICAgICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgICAgICAgICB7JS0gc2V0IGNvbnRlbnQgPSBjb250ZW50LnNwbGl0KF90aGlua19lbmQpWy0xXS5sc3RyaXAoJ1xuJykgJX0KICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgeyUtIHNldCByZWFzb25pbmdfY29udGVudCA9IHJlYXNvbmluZ19jb250ZW50IHwgdHJpbSAlfQogICAgICAgIHslLSBpZiAoX3ByZXNlcnZlX3RoaW5raW5nIG9yIGxvb3AuaW5kZXgwID4gbnMubGFzdF9xdWVyeV9pbmRleCkgYW5kIHJlYXNvbmluZ19jb250ZW50ICV9CiAgICAgICAgICAgIHt7LSAnPHxpbV9zdGFydHw+YXNzaXN0YW50XG48dGhpbms+XG4nICsgcmVhc29uaW5nX2NvbnRlbnQgKyAnXG48L3RoaW5rPlxuXG4nICsgY29udGVudCB9fQogICAgICAgIHslLSBlbHNlICV9CiAgICAgICAgICAgIHt7LSAnPHxpbV9zdGFydHw+YXNzaXN0YW50XG4nICsgY29udGVudCB9fQogICAgICAgIHslLSBlbmRpZiAlfQogICAgICAgIHslLSBpZiBtZXNzYWdlLnRvb2xfY2FsbHMgaXMgZGVmaW5lZCBhbmQgbWVzc2FnZS50b29sX2NhbGxzIGFuZCBtZXNzYWdlLnRvb2xfY2FsbHMgaXMgaXRlcmFibGUgYW5kIG1lc3NhZ2UudG9vbF9jYWxscyBpcyBub3QgbWFwcGluZyAlfQogICAgICAgICAgICB7JS0gZm9yIHRvb2xfY2FsbCBpbiBtZXNzYWdlLnRvb2xfY2FsbHMgJX0KICAgICAgICAgICAgICAgIHslLSBpZiB0b29sX2NhbGwuZnVuY3Rpb24gaXMgZGVmaW5lZCBhbmQgdG9vbF9jYWxsLmZ1bmN0aW9uIGlzIG5vdCBub25lICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIHNldCB0YyA9IHRvb2xfY2FsbC5mdW5jdGlvbiAlfQogICAgICAgICAgICAgICAgeyUtIGVsc2UgJX0KICAgICAgICAgICAgICAgICAgICB7JS0gc2V0IHRjID0gdG9vbF9jYWxsICV9CiAgICAgICAgICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICAgICAgICAgIHslLSBpZiBfdG9vbF9mb3JtYXQgPT0gJ2pzb24nICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIGlmIG5vdCBsb29wLmZpcnN0IG9yIGNvbnRlbnQgfCB0cmltICV9CiAgICAgICAgICAgICAgICAgICAgICAgIHt7LSAnXG5cbicgfX0KICAgICAgICAgICAgICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICAgICAgICAgICAgICB7JS0gc2V0IF9hcmdzID0gJ3t9JyAlfQogICAgICAgICAgICAgICAgICAgIHslLSBpZiB0Yy5hcmd1bWVudHMgaXMgZGVmaW5lZCBhbmQgdGMuYXJndW1lbnRzIGlzIG5vdCBub25lICV9CiAgICAgICAgICAgICAgICAgICAgICAgIHslLSBpZiB0Yy5hcmd1bWVudHMgaXMgbWFwcGluZyAlfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgeyUtIHNldCBfYXJncyA9IHRjLmFyZ3VtZW50cyB8IHRvanNvbiAlfQogICAgICAgICAgICAgICAgICAgICAgICB7JS0gZWxpZiB0Yy5hcmd1bWVudHMgaXMgc3RyaW5nIGFuZCB0Yy5hcmd1bWVudHMgJX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHslLSBzZXQgX2FyZ3MgPSB0Yy5hcmd1bWVudHMgJX0KICAgICAgICAgICAgICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgICAgICAgICAgICAge3stICc8dG9vbF9jYWxsPlxueyJuYW1lIjogJyB9fXt7LSB0Yy5uYW1lIHwgdG9qc29uIH19e3stICcsICJhcmd1bWVudHMiOiAnIH19e3stIF9hcmdzIH19e3stICd9XG48L3Rvb2xfY2FsbD4nIH19CiAgICAgICAgICAgICAgICB7JS0gZWxzZSAlfQogICAgICAgICAgICAgICAgICAgIHslLSBpZiBsb29wLmZpcnN0ICV9CiAgICAgICAgICAgICAgICAgICAgICAgIHslLSBpZiBjb250ZW50IHwgdHJpbSAlfQogICAgICAgICAgICAgICAgICAgICAgICAgICAge3stICdcblxuPHRvb2xfY2FsbD5cbjxmdW5jdGlvbj0nICsgdGMubmFtZSArICc+XG4nIH19CiAgICAgICAgICAgICAgICAgICAgICAgIHslLSBlbHNlICV9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7ey0gJzx0b29sX2NhbGw+XG48ZnVuY3Rpb249JyArIHRjLm5hbWUgKyAnPlxuJyB9fQogICAgICAgICAgICAgICAgICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICAgICAgICAgICAgICB7JS0gZWxzZSAlfQogICAgICAgICAgICAgICAgICAgICAgICB7ey0gJ1xuXG48dG9vbF9jYWxsPlxuPGZ1bmN0aW9uPScgKyB0Yy5uYW1lICsgJz5cbicgfX0KICAgICAgICAgICAgICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICAgICAgICAgICAgICB7JS0gaWYgdGMuYXJndW1lbnRzIGlzIGRlZmluZWQgYW5kIHRjLmFyZ3VtZW50cyBpcyBub3Qgbm9uZSAlfQogICAgICAgICAgICAgICAgICAgICAgICB7JS0gaWYgdGMuYXJndW1lbnRzIGlzIG1hcHBpbmcgJX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHslLSBmb3IgYXJnc19uYW1lLCBhcmdzX3ZhbHVlIGluIHRjLmFyZ3VtZW50cy5pdGVtcygpICV9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3stICc8cGFyYW1ldGVyPScgKyBhcmdzX25hbWUgKyAnPlxuJyB9fQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHslLSBpZiBhcmdzX3ZhbHVlIGlzIG1hcHBpbmcgb3IgKGFyZ3NfdmFsdWUgaXMgc2VxdWVuY2UgYW5kIGFyZ3NfdmFsdWUgaXMgbm90IHN0cmluZykgJX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeyUtIHNldCBfYXYgPSBhcmdzX3ZhbHVlIHwgdG9qc29uICV9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeyUtIGVsc2UgJX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeyUtIHNldCBfYXYgPSBhcmdzX3ZhbHVlIHwgc3RyaW5nICV9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeyUtIGlmIG1heF90b29sX2FyZ19jaGFycyA+IDAgYW5kIF9hdiB8IGxlbmd0aCA+IG1heF90b29sX2FyZ19jaGFycyAlfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7ey0gX2F2WzptYXhfdG9vbF9hcmdfY2hhcnNdICsgJ1xuW1RSVU5DQVRFRCDigJQgb3JpZ2luYWwgbGVuZ3RoICcgfiAoX2F2IHwgbGVuZ3RoIHwgc3RyaW5nKSB+ICcgY2hhcnNdJyB9fQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHslLSBlbHNlICV9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHt7LSBfYXYgfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7ey0gJ1xuPC9wYXJhbWV0ZXI+XG4nIH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7JS0gZW5kZm9yICV9CiAgICAgICAgICAgICAgICAgICAgICAgIHslLSBlbGlmIHRjLmFyZ3VtZW50cyBpcyBzdHJpbmcgYW5kIHRjLmFyZ3VtZW50cyAlfQogICAgICAgICAgICAgICAgICAgICAgICAgICAge3stIHRjLmFyZ3VtZW50cyB9fQogICAgICAgICAgICAgICAgICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICAgICAgICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICAgICAgICAgICAgICB7ey0gJzwvZnVuY3Rpb24+XG48L3Rvb2xfY2FsbD4nIH19CiAgICAgICAgICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICAgICAgeyUtIGVuZGZvciAlfQogICAgICAgIHslLSBlbmRpZiAlfQogICAgICAgIHt7LSAnPHxpbV9lbmR8PlxuJyB9fQogICAgeyUtIGVsaWYgbWVzc2FnZS5yb2xlID09ICd0b29sJyAlfQogICAgICAgIHslLSBzZXQgX2NvbnRlbnRfbG93ZXIgPSBjb250ZW50IHwgbG93ZXIgJX0KICAgICAgICB7JS0gc2V0IF9jb250ZW50X2hlYWQgPSBfY29udGVudF9sb3dlcls6ODBdICV9CiAgICAgICAgeyUtIGlmIGNvbnRlbnQgfCBsZW5ndGggPCA1MDAgYW5kICckICcgbm90IGluIGNvbnRlbnQgYW5kICd0b29rICcgbm90IGluIF9jb250ZW50X2xvd2VyIGFuZCAoJyJlcnJvciI6JyBpbiBfY29udGVudF9oZWFkIG9yICdlcnJvcjonIGluIF9jb250ZW50X2hlYWQgb3IgJ2VyciEnIGluIF9jb250ZW50X2hlYWQgb3IgJ2ZhdGFsOicgaW4gX2NvbnRlbnRfaGVhZCBvciAnZXhjZXB0aW9uOicgaW4gX2NvbnRlbnRfaGVhZCBvciAndHJhY2ViYWNrJyBpbiBfY29udGVudF9oZWFkIG9yICdjb21tYW5kIG5vdCBmb3VuZCcgaW4gX2NvbnRlbnRfaGVhZCBvciAnaW52YWxpZCBzeW50YXgnIGluIF9jb250ZW50X2hlYWQgb3IgJ2ZhaWxlZCB0bycgaW4gX2NvbnRlbnRfaGVhZCkgJX0KICAgICAgICAgICAgeyUtIHNldCBuczIuY29uc2VjdXRpdmVfZmFpbHVyZXMgPSBuczIuY29uc2VjdXRpdmVfZmFpbHVyZXMgKyAxICV9CiAgICAgICAgeyUtIGVsc2UgJX0KICAgICAgICAgICAgeyUtIHNldCBuczIuY29uc2VjdXRpdmVfZmFpbHVyZXMgPSAwICV9CiAgICAgICAgeyUtIGVuZGlmICV9CiAgICAgICAgeyUtIGlmIG5zMi5wcmV2X3JvbGUgIT0gJ3Rvb2wnICV9CiAgICAgICAgICAgIHt7LSAnPHxpbV9zdGFydHw+dXNlcicgfX0KICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICB7JS0gaWYgbWF4X3Rvb2xfcmVzcG9uc2VfY2hhcnMgPiAwIGFuZCBjb250ZW50IHwgbGVuZ3RoID4gbWF4X3Rvb2xfcmVzcG9uc2VfY2hhcnMgJX0KICAgICAgICAgICAgeyUtIHNldCBjb250ZW50ID0gY29udGVudFs6bWF4X3Rvb2xfcmVzcG9uc2VfY2hhcnNdICsgJ1xuW1RSVU5DQVRFRCDigJQgb3JpZ2luYWwgbGVuZ3RoICcgfiAoY29udGVudCB8IGxlbmd0aCB8IHN0cmluZykgfiAnIGNoYXJzXScgJX0KICAgICAgICB7JS0gZW5kaWYgJX0KICAgICAgICB7ey0gJ1xuPHRvb2xfcmVzcG9uc2U+XG4nICsgY29udGVudCB9fQogICAgICAgIHslLSBpZiBuczIuY29uc2VjdXRpdmVfZmFpbHVyZXMgPj0gMiAlfQogICAgICAgICAgICB7ey0gJ1xuXG7imqDvuI8gU1lTVEVNIFdBUk5JTkc6ICcgfiBuczIuY29uc2VjdXRpdmVfZmFpbHVyZXMgfiAnIGNvbnNlY3V0aXZlIHRvb2wgZXJyb3JzIGRldGVjdGVkLiBZb3VyIHByZXZpb3VzIGFwcHJvYWNoIGlzIGluY29ycmVjdC4gWW91IE1VU1QgdXNlIGEgZnVuZGFtZW50YWxseSBkaWZmZXJlbnQgYXBwcm9hY2ggb3IgY29ycmVjdGVkIGFyZ3VtZW50cy4nIH19CiAgICAgICAgeyUtIGVsaWYgbnMyLmNvbnNlY3V0aXZlX2ZhaWx1cmVzID09IDEgJX0KICAgICAgICAgICAge3stICdcblxu4pqg77iPIFNZU1RFTSBXQVJOSU5HOiBUaGUgcHJldmlvdXMgdG9vbCBjYWxsIHJldHVybmVkIGFuIGVycm9yLiBEaWFnbm9zZSB0aGUgZmFpbHVyZSBhbmQgcmV0cnkgd2l0aCBjb21wbGV0ZWx5IGNvcnJlY3RlZCBhcmd1bWVudHMuJyB9fQogICAgICAgIHslLSBlbmRpZiAlfQogICAgICAgIHt7LSAnXG48L3Rvb2xfcmVzcG9uc2U+JyB9fQogICAgICAgIHslLSBpZiBsb29wLmxhc3QgJX0KICAgICAgICAgICAge3stICc8fGltX2VuZHw+XG4nIH19CiAgICAgICAgeyUtIGVsc2UgJX0KICAgICAgICAgICAgeyUtIHNldCBfbmV4dF9yb2xlID0gX21zZ3NbbG9vcC5pbmRleDAgKyAxXS5yb2xlICV9CiAgICAgICAgICAgIHslLSBpZiBfbmV4dF9yb2xlICE9ICd0b29sJyAlfQogICAgICAgICAgICAgICAge3stICc8fGltX2VuZHw+XG4nIH19CiAgICAgICAgICAgIHslLSBlbmRpZiAlfQogICAgICAgIHslLSBlbmRpZiAlfQogICAgeyUtIGVsc2UgJX0KICAgICAgICB7ey0gJzx8aW1fc3RhcnR8PnVzZXJcblsnICsgbWVzc2FnZS5yb2xlICsgJ106ICcgKyBjb250ZW50ICsgJzx8aW1fZW5kfD5cbicgfX0KICAgIHslLSBlbmRpZiAlfQogICAgeyUtIHNldCBuczIucHJldl9yb2xlID0gbWVzc2FnZS5yb2xlICV9CnslLSBlbmRmb3IgJX0KeyUtIGlmIGFkZF9nZW5lcmF0aW9uX3Byb21wdCAlfQogICAge3stICc8fGltX3N0YXJ0fD5hc3Npc3RhbnRcbicgfX0KICAgIHslLSBpZiBub3QgbnNfc3RhdGUudGhpbmtpbmcgJX0KICAgICAgICB7ey0gJzx0aGluaz5cblxuPC90aGluaz5cblxuJyB9fQogICAgeyUtIGVsaWYgbnMyLmNvbnNlY3V0aXZlX2ZhaWx1cmVzID49IDIgJX0KICAgICAgICB7ey0gJzx0aGluaz5cblxuPC90aGluaz5cblxuJyB9fQogICAgeyUtIGVsc2UgJX0KICAgICAgICB7ey0gJzx0aGluaz5cbicgfX0KICAgIHslLSBlbmRpZiAlfQp7JS0gZW5kaWYgJX0=";
7
+ const QWEN_SHARP = {
8
+ id: "qwen-sharp-v21.3",
9
+ name: "Qwen Sharp — concise, tool-safe",
10
+ family: "qwen",
11
+ description: "froggeric fixed Qwen v21.3 template with peculiar-ragdoll's terse system prompt. Agentic default disables retained thinking to avoid multi-turn context/prefill growth.",
12
+ template: Buffer.from(QWEN_SHARP_TEMPLATE_BASE64, "base64").toString("utf8"),
13
+ systemPromptAddendum: null,
14
+ templateKwargs: { preserve_thinking: false },
15
+ };
16
+ const BUILTIN_HOSTING_PROFILES = [QWEN_SHARP];
17
+ /**
18
+ * Product-owned ids are upgraded wholesale, while records with unknown ids remain
19
+ * user-owned. Do not merge profile fields: stale template bytes or prose must not
20
+ * survive an Otto upgrade.
21
+ */
22
+ export function seedBuiltinHostingProfiles(store) {
23
+ let changed = false;
24
+ for (const profile of BUILTIN_HOSTING_PROFILES) {
25
+ if (JSON.stringify(store.hostingProfiles[profile.id]) === JSON.stringify(profile))
26
+ continue;
27
+ store.hostingProfiles[profile.id] = profile;
28
+ changed = true;
29
+ }
30
+ return changed;
31
+ }
32
+ //# sourceMappingURL=builtin-hosting-profiles.js.map
@@ -0,0 +1,33 @@
1
+ import type { BrainPaths } from "./paths.js";
2
+ import type { HostingProfile, Profile, ProfilesStore } from "./schema.js";
3
+ /**
4
+ * The bucket a model's hosting profiles live in. Families are catalog-authoritative
5
+ * when available, otherwise discovery derives them from GGUF metadata; models
6
+ * with neither use one real bucket rather than each being unaddressable. This
7
+ * must be the single definition: a writer that stored under "generic" while a
8
+ * reader looked up `null` was why a family default could be set on an unfamilied
9
+ * model and never take effect.
10
+ */
11
+ export declare const GENERIC_HOSTING_FAMILY = "generic";
12
+ export declare function hostingFamily(family: string | null | undefined): string;
13
+ export declare function familyHostingProfileId(store: ProfilesStore, family: string | null | undefined): string | null;
14
+ export declare function effectiveHostingProfile(store: ProfilesStore, profile: Profile, family: string | null | undefined): HostingProfile | null;
15
+ /**
16
+ * Materialize a selected profile immediately before launch: the template as a
17
+ * file llama-server reads, the system addendum as a string the router injects
18
+ * into each completion request.
19
+ *
20
+ * The addendum is deliberately *not* spliced into the Jinja template, which was
21
+ * the first design. Doing that means rewriting the `messages` list from inside
22
+ * the template, and there is no portable way to do it: minja's support for list
23
+ * mutation differs from Jinja2's, every model family's template consumes the
24
+ * system turn differently, and a message's `content` is an array of parts for
25
+ * multimodal requests rather than a string to concatenate onto. Injecting into
26
+ * the parsed request body is the same composition (it appends to the agent's
27
+ * existing system message instead of replacing it) done where the shape is
28
+ * known and testable.
29
+ */
30
+ export declare function resolveHostingProfileForLaunch(paths: BrainPaths, store: ProfilesStore, profile: Profile, family: string | null | undefined): Profile;
31
+ /** Remove a deleted profile's generated template. Missing or locked files are non-fatal. */
32
+ export declare function removeHostingProfileMaterialization(paths: BrainPaths, id: string): void;
33
+ //# sourceMappingURL=hosting-profiles.d.ts.map
@@ -0,0 +1,71 @@
1
+ /** Brain-owned named chat-template compositions for llama-server. */
2
+ import { rmSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { writePrivateFileAtomicSync } from "./private-files.js";
5
+ const SAFE_FILE = /[^a-zA-Z0-9_-]/gu;
6
+ function materializedTemplatePath(paths, id) {
7
+ return path.join(paths.templatesDir, `${id.replace(SAFE_FILE, "_")}.jinja`);
8
+ }
9
+ /**
10
+ * The bucket a model's hosting profiles live in. Families are catalog-authoritative
11
+ * when available, otherwise discovery derives them from GGUF metadata; models
12
+ * with neither use one real bucket rather than each being unaddressable. This
13
+ * must be the single definition: a writer that stored under "generic" while a
14
+ * reader looked up `null` was why a family default could be set on an unfamilied
15
+ * model and never take effect.
16
+ */
17
+ export const GENERIC_HOSTING_FAMILY = "generic";
18
+ export function hostingFamily(family) {
19
+ return family || GENERIC_HOSTING_FAMILY;
20
+ }
21
+ export function familyHostingProfileId(store, family) {
22
+ return store.familyHostingProfileIds[hostingFamily(family)] ?? null;
23
+ }
24
+ export function effectiveHostingProfile(store, profile, family) {
25
+ const selected = profile.hostingProfileMode === "inherit"
26
+ ? familyHostingProfileId(store, family)
27
+ : profile.hostingProfileMode === "custom"
28
+ ? profile.hostingProfileId
29
+ : null;
30
+ return selected ? (store.hostingProfiles[selected] ?? null) : null;
31
+ }
32
+ /**
33
+ * Materialize a selected profile immediately before launch: the template as a
34
+ * file llama-server reads, the system addendum as a string the router injects
35
+ * into each completion request.
36
+ *
37
+ * The addendum is deliberately *not* spliced into the Jinja template, which was
38
+ * the first design. Doing that means rewriting the `messages` list from inside
39
+ * the template, and there is no portable way to do it: minja's support for list
40
+ * mutation differs from Jinja2's, every model family's template consumes the
41
+ * system turn differently, and a message's `content` is an array of parts for
42
+ * multimodal requests rather than a string to concatenate onto. Injecting into
43
+ * the parsed request body is the same composition (it appends to the agent's
44
+ * existing system message instead of replacing it) done where the shape is
45
+ * known and testable.
46
+ */
47
+ export function resolveHostingProfileForLaunch(paths, store, profile, family) {
48
+ const selected = effectiveHostingProfile(store, profile, family);
49
+ const chatSystemAddendum = selected?.systemPromptAddendum?.trim() || null;
50
+ if (!selected?.template) {
51
+ return { ...profile, chatTemplateFile: null, chatTemplateKwargs: {}, chatSystemAddendum };
52
+ }
53
+ const file = materializedTemplatePath(paths, selected.id);
54
+ writePrivateFileAtomicSync(file, selected.template);
55
+ return {
56
+ ...profile,
57
+ chatTemplateFile: file,
58
+ chatTemplateKwargs: selected.templateKwargs,
59
+ chatSystemAddendum,
60
+ };
61
+ }
62
+ /** Remove a deleted profile's generated template. Missing or locked files are non-fatal. */
63
+ export function removeHostingProfileMaterialization(paths, id) {
64
+ try {
65
+ rmSync(materializedTemplatePath(paths, id), { force: true });
66
+ }
67
+ catch {
68
+ // Profile deletion must not fail because a previous launch left a file locked.
69
+ }
70
+ }
71
+ //# sourceMappingURL=hosting-profiles.js.map
@@ -5,6 +5,7 @@ export { resolveBrainPaths, packageRoot, type BrainPaths } from "./paths.js";
5
5
  export { parseBooleanEnv, applyEnvOverrides } from "./env.js";
6
6
  export { loadBrainConfig, loadPersistedConfig, saveBrainConfig, loadProfilesStore, saveProfilesStore, loadCatalog, } from "./store.js";
7
7
  export { defaultProfile, forModel, put, calibrationKey, geometryKey, getCalibration, putCalibration, hasStaleCalibration, } from "./profiles.js";
8
+ export { effectiveHostingProfile, resolveHostingProfileForLaunch } from "./hosting-profiles.js";
8
9
  export { calibrationInfo, nativeContextLimit, profileFieldDescriptors, profileWarnings, sanitizeProfilePatch, formatReasoningBudget, CACHE_TYPE_CYCLE, REASONING_BUDGET_CYCLE, UNRESTRICTED_REASONING_BUDGET, type CalibrationInfo, type CalibrationState, type ProfileFieldDescriptor, type ProfileWarning, } from "./profile-edit.js";
9
10
  export * from "./schema.js";
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -5,6 +5,7 @@ export { resolveBrainPaths, packageRoot } from "./paths.js";
5
5
  export { parseBooleanEnv, applyEnvOverrides } from "./env.js";
6
6
  export { loadBrainConfig, loadPersistedConfig, saveBrainConfig, loadProfilesStore, saveProfilesStore, loadCatalog, } from "./store.js";
7
7
  export { defaultProfile, forModel, put, calibrationKey, geometryKey, getCalibration, putCalibration, hasStaleCalibration, } from "./profiles.js";
8
+ export { effectiveHostingProfile, resolveHostingProfileForLaunch } from "./hosting-profiles.js";
8
9
  export { calibrationInfo, nativeContextLimit, profileFieldDescriptors, profileWarnings, sanitizeProfilePatch, formatReasoningBudget, CACHE_TYPE_CYCLE, REASONING_BUDGET_CYCLE, UNRESTRICTED_REASONING_BUDGET, } from "./profile-edit.js";
9
10
  export * from "./schema.js";
10
11
  //# sourceMappingURL=index.js.map
@@ -12,6 +12,8 @@ export interface BrainPaths {
12
12
  logFile: string;
13
13
  logsDir: string;
14
14
  resultsDir: string;
15
+ /** Brain-owned Jinja files materialized from named hosting profiles. */
16
+ templatesDir: string;
15
17
  }
16
18
  export declare function resolveBrainPaths(env?: NodeJS.ProcessEnv): BrainPaths;
17
19
  /**
@@ -26,6 +26,7 @@ export function resolveBrainPaths(env = process.env) {
26
26
  logFile: path.join(root, "otto-brain.log"),
27
27
  logsDir: path.join(root, "logs"),
28
28
  resultsDir: path.join(root, "results"),
29
+ templatesDir: path.join(root, "templates"),
29
30
  };
30
31
  }
31
32
  /**
@@ -45,10 +45,12 @@ export declare const MAX_PARALLEL_SLOTS = 16;
45
45
  export declare const MAX_GPU_LAYERS = 999;
46
46
  export declare const MIN_CONTEXT_SIZE = 1024;
47
47
  export declare const CONTEXT_STEP = 8192;
48
+ export declare const CONTEXT_MULTIPLIERS: number[];
48
49
  /** The context ceiling: the model's native window, or a generous bound if unknown. */
49
50
  export declare function nativeContextLimit(model: Model | null): number;
51
+ export declare function contextLimit(model: Model | null, multiplier?: number): number;
50
52
  /** The editable fields, resolved against one model's capabilities. */
51
- export declare function profileFieldDescriptors(model: Model | null): ProfileFieldDescriptor[];
53
+ export declare function profileFieldDescriptors(model: Model | null, profile?: Profile | null): ProfileFieldDescriptor[];
52
54
  /** A note attached to a field, or to the profile as a whole when `field` is null. */
53
55
  export interface ProfileWarning {
54
56
  field: string | null;
@@ -90,5 +92,5 @@ export interface SanitizeResult {
90
92
  * CLI-only: they have no measured effect worth exposing and `extraArgs` is
91
93
  * arbitrary process arguments.
92
94
  */
93
- export declare function sanitizeProfilePatch(current: Profile, patch: unknown, model: Model | null): SanitizeResult;
95
+ export declare function sanitizeProfilePatch(current: Profile, patch: unknown, model: Model | null, runtimeBuild?: number | null): SanitizeResult;
94
96
  //# sourceMappingURL=profile-edit.d.ts.map