- `LocalModelService.launchRefusal(endpointId)`: what a session launch asks
before it starts on the managed endpoint. It answers the error that
refuses the launch (the runtime child is not alive, another program holds
the port, the runtime keeps stopping, the model's digest changed) or
nothing. New error code `runtime-not-running` with its sentence, for a
runtime the person stopped: only a refused session start says it.
- The fake runtime's MacBook log line is rebuilt from the two values the
measurement notes quote, so it is `INFERENCE_COMPUTE_LINE_MACBOOK` now.
Only the Mac mini line is a verbatim capture and keeps `REAL_`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
Review fixes for the read-only recommendation and its card.
Recommendation (pure):
- `limited` is now a statement about the Mac: no model with 12 billion
parameters or more fits. It is judged on everything that fits, before
the disk filter and whatever the pick is. A 32 or 36 GB Mac whose pick
is a 9B model no longer reads "This Mac can run a small model" above a
list of 20B, 27B and 35B models, and knowing the chip no longer lowers
the verdict. A small pick under `ok` carries a note that says so.
- A model that fits the memory but has no room on the disk is no longer
dropped in silence: the best-ranked one is named in a note with the GB
that are missing. A full disk cannot turn a 48 GB Mac into a
small-model Mac.
- "Models fit, but not at a context a harness needs" has its own
sentence with the budget and the need, worded as an estimate. The
16 GB Mac mini misses by 1.3 points (10.3 of 10.4 GB) and was told
flatly that its memory is too little, the sentence of an 8 GB Mac.
- The installed memory is named by the size the Mac is sold as (48, not
51.5): the hardware line and the memory sentence. Every computed size
stays decimal GB from the one formatter. An unknown memory size is no
longer reported as "0.0 GB".
- Slow is graded on the unrounded speed: 9.95 to 9.9996 tokens per
second printed as 10 and read ok.
- The disk sentence names the 10 GiB that must stay free, so its
numbers add up for the reader.
Card and Settings group:
- The Settings group "Model on this Mac" is hidden on a server that is
not a Mac, and until the first answer says it is one.
- Only `403 FORBIDDEN` is a refusal. `403 PASSWORD_CHANGE_REQUIRED`
(an admin who must change the password) is a failed request and is
asked again, so that admin no longer loses the card until a reload.
- Each fit and speed label carries what it measures as its tooltip and
accessible name, an alternative with a speed estimate prints it, and
the count beside "Other models that fit" is gone (it was the number
of rows, capped at four, not the number of models that fit).
- The line saying nothing can be set up yet sits right under the pick,
in the colour of every other line (it was 3.9:1), and says whose step
it is.
- The dismissal key is named for this stage, so hiding the read-only
card does not hide the install offer later.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The read-only route behind the recommendation, and the one module that reads
the CLI registry for it.
- harness-facts.ts: one fact per catalogue harness row, resolved in the CLI
registry at call time: the registry's label, `installed` from the existing
availability probe (asked once per build), `installable` for a stock CLI
whose install command for this platform needs no administrator rights, and
`acceptsModelId`, the test a custom model launch applies to the model id.
A row whose CLI is unknown, disabled or has no custom model mechanism gets
no fact. No CLI id is compared with a literal.
- routes/local-model-routes.ts: answers { hardware, recommendation,
catalogUpdated } in the usual envelope and reformats nothing. Admin only in
multi-user mode (403), checked before anything is probed. A failed probe is
an answer (200, the hardware's `source` says what was read); a 500 is left
for a fault in the catalogue, the harness facts or recommend(). It writes no
file and reads no setting.
Tests: test/local-model-harness-facts.test.ts,
test/routes/local-model-routes.test.ts (app.inject(), no port; the temp HOME
is byte-identical before and after the requests).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The core of the read-only recommendation: what this Mac is, what the catalogue
knows, and which model fits. Nothing is downloaded, installed or written.
- hardware.ts: one bounded async spawn of /usr/sbin/sysctl with seven keys,
without -n, parsed as "key: value" lines by the pure parseSysctl(). Free disk
of the data dir through statfs behind the bounded path probe. Fallbacks from
node:os, a 60 s cache, never throws, never system_profiler. A wired limit of
0 means "not set".
- local-model-catalog.ts: seven models, three harness rows (pi gated) and the
chips table as a compiled constant, validated by a Zod schema on first use.
No override file. Model ids are name:tag in lowercase without a slash; every
model carries paramsB and an (empty) tested list. Chip names match on the
end of the chip string, so 'M4' is not the chip of 'Apple M4 Pro'.
- recommend.ts: device budget (the wired limit when above 0, else 2/3 of
memory below 36 GB and 3/4 from 36 GB, less the reserve), need = weights +
KV cache + 1 GiB + prompt cache, fit good up to 0.85 and tight up to 0.98,
speed ok, unknown or slow (a chip in no chips row is unknown), and the
ranking. Verdicts ok, limited (best model under 12 billion parameters) and
none with its reason. "Recommended" only for a pick with a tested entry for
this Mac, which no row has today.
- types/local-model.ts: comments brought in line with the code (chip matching,
budgetShare rounding, a budget of 0 on an Intel Mac).
- cli-registry-no-id-branching.test.ts: the guard also catches a comparison of
a `cli` field with a CLI id (plus one stray blank line Prettier refused).
Tests: test/local-model-format.test.ts, test/local-model-hardware.test.ts
(the captured output of two real Macs), test/local-model-catalog.test.ts,
test/local-model-recommend.test.ts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The contract three builders code against in parallel. Nothing is recommended
yet: the route answers 501 and the catalogue is empty.
- src/types/local-model.ts: HardwareFacts, the catalogue rows, HarnessFact,
Candidate, Recommendation, LocalModelStatus, and LOCAL_MODEL_LABELS, the one
list of sentences the server may put into the payload (the zh-CN dictionary
is keyed by them).
- src/local-model/format.ts: formatGb, the one place a byte count becomes a
size a person reads (decimal GB), and memClassGb.
- src/config/local-model-catalog.ts: the Zod schema (model ids are name:tag,
lowercase, no slash) and an empty stock catalogue. No override file.
- Skeletons with their final signatures: hardware.ts, recommend.ts,
harness-facts.ts, routes/local-model-routes.ts (injectable dependencies),
public/local-model-ui.js with its script tag.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>