@otto-code/brain 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/otto-brain +2 -0
- package/dist/bench/context-corpus.d.ts +58 -0
- package/dist/bench/context-corpus.js +210 -0
- package/dist/bench/corpus.d.ts +31 -0
- package/dist/bench/corpus.js +379 -0
- package/dist/bench/curated-repos.d.ts +35 -0
- package/dist/bench/curated-repos.js +41 -0
- package/dist/bench/health.d.ts +50 -0
- package/dist/bench/health.js +112 -0
- package/dist/bench/index.d.ts +103 -0
- package/dist/bench/index.js +209 -0
- package/dist/bench/mine.d.ts +32 -0
- package/dist/bench/mine.js +101 -0
- package/dist/bench/repo-task.d.ts +93 -0
- package/dist/bench/repo-task.js +380 -0
- package/dist/bench/repo.d.ts +125 -0
- package/dist/bench/repo.js +195 -0
- package/dist/bench/rescore.d.ts +41 -0
- package/dist/bench/rescore.js +127 -0
- package/dist/bench/tasks.d.ts +124 -0
- package/dist/bench/tasks.js +1249 -0
- package/dist/bench/verify.d.ts +142 -0
- package/dist/bench/verify.js +350 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +72 -0
- package/dist/commands/bench.d.ts +20 -0
- package/dist/commands/bench.js +222 -0
- package/dist/commands/calibrate.d.ts +19 -0
- package/dist/commands/calibrate.js +75 -0
- package/dist/commands/catalog.d.ts +32 -0
- package/dist/commands/catalog.js +48 -0
- package/dist/commands/config.d.ts +13 -0
- package/dist/commands/config.js +90 -0
- package/dist/commands/lifecycle.d.ts +29 -0
- package/dist/commands/lifecycle.js +194 -0
- package/dist/commands/pull.d.ts +22 -0
- package/dist/commands/pull.js +135 -0
- package/dist/commands/report.d.ts +10 -0
- package/dist/commands/report.js +34 -0
- package/dist/commands/rescore.d.ts +11 -0
- package/dist/commands/rescore.js +48 -0
- package/dist/commands/runtime.d.ts +21 -0
- package/dist/commands/runtime.js +62 -0
- package/dist/commands/scan.d.ts +24 -0
- package/dist/commands/scan.js +55 -0
- package/dist/commands/search.d.ts +31 -0
- package/dist/commands/search.js +135 -0
- package/dist/commands/share.d.ts +26 -0
- package/dist/commands/share.js +148 -0
- package/dist/commands/sweep.d.ts +21 -0
- package/dist/commands/sweep.js +69 -0
- package/dist/commands/ui.d.ts +9 -0
- package/dist/commands/ui.js +28 -0
- package/dist/config/env.d.ts +10 -0
- package/dist/config/env.js +48 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.js +9 -0
- package/dist/config/otto-home.d.ts +2 -0
- package/dist/config/otto-home.js +24 -0
- package/dist/config/paths.d.ts +20 -0
- package/dist/config/paths.js +33 -0
- package/dist/config/private-files.d.ts +3 -0
- package/dist/config/private-files.js +32 -0
- package/dist/config/profiles.d.ts +34 -0
- package/dist/config/profiles.js +116 -0
- package/dist/config/schema.d.ts +1035 -0
- package/dist/config/schema.js +191 -0
- package/dist/config/store.d.ts +11 -0
- package/dist/config/store.js +73 -0
- package/dist/gguf.d.ts +61 -0
- package/dist/gguf.js +231 -0
- package/dist/gpu.d.ts +12 -0
- package/dist/gpu.js +49 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +5 -0
- package/dist/models/dirs.d.ts +9 -0
- package/dist/models/dirs.js +21 -0
- package/dist/models/download.d.ts +31 -0
- package/dist/models/download.js +105 -0
- package/dist/models/enrich.d.ts +36 -0
- package/dist/models/enrich.js +68 -0
- package/dist/models/hf.d.ts +62 -0
- package/dist/models/hf.js +114 -0
- package/dist/models/index.d.ts +15 -0
- package/dist/models/index.js +49 -0
- package/dist/models/manage.d.ts +27 -0
- package/dist/models/manage.js +112 -0
- package/dist/models/pick.d.ts +3 -0
- package/dist/models/pick.js +36 -0
- package/dist/models/scan.d.ts +13 -0
- package/dist/models/scan.js +185 -0
- package/dist/ops/archive.d.ts +34 -0
- package/dist/ops/archive.js +105 -0
- package/dist/ops/calibrate.d.ts +49 -0
- package/dist/ops/calibrate.js +74 -0
- package/dist/ops/report.d.ts +26 -0
- package/dist/ops/report.js +532 -0
- package/dist/ops/results.d.ts +207 -0
- package/dist/ops/results.js +250 -0
- package/dist/ops/sweep.d.ts +77 -0
- package/dist/ops/sweep.js +126 -0
- package/dist/output/index.d.ts +5 -0
- package/dist/output/index.js +5 -0
- package/dist/output/render.d.ts +4 -0
- package/dist/output/render.js +96 -0
- package/dist/output/types.d.ts +52 -0
- package/dist/output/types.js +9 -0
- package/dist/output/with-output.d.ts +3 -0
- package/dist/output/with-output.js +42 -0
- package/dist/run.d.ts +5 -0
- package/dist/run.js +19 -0
- package/dist/runtime/args.d.ts +21 -0
- package/dist/runtime/args.js +71 -0
- package/dist/runtime/index.d.ts +13 -0
- package/dist/runtime/index.js +41 -0
- package/dist/runtime/lmstudio.d.ts +10 -0
- package/dist/runtime/lmstudio.js +88 -0
- package/dist/runtime/managed.d.ts +26 -0
- package/dist/runtime/managed.js +148 -0
- package/dist/service/model-selector.d.ts +36 -0
- package/dist/service/model-selector.js +123 -0
- package/dist/service/pid-lock.d.ts +18 -0
- package/dist/service/pid-lock.js +50 -0
- package/dist/service/router.d.ts +144 -0
- package/dist/service/router.js +746 -0
- package/dist/service/scheduler.d.ts +74 -0
- package/dist/service/scheduler.js +110 -0
- package/dist/service/serve.d.ts +41 -0
- package/dist/service/serve.js +278 -0
- package/dist/service/supervisor.d.ts +67 -0
- package/dist/service/supervisor.js +222 -0
- package/dist/service/tailscale.d.ts +19 -0
- package/dist/service/tailscale.js +62 -0
- package/dist/service/tls.d.ts +77 -0
- package/dist/service/tls.js +192 -0
- package/dist/sysmon.d.ts +50 -0
- package/dist/sysmon.js +103 -0
- package/dist/tui/app.d.ts +208 -0
- package/dist/tui/app.js +1801 -0
- package/dist/tui/screen.d.ts +72 -0
- package/dist/tui/screen.js +176 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.js +7 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +8 -0
- package/dist/vram.d.ts +67 -0
- package/dist/vram.js +132 -0
- package/package.json +58 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { RankedModel } from "../ops/results.js";
|
|
2
|
+
import type { GpuInfo, Model } from "../types.js";
|
|
3
|
+
export declare const MIN_TRUSTED_RUNS = 2;
|
|
4
|
+
export declare const MAX_TRUSTED_STD = 0.15;
|
|
5
|
+
export declare const PROBE_CONTEXT_TOKENS = 8192;
|
|
6
|
+
/** Whether a scanned model carries catalog metadata marking it a coder. */
|
|
7
|
+
export declare function isCodingCapable(model: Model): boolean;
|
|
8
|
+
export interface SelectCodingModelOptions {
|
|
9
|
+
/** Every scanned model (the catalog the router already has). */
|
|
10
|
+
models: Model[];
|
|
11
|
+
/** Track A's per-model bench ranking (mean score + runs + std). */
|
|
12
|
+
ranking: RankedModel[];
|
|
13
|
+
/**
|
|
14
|
+
* VRAM-fit predicate. Omit (or pass undefined) to skip the fit filter — the
|
|
15
|
+
* caller does this when GPU info is absent, mirroring serve.ts's "absent →
|
|
16
|
+
* skip" behaviour.
|
|
17
|
+
*/
|
|
18
|
+
fits?: (model: Model) => boolean;
|
|
19
|
+
/** Id of the currently-loaded model, a tiebreak to avoid a needless swap. */
|
|
20
|
+
preferLoadedId?: string | null;
|
|
21
|
+
/** The existing default, returned when no candidate survives. */
|
|
22
|
+
fallback: Model | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Pick the best-ranked coding model that fits the VRAM budget. Pure and
|
|
26
|
+
* deterministic — no IO, no clock, no randomness.
|
|
27
|
+
*/
|
|
28
|
+
export declare function selectCodingModel({ models, ranking, fits, preferLoadedId, fallback, }: SelectCodingModelOptions): Model | null;
|
|
29
|
+
/**
|
|
30
|
+
* Build the route-time VRAM-fit predicate, or undefined when GPU info is absent
|
|
31
|
+
* (no nvidia-smi) so the caller skips the fit filter entirely. The probe profile
|
|
32
|
+
* is built once and reused: a model fits when its weights + KV at
|
|
33
|
+
* PROBE_CONTEXT_TOKENS sit inside the card's total VRAM budget.
|
|
34
|
+
*/
|
|
35
|
+
export declare function makeVramFitPredicate(gpu: GpuInfo | null): ((model: Model) => boolean) | undefined;
|
|
36
|
+
//# sourceMappingURL=model-selector.d.ts.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-time model selection for the UNNAMED request path.
|
|
3
|
+
*
|
|
4
|
+
* When a client hits the brain without naming a model, the old default served
|
|
5
|
+
* "whatever is loaded, else catalog[0]" — blind to which local model is actually
|
|
6
|
+
* the best coder. This picks the best-ranked coding-capable model that fits the
|
|
7
|
+
* VRAM budget instead, wiring together Track A (the bench ranking) and Track B1
|
|
8
|
+
* (the catalog coding metadata carried onto the scanned Model).
|
|
9
|
+
*
|
|
10
|
+
* `selectCodingModel` is PURE: it takes the models, the ranking, an optional
|
|
11
|
+
* VRAM-fit predicate, and a fallback, and returns the chosen model with no IO —
|
|
12
|
+
* so the decision logic is trivially testable. `makeVramFitPredicate` is the one
|
|
13
|
+
* impure edge (it reads GPU total VRAM and runs a vram.budget), deliberately kept
|
|
14
|
+
* out of the pure path.
|
|
15
|
+
*/
|
|
16
|
+
import { ProfileSchema } from "../config/schema.js";
|
|
17
|
+
import * as vram from "../vram.js";
|
|
18
|
+
// --- Minimum-confidence gate. A bench score is only "trusted" for ranking when
|
|
19
|
+
// it is backed by enough repeated runs and its spread across those runs is
|
|
20
|
+
// tight. rankModels reports one entry per model with the MEAN overall score
|
|
21
|
+
// (0..1), the run COUNT, and the sample STD (also 0..1). A single run has std 0
|
|
22
|
+
// — falsely confident — so we require at least two runs, and we reject a mean
|
|
23
|
+
// whose runs disagree by more than MAX_TRUSTED_STD. Untrusted models stay
|
|
24
|
+
// eligible but sort below every trusted one.
|
|
25
|
+
export const MIN_TRUSTED_RUNS = 2;
|
|
26
|
+
export const MAX_TRUSTED_STD = 0.15;
|
|
27
|
+
// --- Route-time fit probe. We do not have the saved per-model profile or a
|
|
28
|
+
// measured calibration here, so fit is tested at a modest working context: a
|
|
29
|
+
// model whose weights + KV at PROBE_CONTEXT_TOKENS overflow VRAM is excluded,
|
|
30
|
+
// and one that fits is includable (serve.ts clamps the real context down at load
|
|
31
|
+
// time via fitToBudget).
|
|
32
|
+
export const PROBE_CONTEXT_TOKENS = 8192;
|
|
33
|
+
const CODING_USE_CASE = "coding";
|
|
34
|
+
// Matches curated tier labels like "coding", "coder", "code-specialist".
|
|
35
|
+
const CODING_TIER_RE = /cod(ing|er|e-)/i;
|
|
36
|
+
/** Whether a scanned model carries catalog metadata marking it a coder. */
|
|
37
|
+
export function isCodingCapable(model) {
|
|
38
|
+
if (model.useCases?.some((u) => u.toLowerCase() === CODING_USE_CASE))
|
|
39
|
+
return true;
|
|
40
|
+
if (model.tier && CODING_TIER_RE.test(model.tier))
|
|
41
|
+
return true;
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
/** Look a scanned model up in the ranking by id first, then display name. */
|
|
45
|
+
function rankingLookup(ranking) {
|
|
46
|
+
const byId = new Map();
|
|
47
|
+
const byName = new Map();
|
|
48
|
+
for (const entry of ranking) {
|
|
49
|
+
if (entry.id)
|
|
50
|
+
byId.set(entry.id, entry);
|
|
51
|
+
if (!byName.has(entry.displayName))
|
|
52
|
+
byName.set(entry.displayName, entry);
|
|
53
|
+
}
|
|
54
|
+
return (model) => byId.get(model.id) ?? byName.get(model.displayName) ?? null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Decision order:
|
|
58
|
+
* trusted bench score (desc) → any bench score (desc, untrusted-with-data over
|
|
59
|
+
* none) → loaded model (avoid a swap) → larger advertised context → name.
|
|
60
|
+
* The trailing name comparison makes the order total, so the pick is fully
|
|
61
|
+
* deterministic given the same inputs.
|
|
62
|
+
*/
|
|
63
|
+
function compareCandidates(a, b, preferLoadedId) {
|
|
64
|
+
if (a.trusted !== b.trusted)
|
|
65
|
+
return a.trusted ? -1 : 1;
|
|
66
|
+
if (a.score !== b.score)
|
|
67
|
+
return b.score - a.score;
|
|
68
|
+
const aLoaded = preferLoadedId != null && a.model.id === preferLoadedId;
|
|
69
|
+
const bLoaded = preferLoadedId != null && b.model.id === preferLoadedId;
|
|
70
|
+
if (aLoaded !== bLoaded)
|
|
71
|
+
return aLoaded ? -1 : 1;
|
|
72
|
+
const aCtx = a.model.contextMax ?? 0;
|
|
73
|
+
const bCtx = b.model.contextMax ?? 0;
|
|
74
|
+
if (aCtx !== bCtx)
|
|
75
|
+
return bCtx - aCtx;
|
|
76
|
+
return a.model.displayName.localeCompare(b.model.displayName);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Pick the best-ranked coding model that fits the VRAM budget. Pure and
|
|
80
|
+
* deterministic — no IO, no clock, no randomness.
|
|
81
|
+
*/
|
|
82
|
+
export function selectCodingModel({ models, ranking, fits, preferLoadedId = null, fallback, }) {
|
|
83
|
+
if (models.length === 0)
|
|
84
|
+
return fallback;
|
|
85
|
+
// 1. Candidate set: coding-capable models. If nothing is tagged (a catalog
|
|
86
|
+
// with no coding metadata, or hand-placed models), don't fail closed —
|
|
87
|
+
// fall back to the whole set.
|
|
88
|
+
const tagged = models.filter(isCodingCapable);
|
|
89
|
+
let candidates = tagged.length > 0 ? tagged : models;
|
|
90
|
+
// 2. VRAM fit filter (skipped when no predicate — i.e. GPU info absent). If
|
|
91
|
+
// nothing coding-capable fits, keep the existing default rather than
|
|
92
|
+
// forcing a model that overflows the budget.
|
|
93
|
+
if (fits) {
|
|
94
|
+
const fitting = candidates.filter((m) => fits(m));
|
|
95
|
+
if (fitting.length === 0)
|
|
96
|
+
return fallback;
|
|
97
|
+
candidates = fitting;
|
|
98
|
+
}
|
|
99
|
+
// 3. Rank by bench score behind the confidence gate.
|
|
100
|
+
const lookup = rankingLookup(ranking);
|
|
101
|
+
const scored = candidates.map((model) => {
|
|
102
|
+
const entry = lookup(model);
|
|
103
|
+
const trusted = Boolean(entry && entry.runs >= MIN_TRUSTED_RUNS && entry.std <= MAX_TRUSTED_STD);
|
|
104
|
+
const score = entry ? entry.overall : Number.NEGATIVE_INFINITY;
|
|
105
|
+
return { model, trusted, score };
|
|
106
|
+
});
|
|
107
|
+
scored.sort((a, b) => compareCandidates(a, b, preferLoadedId));
|
|
108
|
+
return scored[0]?.model ?? fallback;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Build the route-time VRAM-fit predicate, or undefined when GPU info is absent
|
|
112
|
+
* (no nvidia-smi) so the caller skips the fit filter entirely. The probe profile
|
|
113
|
+
* is built once and reused: a model fits when its weights + KV at
|
|
114
|
+
* PROBE_CONTEXT_TOKENS sit inside the card's total VRAM budget.
|
|
115
|
+
*/
|
|
116
|
+
export function makeVramFitPredicate(gpu) {
|
|
117
|
+
if (!gpu)
|
|
118
|
+
return undefined;
|
|
119
|
+
const profile = ProfileSchema.parse({ contextSize: PROBE_CONTEXT_TOKENS });
|
|
120
|
+
const totalVramBytes = gpu.totalBytes;
|
|
121
|
+
return (model) => vram.budget({ model, profile, totalVramBytes }).fits;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=model-selector.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface PidRecord {
|
|
2
|
+
pid: number;
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
startedAt: string;
|
|
6
|
+
/** Whether the listener terminates TLS. Absent on records written before TLS support. */
|
|
7
|
+
secure?: boolean;
|
|
8
|
+
/** The user-facing address (MagicDNS/cert hostname when TLS is on). Optional for old records. */
|
|
9
|
+
displayHost?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function writePidFile(record: PidRecord, env?: NodeJS.ProcessEnv): void;
|
|
12
|
+
export declare function readPidFile(env?: NodeJS.ProcessEnv): PidRecord | null;
|
|
13
|
+
export declare function removePidFile(env?: NodeJS.ProcessEnv): void;
|
|
14
|
+
/** signal 0 probes liveness without delivering a signal; EPERM means alive. */
|
|
15
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
16
|
+
/** The running service record, clearing a stale pid file if the process is gone. */
|
|
17
|
+
export declare function readRunningService(env?: NodeJS.ProcessEnv): PidRecord | null;
|
|
18
|
+
//# sourceMappingURL=pid-lock.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The service pid/lock file, following the daemon's `otto.pid` pattern: a small
|
|
3
|
+
* JSON record of the running brain's pid + bind address under
|
|
4
|
+
* `$OTTO_HOME/otto-brain/otto-brain.pid`. Lets `otto brain status`/`stop` find a
|
|
5
|
+
* running instance and lets the Otto daemon supervise a managed child.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
8
|
+
import { resolveBrainPaths } from "../config/paths.js";
|
|
9
|
+
import { writePrivateFileAtomicSync } from "../config/private-files.js";
|
|
10
|
+
export function writePidFile(record, env = process.env) {
|
|
11
|
+
const { pidFile } = resolveBrainPaths(env);
|
|
12
|
+
writePrivateFileAtomicSync(pidFile, `${JSON.stringify(record, null, 2)}\n`);
|
|
13
|
+
}
|
|
14
|
+
export function readPidFile(env = process.env) {
|
|
15
|
+
const { pidFile } = resolveBrainPaths(env);
|
|
16
|
+
if (!existsSync(pidFile))
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(pidFile, "utf8"));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function removePidFile(env = process.env) {
|
|
26
|
+
const { pidFile } = resolveBrainPaths(env);
|
|
27
|
+
rmSync(pidFile, { force: true });
|
|
28
|
+
}
|
|
29
|
+
/** signal 0 probes liveness without delivering a signal; EPERM means alive. */
|
|
30
|
+
export function isProcessAlive(pid) {
|
|
31
|
+
try {
|
|
32
|
+
process.kill(pid, 0);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return error.code === "EPERM";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** The running service record, clearing a stale pid file if the process is gone. */
|
|
40
|
+
export function readRunningService(env = process.env) {
|
|
41
|
+
const record = readPidFile(env);
|
|
42
|
+
if (!record)
|
|
43
|
+
return null;
|
|
44
|
+
if (!isProcessAlive(record.pid)) {
|
|
45
|
+
removePidFile(env);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return record;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=pid-lock.js.map
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import type { Supervisor } from "./supervisor.js";
|
|
3
|
+
import { type RankedModel } from "../ops/results.js";
|
|
4
|
+
import type { GpuInfo, Model } from "../types.js";
|
|
5
|
+
import type { Profile } from "../config/schema.js";
|
|
6
|
+
type Verdict = "ok" | "reasoning-only" | "truncated" | "failed";
|
|
7
|
+
/** A logger sink; only `warn` is used by the router. */
|
|
8
|
+
export interface Logger {
|
|
9
|
+
warn(message: string): void;
|
|
10
|
+
}
|
|
11
|
+
/** A source of the catalog: a getter, a snapshot array, or nothing. */
|
|
12
|
+
export type GetCatalog = (() => Model[]) | Model[] | null;
|
|
13
|
+
interface TelemetryTotals {
|
|
14
|
+
requests: number;
|
|
15
|
+
ok: number;
|
|
16
|
+
reasoningOnly: number;
|
|
17
|
+
truncated: number;
|
|
18
|
+
failed: number;
|
|
19
|
+
}
|
|
20
|
+
/** One recorded completion outcome; fields vary by streaming vs. buffered. */
|
|
21
|
+
export interface TelemetryRecord {
|
|
22
|
+
verdict: string;
|
|
23
|
+
at?: string;
|
|
24
|
+
path?: string;
|
|
25
|
+
ms?: number;
|
|
26
|
+
streamed?: boolean;
|
|
27
|
+
finishReason?: string | null;
|
|
28
|
+
contentChars?: number;
|
|
29
|
+
reasoningChars?: number;
|
|
30
|
+
outputTokens?: number | null;
|
|
31
|
+
toolCalls?: number;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
/** The verdict of classifying a completion body. */
|
|
35
|
+
export interface Analysis {
|
|
36
|
+
finishReason: string | null;
|
|
37
|
+
contentChars: number;
|
|
38
|
+
reasoningChars: number;
|
|
39
|
+
outputTokens: number | null;
|
|
40
|
+
toolCalls: number;
|
|
41
|
+
verdict: Verdict;
|
|
42
|
+
}
|
|
43
|
+
export declare class Telemetry {
|
|
44
|
+
keep: number;
|
|
45
|
+
records: TelemetryRecord[];
|
|
46
|
+
totals: TelemetryTotals;
|
|
47
|
+
constructor(keep?: number);
|
|
48
|
+
record(entry: TelemetryRecord): void;
|
|
49
|
+
/** Advice derived from observed behaviour, not guesswork. */
|
|
50
|
+
get warning(): string | null;
|
|
51
|
+
}
|
|
52
|
+
/** Classify a completion body (Anthropic or OpenAI shaped). */
|
|
53
|
+
export declare function analyse(bodyText: string): Analysis | null;
|
|
54
|
+
type ModelState = "loaded" | "loading" | "not-loaded";
|
|
55
|
+
interface DescribeOptions {
|
|
56
|
+
state?: ModelState;
|
|
57
|
+
profile?: Profile | null;
|
|
58
|
+
createdAt?: Date | null;
|
|
59
|
+
}
|
|
60
|
+
/** An LM Studio-style model description, an OpenAI model object enriched. */
|
|
61
|
+
export interface ModelEntry {
|
|
62
|
+
id: string;
|
|
63
|
+
object: "model";
|
|
64
|
+
created: number;
|
|
65
|
+
owned_by: string;
|
|
66
|
+
type: "vlm" | "llm";
|
|
67
|
+
publisher: string | null;
|
|
68
|
+
arch: string | null;
|
|
69
|
+
compatibility_type: "gguf";
|
|
70
|
+
quantization: string | null;
|
|
71
|
+
state: ModelState;
|
|
72
|
+
max_context_length: number | null;
|
|
73
|
+
loaded_context_length?: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Describe one catalog model the way LM Studio's API does. Returns an
|
|
77
|
+
* OpenAI-style model object enriched with LM Studio's fields (arch,
|
|
78
|
+
* quantization, state, context lengths) so clients can display the real name
|
|
79
|
+
* and know the context window. `state` is 'loaded' | 'loading' | 'not-loaded';
|
|
80
|
+
* `loaded_context_length` is only meaningful for the model actually running.
|
|
81
|
+
*/
|
|
82
|
+
export declare function describeModel(model: Model, options?: DescribeOptions): ModelEntry;
|
|
83
|
+
export declare function describeModel(model: Model | null, options?: DescribeOptions): ModelEntry | null;
|
|
84
|
+
/**
|
|
85
|
+
* Build the LM Studio-style catalog: every model found on disk, with the one
|
|
86
|
+
* the supervisor is running marked 'loaded'. Falls back to just the running
|
|
87
|
+
* model when no catalog provider is wired in.
|
|
88
|
+
*/
|
|
89
|
+
export declare function buildModelList(supervisor: Supervisor, getCatalog: GetCatalog): ModelEntry[];
|
|
90
|
+
export type ModelGateResult = {
|
|
91
|
+
ok: true;
|
|
92
|
+
model: Model;
|
|
93
|
+
} | {
|
|
94
|
+
ok: false;
|
|
95
|
+
status: number;
|
|
96
|
+
message: string;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Pure model-admission decision, factored out of the router so it is unit
|
|
100
|
+
* testable. `pinned` is the single model a locked host serves; `resolved` is the
|
|
101
|
+
* normal catalog resolution used when the lock is off. With the lock on, a
|
|
102
|
+
* request naming a model other than the pin is refused (409) rather than queuing
|
|
103
|
+
* a switch; an unnamed request rides the pin.
|
|
104
|
+
*/
|
|
105
|
+
export declare function decideModelGate(params: {
|
|
106
|
+
lockModel: boolean;
|
|
107
|
+
requestedName: string | null;
|
|
108
|
+
pinned: Model | null;
|
|
109
|
+
resolved: Model | null;
|
|
110
|
+
}): ModelGateResult;
|
|
111
|
+
export interface RouterOptions {
|
|
112
|
+
supervisor: Supervisor;
|
|
113
|
+
telemetry: Telemetry;
|
|
114
|
+
logger?: Logger | null;
|
|
115
|
+
getCatalog?: GetCatalog;
|
|
116
|
+
loadModel?: ((model: Model) => Promise<void>) | null;
|
|
117
|
+
loadRanking?: () => RankedModel[];
|
|
118
|
+
queryGpuInfo?: () => Promise<GpuInfo | null>;
|
|
119
|
+
/** The brain package version, reported on `/__host/status` for the host UI. */
|
|
120
|
+
version?: string | null;
|
|
121
|
+
/** Effective config with secrets redacted — served on `/__host/config`. */
|
|
122
|
+
getConfig?: (() => unknown) | null;
|
|
123
|
+
/** Benchmark rankings/variance/latest — served on `/__host/evals`. */
|
|
124
|
+
getEvals?: (() => unknown) | null;
|
|
125
|
+
/** Live: pin the host to one model (refuse completions naming a different one). */
|
|
126
|
+
getLockModel?: () => boolean;
|
|
127
|
+
/** Live: the configured default model, the pin target before one is resident. */
|
|
128
|
+
getDefaultModel?: () => string | null;
|
|
129
|
+
/**
|
|
130
|
+
* Apply an editable config patch (write config.json, live-switch the model,
|
|
131
|
+
* update the lock), for POST /__host/config. Absent = the write endpoint is
|
|
132
|
+
* not offered. Returns the new effective config (secrets redacted).
|
|
133
|
+
*/
|
|
134
|
+
applyConfigPatch?: ((patch: unknown) => Promise<unknown>) | null;
|
|
135
|
+
/**
|
|
136
|
+
* Live: whether remote clients may WRITE config (POST /__host/config). Off by
|
|
137
|
+
* default, so a shared brain can be used but not reconfigured over the network
|
|
138
|
+
* until its owner opts in. Read/use are unaffected.
|
|
139
|
+
*/
|
|
140
|
+
getAllowConfigWrite?: () => boolean;
|
|
141
|
+
}
|
|
142
|
+
export declare function createRouter({ supervisor, telemetry, logger, getCatalog, loadModel, loadRanking, queryGpuInfo, version, getConfig, getEvals, getLockModel, getDefaultModel, applyConfigPatch, getAllowConfigWrite, }: RouterOptions): (req: http.IncomingMessage, res: http.ServerResponse) => void;
|
|
143
|
+
export {};
|
|
144
|
+
//# sourceMappingURL=router.d.ts.map
|