mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 18:39:33 +01:00
feat(local-model): contract for the install stage (types, stubs, fake runtime)
The types, the module skeletons and the test fake that let the runtime, the install job and the window be built at the same time. Types: - src/types/local-model-install.ts (new): the runtime pin, the install states, steps and error codes with one sentence and one next step each, the progress, the `install` object of GET /api/local-model, the bodies and 409 answers of the install verbs, the state file. `LocalModelStatus` moves here; `install` is optional until the route emits it. - src/types/local-model.ts: the catalogue row's Mac memory figures, the measured speed of a tested entry, the `measured` budget source, `RecommendOptions`, and the row tag, the coding agents and the install total of a candidate. All optional until the catalogue and `recommend()` carry them, so the read-only stage keeps compiling and its tests stay green. Modules with final signatures and bodies that throw: runtime.ts (the compiled pin is final), ollama-client.ts, developer-tools.ts, state-store.ts, install-job.ts (the state, step and transition tables are final), connect.ts, custom-model-hosts-writer.ts, cli-install-runner.ts. Nothing imports them yet, so the server is unchanged. Implemented: paths.ts (every folder of the feature, from dataPath()), and `modelFitsBudget()` in recommend.ts, the judgement the job makes with the measured budget before it downloads a model. `recommend()` takes the options parameter and does not read it yet. Test fake: test/fixtures/fake-ollama.ts, a runtime on a port the system picks, in the shapes measured on a Mac: version, tags, ps, the pull stream (MLX and GGUF lines, an error line, a cut stream, a resumed pull), chat with and without a tool call, the model list, a runtime archive with Range support, and a fake child process that logs the device total. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
66ca92f7cf
commit
771072c36b
17 files changed
+3770
-23
No files matched your search
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @fileoverview The shared runner of a STOCK CLI's install command: the one place Codeman
|
||||
* executes an install one-liner, used by `POST /api/clis/:id/install`
|
||||
* (src/web/routes/cli-registry-routes.ts) and by the local model install job.
|
||||
*
|
||||
* CONTRACT STAGE: `CLI_INSTALL_TIMEOUT_MS` is final. Every function below has its final
|
||||
* signature and a body that throws. The bodies are MOVED here from cli-registry-routes.ts
|
||||
* next, and that route keeps its behaviour byte for byte (same answers, same audit entry,
|
||||
* same 409 for a second install of one id, same environment).
|
||||
*
|
||||
* What moves, unchanged in behaviour:
|
||||
* - `installEnv()` and `npmGlobalPrefixWritable()`: the installer's environment is the
|
||||
* server's minus every `CODEMAN_*` variable, with npm's global prefix pointed at a place
|
||||
* the server user can write. test/routes/cli-registry-routes.test.ts imports both from the
|
||||
* route module, which re-exports them from here.
|
||||
* - `runInstallCommand()`: `shell: true`, its own process group, output capped, the whole
|
||||
* group killed on timeout. New: an abort signal that kills the group the same way, so the
|
||||
* install job's Cancel has something to stop.
|
||||
* - `forgetResolvedCli()`: after an install, successful or not, every cached lookup of the
|
||||
* CLI's binaries is dropped, or a pre-install miss is replayed for up to five minutes.
|
||||
* - The set of ids with an install running. ONE set for both callers
|
||||
* (`tryBeginCliInstall()`), so the route and the job can never run two installers of one
|
||||
* CLI over the same prefix.
|
||||
*
|
||||
* Rules:
|
||||
* - The command executed is only ever a STOCK entry's (`stockInstallCommand()` reads the
|
||||
* compiled `STOCK_CLIS`, never the registry merged with `clis.json`, so an override file
|
||||
* cannot change what runs). A custom entry's install text is never executed.
|
||||
* - Who may call it is the caller's rule, not this module's: the route keeps its
|
||||
* `cliManagementEnabled` gate; the install job runs it only for a harness row of the local
|
||||
* model catalogue, after the person's consent click on a screen that showed the literal
|
||||
* command, admin only in multi-user mode, and writes the admin audit entry itself.
|
||||
*/
|
||||
|
||||
/** Marks a body the contract stage left unwritten. */
|
||||
function notImplemented(what: string, ..._args: unknown[]): never {
|
||||
throw new Error(`${what} is not implemented yet`);
|
||||
}
|
||||
|
||||
/** How long an install command may run before its process group is killed. */
|
||||
export const CLI_INSTALL_TIMEOUT_MS = 300_000;
|
||||
|
||||
/** How an install command ended. */
|
||||
export interface CliInstallResult {
|
||||
/** The exit code, `null` when the command was killed or could not be started. */
|
||||
code: number | null;
|
||||
/** stdout and stderr together, capped. */
|
||||
output: string;
|
||||
/** The timeout killed it. */
|
||||
timedOut: boolean;
|
||||
/** The abort signal killed it. */
|
||||
aborted: boolean;
|
||||
}
|
||||
|
||||
export interface RunInstallCommandOptions {
|
||||
/** Kills the installer's whole process group (SIGTERM, then SIGKILL). */
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when `npm install -g` can write to this environment's npm global prefix, or when that
|
||||
* cannot be told (then nothing is redirected). Moved from cli-registry-routes.ts unchanged.
|
||||
*/
|
||||
export async function npmGlobalPrefixWritable(source: NodeJS.ProcessEnv): Promise<boolean> {
|
||||
return notImplemented('npmGlobalPrefixWritable', source);
|
||||
}
|
||||
|
||||
/**
|
||||
* The environment of an install command: `source` minus every `CODEMAN_*` variable, with
|
||||
* `NPM_CONFIG_PREFIX` pointed at `$HOME/.local` inside the Compose container or when the
|
||||
* global prefix is not writable. Moved from cli-registry-routes.ts unchanged.
|
||||
*/
|
||||
export function installEnv(
|
||||
source: NodeJS.ProcessEnv = process.env,
|
||||
prefixWritable: (env: NodeJS.ProcessEnv) => boolean = () => true
|
||||
): NodeJS.ProcessEnv {
|
||||
return notImplemented('installEnv', source, prefixWritable);
|
||||
}
|
||||
|
||||
/** Runs one install command to its end. Never throws: a spawn that fails is a result with `code: null`. */
|
||||
export async function runInstallCommand(
|
||||
command: string,
|
||||
options: RunInstallCommandOptions = {}
|
||||
): Promise<CliInstallResult> {
|
||||
return notImplemented('runInstallCommand', command, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* The install command of the STOCK CLI `id` for this platform, read from the compiled
|
||||
* `STOCK_CLIS`. `undefined` for an id that is not a stock CLI or has no command here.
|
||||
*/
|
||||
export function stockInstallCommand(id: string): string | undefined {
|
||||
return notImplemented('stockInstallCommand', id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claims the one install slot of `id`. Returns the function that releases it, or `null` when
|
||||
* an install of that CLI is already running (the caller answers 409 or waits).
|
||||
*/
|
||||
export function tryBeginCliInstall(id: string): (() => void) | null {
|
||||
return notImplemented('tryBeginCliInstall', id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forgets every cached binary lookup of a CLI: the per-id resolver and every per-binary cache,
|
||||
* hits and misses alike. Called after anything that may have changed what is on disk.
|
||||
*/
|
||||
export function forgetResolvedCli(id: string, binaries: readonly string[]): void {
|
||||
return notImplemented('forgetResolvedCli', id, binaries);
|
||||
}
|
||||
Reference in new issue
Block a user