@otto-code/brain 0.8.7 → 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -1
- package/dist/commands/bench.js +19 -5
- package/dist/commands/catalog.d.ts +3 -0
- package/dist/commands/catalog.js +2 -0
- package/dist/commands/pull.d.ts +1 -0
- package/dist/commands/pull.js +47 -14
- package/dist/commands/repo-download.d.ts +14 -0
- package/dist/commands/repo-download.js +29 -0
- package/dist/commands/runtime.d.ts +3 -0
- package/dist/commands/runtime.js +65 -18
- package/dist/commands/search.d.ts +3 -0
- package/dist/commands/search.js +38 -17
- package/dist/config/builtin-hosting-profiles.d.ts +8 -0
- package/dist/config/builtin-hosting-profiles.js +32 -0
- package/dist/config/hosting-profiles.d.ts +33 -0
- package/dist/config/hosting-profiles.js +71 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +1 -0
- package/dist/config/paths.d.ts +2 -0
- package/dist/config/paths.js +1 -0
- package/dist/config/profile-edit.d.ts +5 -3
- package/dist/config/profile-edit.js +134 -14
- package/dist/config/profiles.js +66 -3
- package/dist/config/schema.d.ts +998 -0
- package/dist/config/schema.js +79 -0
- package/dist/config/store.js +28 -16
- package/dist/gguf.d.ts +1 -0
- package/dist/gguf.js +1 -0
- package/dist/models/download.d.ts +7 -0
- package/dist/models/download.js +165 -17
- package/dist/models/enrich.d.ts +6 -19
- package/dist/models/enrich.js +138 -4
- package/dist/models/hf.d.ts +14 -1
- package/dist/models/hf.js +239 -6
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.js +8 -5
- package/dist/models/manage.d.ts +4 -0
- package/dist/models/manage.js +34 -4
- package/dist/models/scan.js +8 -42
- package/dist/ops/calibrate.d.ts +4 -1
- package/dist/ops/calibrate.js +10 -7
- package/dist/ops/sweep.d.ts +3 -1
- package/dist/ops/sweep.js +3 -3
- package/dist/runtime/args.d.ts +2 -2
- package/dist/runtime/args.js +18 -1
- package/dist/runtime/index.d.ts +8 -1
- package/dist/runtime/index.js +11 -1
- package/dist/runtime/managed.d.ts +40 -0
- package/dist/runtime/managed.js +146 -7
- package/dist/service/host-api.d.ts +20 -3
- package/dist/service/host-api.js +313 -20
- package/dist/service/router.d.ts +18 -0
- package/dist/service/router.js +89 -4
- package/dist/service/serve.js +221 -22
- package/dist/service/supervisor.d.ts +32 -4
- package/dist/service/supervisor.js +30 -6
- package/dist/tui/app.js +1 -1
- package/dist/types.d.ts +24 -0
- package/dist/vram.d.ts +3 -0
- package/dist/vram.js +24 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import { addRestartOptions, addServeOptions, addStartOptions, addStatusOptions,
|
|
|
15
15
|
import { addPullOptions, runPullCommand } from "./commands/pull.js";
|
|
16
16
|
import { addReportOptions, runReportCommand } from "./commands/report.js";
|
|
17
17
|
import { addRescoreOptions, runRescoreCommand } from "./commands/rescore.js";
|
|
18
|
-
import { addRuntimeInstallOptions, addRuntimeListOptions, runRuntimeInstallCommand, runRuntimeListCommand, } from "./commands/runtime.js";
|
|
18
|
+
import { addRuntimeInstallOptions, addRuntimeListOptions, addRuntimeRemoveOptions, runRuntimeInstallCommand, runRuntimeListCommand, runRuntimeRemoveCommand, } from "./commands/runtime.js";
|
|
19
19
|
import { addScanOptions, runScanCommand } from "./commands/scan.js";
|
|
20
20
|
import { addAddOptions, addSearchOptions, runAddCommand, runSearchCommand, } from "./commands/search.js";
|
|
21
21
|
import { addSweepOptions, runSweepCommand } from "./commands/sweep.js";
|
|
@@ -57,6 +57,7 @@ export function registerBrainCommands(program) {
|
|
|
57
57
|
const runtime = program.command("runtime").description("Manage the llama.cpp runtime");
|
|
58
58
|
addRuntimeListOptions(runtime.command("list")).action(withOutput(runRuntimeListCommand));
|
|
59
59
|
addRuntimeInstallOptions(runtime.command("install")).action(withOutput(runRuntimeInstallCommand));
|
|
60
|
+
addRuntimeRemoveOptions(runtime.command("remove")).action(withOutput(runRuntimeRemoveCommand));
|
|
60
61
|
// config subgroup.
|
|
61
62
|
const config = program.command("config").description("Inspect and edit brain config");
|
|
62
63
|
addConfigShowOptions(config.command("show")).action(withOutput(runConfigShowCommand));
|
package/dist/commands/bench.js
CHANGED
|
@@ -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.
|
|
5
|
-
*
|
|
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({
|
|
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({
|
|
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);
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
* params, tier, use-cases) that only the JSON output surfaces.
|
|
9
9
|
*/
|
|
10
10
|
import type { Command } from "commander";
|
|
11
|
+
import type { CatalogModel } from "../config/schema.js";
|
|
11
12
|
import type { AnyCommandResult, OutputSchema } from "../output/index.js";
|
|
12
13
|
export interface CatalogRow {
|
|
13
14
|
id: string;
|
|
14
15
|
name: string;
|
|
16
|
+
family: string | null;
|
|
15
17
|
installed: boolean;
|
|
16
18
|
publisher: string;
|
|
17
19
|
repo: string;
|
|
@@ -25,6 +27,7 @@ export interface CatalogRow {
|
|
|
25
27
|
tier: string;
|
|
26
28
|
useCases: string[];
|
|
27
29
|
why: string;
|
|
30
|
+
components?: CatalogModel["components"];
|
|
28
31
|
}
|
|
29
32
|
export declare const catalogSchema: OutputSchema<CatalogRow>;
|
|
30
33
|
export declare function addCatalogOptions(cmd: Command): Command;
|
package/dist/commands/catalog.js
CHANGED
|
@@ -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,
|
|
@@ -42,6 +43,7 @@ export async function runCatalogCommand(_options, _command) {
|
|
|
42
43
|
tier: model.tier ?? "",
|
|
43
44
|
useCases: model.useCases ?? [],
|
|
44
45
|
why: model.why ?? "",
|
|
46
|
+
components: model.components,
|
|
45
47
|
}));
|
|
46
48
|
return { type: "list", data: rows, schema: catalogSchema };
|
|
47
49
|
}
|
package/dist/commands/pull.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface PullOptionsInput {
|
|
|
17
17
|
file?: string;
|
|
18
18
|
quant?: string;
|
|
19
19
|
listQuants?: boolean;
|
|
20
|
+
component?: string[];
|
|
20
21
|
}
|
|
21
22
|
export declare function runPullCommand(modelArg: string, options: PullOptionsInput, _command: Command): Promise<AnyCommandResult<PullRow>>;
|
|
22
23
|
//# sourceMappingURL=pull.d.ts.map
|
package/dist/commands/pull.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { loadBrainConfig, loadCatalog } from "../config/index.js";
|
|
2
|
-
import {
|
|
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: [
|
|
@@ -44,6 +45,7 @@ export function addPullOptions(cmd) {
|
|
|
44
45
|
.argument("<model>", "catalog id or name fragment")
|
|
45
46
|
.option("--file <name.gguf>", "explicit GGUF file name in the repo")
|
|
46
47
|
.option("--quant <label>", "download a specific quantization (e.g. Q5_K_M)")
|
|
48
|
+
.option("--component <id...>", "download optional bundle component ids")
|
|
47
49
|
.option("--list-quants", "list the quantizations the repo offers and exit");
|
|
48
50
|
}
|
|
49
51
|
export async function runPullCommand(modelArg, options, _command) {
|
|
@@ -51,6 +53,42 @@ export async function runPullCommand(modelArg, options, _command) {
|
|
|
51
53
|
const catalog = loadCatalog();
|
|
52
54
|
const model = findCatalogModel(catalog.models, modelArg);
|
|
53
55
|
const token = resolveHfToken(config);
|
|
56
|
+
// A bundle combines one selected primary quant and explicit optional
|
|
57
|
+
// companions. It must not pull the generic discovery projector as a side
|
|
58
|
+
// effect, because that makes the download plan ambiguous.
|
|
59
|
+
if (model.components && !options.listQuants && !options.file) {
|
|
60
|
+
const { quants } = await listRepoQuants(model.hfRepo, token);
|
|
61
|
+
const choice = options.quant
|
|
62
|
+
? quants.find((quant) => quant.quant.toLowerCase() === options.quant.toLowerCase())
|
|
63
|
+
: undefined;
|
|
64
|
+
if (options.quant && !choice) {
|
|
65
|
+
throw new CommandError({
|
|
66
|
+
code: "NO_QUANT",
|
|
67
|
+
message: `${model.hfRepo} has no ${options.quant}`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const plan = bundleDownloadPlan(model, options.component ?? [], choice?.files, choice?.sizeBytes);
|
|
71
|
+
const progressLabel = `${model.name}${choice ? ` ${choice.quant}` : ""}`;
|
|
72
|
+
const written = await downloadRepoFilesWithProgress({
|
|
73
|
+
activityTarget: model.name,
|
|
74
|
+
progressLabel,
|
|
75
|
+
totalBytes: plan.totalBytes,
|
|
76
|
+
repo: plan.repo,
|
|
77
|
+
files: plan.files,
|
|
78
|
+
destRoot: managedModelsDir(config),
|
|
79
|
+
token,
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
type: "single",
|
|
83
|
+
data: {
|
|
84
|
+
model: choice ? `${model.name} (${choice.quant})` : model.name,
|
|
85
|
+
repo: model.hfRepo,
|
|
86
|
+
path: written[0] ?? "(already present)",
|
|
87
|
+
size: plan.totalBytes ? formatBytes(plan.totalBytes) : "-",
|
|
88
|
+
},
|
|
89
|
+
schema: pullSchema,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
54
92
|
// Discover-and-choose paths both need the repo's quant listing.
|
|
55
93
|
if (options.listQuants || options.quant) {
|
|
56
94
|
const { quants, mmproj } = await listRepoQuants(model.hfRepo, token);
|
|
@@ -61,6 +99,7 @@ export async function runPullCommand(modelArg, options, _command) {
|
|
|
61
99
|
quant: q.quant,
|
|
62
100
|
size: formatBytes(q.sizeBytes),
|
|
63
101
|
files: q.files.length,
|
|
102
|
+
fileNames: q.files,
|
|
64
103
|
})),
|
|
65
104
|
schema: quantSchema,
|
|
66
105
|
};
|
|
@@ -78,22 +117,15 @@ export async function runPullCommand(modelArg, options, _command) {
|
|
|
78
117
|
}
|
|
79
118
|
const files = [...choice.files, ...(mmproj ? mmproj.files : [])];
|
|
80
119
|
const total = choice.sizeBytes + (mmproj?.sizeBytes ?? 0);
|
|
81
|
-
|
|
82
|
-
|
|
120
|
+
const written = await downloadRepoFilesWithProgress({
|
|
121
|
+
activityTarget: `${model.name} (${choice.quant})`,
|
|
122
|
+
progressLabel: `${model.name} ${choice.quant}`,
|
|
123
|
+
totalBytes: total,
|
|
83
124
|
repo: model.hfRepo,
|
|
84
125
|
files,
|
|
85
126
|
destRoot: managedModelsDir(config),
|
|
86
127
|
token,
|
|
87
|
-
|
|
88
|
-
activity.update(total ? p.receivedBytes / total : null);
|
|
89
|
-
const pct = total ? Math.floor((p.receivedBytes / total) * 100) : 0;
|
|
90
|
-
if (pct !== lastPct && pct % 5 === 0) {
|
|
91
|
-
lastPct = pct;
|
|
92
|
-
process.stderr.write(` ${model.name} ${choice.quant}: ${pct}%\r`);
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
}));
|
|
96
|
-
process.stderr.write("\n");
|
|
128
|
+
});
|
|
97
129
|
return {
|
|
98
130
|
type: "single",
|
|
99
131
|
data: {
|
|
@@ -119,12 +151,13 @@ export async function runPullCommand(modelArg, options, _command) {
|
|
|
119
151
|
return;
|
|
120
152
|
activity.update(p.receivedBytes / p.totalBytes);
|
|
121
153
|
const pct = Math.floor((p.receivedBytes / p.totalBytes) * 100);
|
|
122
|
-
if (pct
|
|
154
|
+
if (pct > lastPct) {
|
|
123
155
|
lastPct = pct;
|
|
124
156
|
process.stderr.write(` ${model.name}: ${pct}%\r`);
|
|
125
157
|
}
|
|
126
158
|
},
|
|
127
159
|
}));
|
|
160
|
+
process.stderr.write(` ${model.name}: 100%\r`);
|
|
128
161
|
process.stderr.write("\n");
|
|
129
162
|
return {
|
|
130
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
|
|
@@ -8,6 +8,7 @@ import type { Command } from "commander";
|
|
|
8
8
|
import type { AnyCommandResult } from "../output/index.js";
|
|
9
9
|
export interface RuntimeRow {
|
|
10
10
|
label: string;
|
|
11
|
+
displayName: string;
|
|
11
12
|
version: string;
|
|
12
13
|
source: string;
|
|
13
14
|
dir: string;
|
|
@@ -15,6 +16,8 @@ export interface RuntimeRow {
|
|
|
15
16
|
export declare function addRuntimeListOptions(cmd: Command): Command;
|
|
16
17
|
export declare function runRuntimeListCommand(_options: unknown, _command: Command): Promise<AnyCommandResult<RuntimeRow>>;
|
|
17
18
|
export declare function addRuntimeInstallOptions(cmd: Command): Command;
|
|
19
|
+
export declare function addRuntimeRemoveOptions(cmd: Command): Command;
|
|
20
|
+
export declare function runRuntimeRemoveCommand(name: string, _options: unknown, _command: Command): Promise<AnyCommandResult<RuntimeRow>>;
|
|
18
21
|
export declare function runRuntimeInstallCommand(options: {
|
|
19
22
|
build?: string;
|
|
20
23
|
variant?: string;
|
package/dist/commands/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadBrainConfig } from "../config/index.js";
|
|
2
2
|
import { resolveBrainPaths } from "../config/paths.js";
|
|
3
3
|
import { CommandError } from "../output/types.js";
|
|
4
|
-
import { defaultRuntimeSpec, installManagedRuntime, listAllRuntimes, listRuntimeDevices, probeNvidiaGpu, supportedVariants, } from "../runtime/index.js";
|
|
4
|
+
import { DEFAULT_LLAMA_BUILD, defaultRuntimeSpec, installManagedRuntime, MissingAssetError, removeManagedRuntime, resolveLatestBuildOrPin, listAllRuntimes, listRuntimeDevices, probeNvidiaGpu, supportedVariants, } from "../runtime/index.js";
|
|
5
5
|
const runtimeSchema = {
|
|
6
6
|
idField: "dir",
|
|
7
7
|
columns: [
|
|
@@ -19,6 +19,7 @@ const runtimeSchema = {
|
|
|
19
19
|
function toRows() {
|
|
20
20
|
return listAllRuntimes().map((r) => ({
|
|
21
21
|
label: r.label,
|
|
22
|
+
displayName: r.displayName ?? `${r.label} · ${r.version}`,
|
|
22
23
|
version: r.version,
|
|
23
24
|
source: r.source,
|
|
24
25
|
dir: r.dir,
|
|
@@ -33,9 +34,40 @@ export async function runRuntimeListCommand(_options, _command) {
|
|
|
33
34
|
export function addRuntimeInstallOptions(cmd) {
|
|
34
35
|
return cmd
|
|
35
36
|
.description("Download a self-contained llama.cpp runtime")
|
|
36
|
-
.option("--build <tag>", "llama.cpp release build tag")
|
|
37
|
+
.option("--build <tag>", "llama.cpp release build tag (for example b10355)")
|
|
37
38
|
.option("--variant <name>", `accelerator to install (${supportedVariants().join("|")}); defaults to the best one this machine can use`);
|
|
38
39
|
}
|
|
40
|
+
export function addRuntimeRemoveOptions(cmd) {
|
|
41
|
+
return cmd.description("Remove one Otto-managed runtime").argument("<name>");
|
|
42
|
+
}
|
|
43
|
+
export async function runRuntimeRemoveCommand(name, _options, _command) {
|
|
44
|
+
loadBrainConfig();
|
|
45
|
+
removeManagedRuntime(resolveBrainPaths().runtimesDir, name);
|
|
46
|
+
return { type: "list", data: toRows(), schema: runtimeSchema };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Every warning this command emits is one line on purpose. The daemon's
|
|
50
|
+
* BrainOpsManager keeps the *last* stderr line as the job's message, so a
|
|
51
|
+
* multi-line warning would surface in the UI as a dangling fragment. As one line
|
|
52
|
+
* it survives intact on both the CLI and the GUI.
|
|
53
|
+
*/
|
|
54
|
+
function warn(message) {
|
|
55
|
+
process.stderr.write(` warning: ${message.replace(/\s+/gu, " ").trim()}\n`);
|
|
56
|
+
}
|
|
57
|
+
/** Download + extract a spec, reporting percentage and phase on stderr. */
|
|
58
|
+
async function install(spec, runtimesDir) {
|
|
59
|
+
process.stderr.write(` installing ${spec.label} (${spec.version})…\n`);
|
|
60
|
+
const runtime = await installManagedRuntime(spec, runtimesDir, (p) => {
|
|
61
|
+
if (p.phase === "downloading" && p.totalBytes) {
|
|
62
|
+
const pct = Math.floor(((p.receivedBytes ?? 0) / p.totalBytes) * 100);
|
|
63
|
+
process.stderr.write(` ${pct}%\r`);
|
|
64
|
+
}
|
|
65
|
+
if (p.phase === "extracting")
|
|
66
|
+
process.stderr.write("\n extracting…\n");
|
|
67
|
+
});
|
|
68
|
+
process.stderr.write("\n");
|
|
69
|
+
return runtime;
|
|
70
|
+
}
|
|
39
71
|
export async function runRuntimeInstallCommand(options, _command) {
|
|
40
72
|
loadBrainConfig();
|
|
41
73
|
const { runtimesDir } = resolveBrainPaths();
|
|
@@ -49,38 +81,53 @@ export async function runRuntimeInstallCommand(options, _command) {
|
|
|
49
81
|
}
|
|
50
82
|
// Only "auto" needs the GPU probe, and only to choose between CUDA and Vulkan
|
|
51
83
|
// on the platforms that have both.
|
|
52
|
-
const
|
|
84
|
+
const target = {
|
|
53
85
|
variant: options.variant ?? "auto",
|
|
54
86
|
hasNvidiaGpu: options.variant ? undefined : await probeNvidiaGpu(),
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
87
|
+
};
|
|
88
|
+
// "latest" is a request to update, not a promise upstream can always keep: the
|
|
89
|
+
// lookup is unauthenticated and rate limited, so it falls back to the pin.
|
|
90
|
+
const resolvedLatest = options.build === "latest" ? await resolveLatestBuildOrPin() : null;
|
|
91
|
+
if (resolvedLatest?.warning)
|
|
92
|
+
warn(resolvedLatest.warning);
|
|
93
|
+
let spec = defaultRuntimeSpec(resolvedLatest ? resolvedLatest.build : options.build, target);
|
|
94
|
+
let runtime;
|
|
95
|
+
try {
|
|
96
|
+
runtime = await install(spec, runtimesDir);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// Asset names are not derivable from the build tag, and the scheme has
|
|
100
|
+
// already changed once (b4600 shipped `win-cuda-cu12.4-x64`, b10265 ships
|
|
101
|
+
// `win-cuda-12.4-x64`), so a tag resolved at run time can name assets that
|
|
102
|
+
// do not exist under it. The pin's names are the ones managed.test.ts holds,
|
|
103
|
+
// which makes it the only build this can fall back to. An explicit --build
|
|
104
|
+
// still fails loudly: the user named that tag on purpose.
|
|
105
|
+
if (!(error instanceof MissingAssetError) ||
|
|
106
|
+
!resolvedLatest ||
|
|
107
|
+
spec.version === DEFAULT_LLAMA_BUILD) {
|
|
108
|
+
throw error;
|
|
61
109
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
110
|
+
warn(`llama.cpp build ${spec.version} does not publish the asset this platform needs,` +
|
|
111
|
+
` so Otto installed the pinned build ${DEFAULT_LLAMA_BUILD} instead.`);
|
|
112
|
+
spec = defaultRuntimeSpec(DEFAULT_LLAMA_BUILD, target);
|
|
113
|
+
runtime = await install(spec, runtimesDir);
|
|
114
|
+
}
|
|
66
115
|
// A GPU variant that finds no device still runs - on the CPU, at roughly a
|
|
67
116
|
// fortieth of the prefill throughput, and says nothing about it. Measured on
|
|
68
117
|
// WSL2, which carries no NVIDIA Vulkan ICD. Warn rather than fail: the runtime
|
|
69
118
|
// is genuinely installed and usable, just not accelerated.
|
|
70
|
-
// One line on purpose. The daemon's BrainOpsManager keeps the *last* stderr line
|
|
71
|
-
// as the job's message, so a multi-line warning would surface in the UI as a
|
|
72
|
-
// dangling fragment. As one line it survives intact on both the CLI and the GUI.
|
|
73
119
|
if (spec.variant !== "cpu") {
|
|
74
120
|
const devices = await listRuntimeDevices(runtime);
|
|
75
121
|
if (devices.length === 0) {
|
|
76
|
-
|
|
77
|
-
` on the CPU until a working ${spec.variant} driver for this GPU is installed
|
|
122
|
+
warn(`this ${spec.variant} runtime reports no GPU device, so inference will run` +
|
|
123
|
+
` on the CPU until a working ${spec.variant} driver for this GPU is installed.`);
|
|
78
124
|
}
|
|
79
125
|
}
|
|
80
126
|
return {
|
|
81
127
|
type: "single",
|
|
82
128
|
data: {
|
|
83
129
|
label: runtime.label,
|
|
130
|
+
displayName: runtime.displayName ?? `${runtime.label} · ${runtime.version}`,
|
|
84
131
|
version: runtime.version,
|
|
85
132
|
source: runtime.source,
|
|
86
133
|
dir: runtime.dir,
|
|
@@ -12,6 +12,7 @@ export interface SearchRow {
|
|
|
12
12
|
likes: number;
|
|
13
13
|
gated: string;
|
|
14
14
|
installed: boolean;
|
|
15
|
+
summary: string | null;
|
|
15
16
|
}
|
|
16
17
|
export declare function addSearchOptions(cmd: Command): Command;
|
|
17
18
|
export declare function runSearchCommand(query: string, options: {
|
|
@@ -27,5 +28,7 @@ export declare function addAddOptions(cmd: Command): Command;
|
|
|
27
28
|
export declare function runAddCommand(repo: string, options: {
|
|
28
29
|
quant?: string;
|
|
29
30
|
listQuants?: boolean;
|
|
31
|
+
component?: string[];
|
|
32
|
+
primaryOnly?: boolean;
|
|
30
33
|
}, _command: Command): Promise<AnyCommandResult<AddRow>>;
|
|
31
34
|
//# sourceMappingURL=search.d.ts.map
|
package/dist/commands/search.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { loadBrainConfig } from "../config/index.js";
|
|
2
|
-
import {
|
|
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();
|
|
8
|
-
const quants = new
|
|
9
|
+
const quants = new Map();
|
|
10
|
+
const projectorRepos = new Set();
|
|
9
11
|
for (const model of scanModels(config)) {
|
|
10
12
|
const repo = repoOfModel(model);
|
|
11
13
|
if (!repo)
|
|
12
14
|
continue;
|
|
13
15
|
repos.add(repo.toLowerCase());
|
|
14
16
|
if (model.quant)
|
|
15
|
-
quants.
|
|
17
|
+
quants.set(`${repo.toLowerCase()} ${model.quant.toUpperCase()}`, model.id);
|
|
18
|
+
if (model.mmprojPath)
|
|
19
|
+
projectorRepos.add(repo.toLowerCase());
|
|
16
20
|
}
|
|
17
|
-
return { repos, quants };
|
|
21
|
+
return { repos, quants, projectorRepos };
|
|
18
22
|
}
|
|
19
23
|
const searchSchema = {
|
|
20
24
|
idField: "repo",
|
|
@@ -46,6 +50,7 @@ export async function runSearchCommand(query, options, _command) {
|
|
|
46
50
|
likes: r.likes,
|
|
47
51
|
gated: r.gated ? "yes" : "",
|
|
48
52
|
installed: repos.has(r.repo.toLowerCase()),
|
|
53
|
+
summary: r.summary,
|
|
49
54
|
})),
|
|
50
55
|
schema: searchSchema,
|
|
51
56
|
};
|
|
@@ -73,6 +78,8 @@ export function addAddOptions(cmd) {
|
|
|
73
78
|
.description("Download a model from an arbitrary Hugging Face repo")
|
|
74
79
|
.argument("<repo>", "owner/repo on Hugging Face")
|
|
75
80
|
.option("--quant <label>", "quantization to download (e.g. Q5_K_M)")
|
|
81
|
+
.option("--component <id...>", "download optional discovered component ids")
|
|
82
|
+
.option("--primary-only", "download only the selected primary quant")
|
|
76
83
|
.option("--list-quants", "list the quantizations the repo offers and exit");
|
|
77
84
|
}
|
|
78
85
|
export async function runAddCommand(repo, options, _command) {
|
|
@@ -80,7 +87,7 @@ export async function runAddCommand(repo, options, _command) {
|
|
|
80
87
|
const token = resolveHfToken(config);
|
|
81
88
|
const { quants, mmproj } = await listRepoQuants(repo, token);
|
|
82
89
|
if (options.listQuants || !options.quant) {
|
|
83
|
-
const { quants: installedQuants } = installedIndex(config);
|
|
90
|
+
const { quants: installedQuants, projectorRepos } = installedIndex(config);
|
|
84
91
|
const listing = {
|
|
85
92
|
type: "list",
|
|
86
93
|
data: quants.map((q) => ({
|
|
@@ -88,7 +95,17 @@ export async function runAddCommand(repo, options, _command) {
|
|
|
88
95
|
size: formatBytes(q.sizeBytes),
|
|
89
96
|
sizeBytes: q.sizeBytes,
|
|
90
97
|
files: q.files.length,
|
|
98
|
+
modelId: installedQuants.get(`${repo.toLowerCase()} ${q.quant.toUpperCase()}`) ?? null,
|
|
91
99
|
installed: installedQuants.has(`${repo.toLowerCase()} ${q.quant.toUpperCase()}`),
|
|
100
|
+
...(mmproj
|
|
101
|
+
? {
|
|
102
|
+
projector: {
|
|
103
|
+
file: mmproj.files[0],
|
|
104
|
+
sizeBytes: mmproj.sizeBytes,
|
|
105
|
+
installed: projectorRepos.has(repo.toLowerCase()),
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
: {}),
|
|
92
109
|
})),
|
|
93
110
|
schema: addQuantSchema,
|
|
94
111
|
};
|
|
@@ -104,23 +121,27 @@ export async function runAddCommand(repo, options, _command) {
|
|
|
104
121
|
message: `${repo} has no ${options.quant} - available: ${quants.map((q) => q.quant).join(", ") || "none"}`,
|
|
105
122
|
});
|
|
106
123
|
}
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
const requested = new Set(options.component ?? []);
|
|
125
|
+
const unknown = [...requested].filter((id) => id !== "vision-projector");
|
|
126
|
+
if (unknown.length)
|
|
127
|
+
throw new CommandError({
|
|
128
|
+
code: "NO_COMPONENT",
|
|
129
|
+
message: `unknown bundle components: ${unknown.join(", ")}`,
|
|
130
|
+
});
|
|
131
|
+
const includeProjector = Boolean(mmproj) &&
|
|
132
|
+
(requested.has("vision-projector") ||
|
|
133
|
+
(!options.primaryOnly && options.component === undefined));
|
|
134
|
+
const files = [...choice.files, ...(includeProjector ? mmproj.files : [])];
|
|
135
|
+
const total = choice.sizeBytes + (includeProjector ? mmproj.sizeBytes : 0);
|
|
136
|
+
const written = await downloadRepoFilesWithProgress({
|
|
137
|
+
activityTarget: `${repo} (${choice.quant})`,
|
|
138
|
+
progressLabel: `${repo} ${choice.quant}`,
|
|
139
|
+
totalBytes: total,
|
|
111
140
|
repo,
|
|
112
141
|
files,
|
|
113
142
|
destRoot: managedModelsDir(config),
|
|
114
143
|
token,
|
|
115
|
-
onProgress: (p) => {
|
|
116
|
-
const pct = total ? Math.floor((p.receivedBytes / total) * 100) : 0;
|
|
117
|
-
if (pct !== lastPct && pct % 5 === 0) {
|
|
118
|
-
lastPct = pct;
|
|
119
|
-
process.stderr.write(` ${repo} ${choice.quant}: ${pct}%\r`);
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
144
|
});
|
|
123
|
-
process.stderr.write("\n");
|
|
124
145
|
return {
|
|
125
146
|
type: "single",
|
|
126
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
|