Skip to content

fix(mlx): hide the unavailable MLX provider stub - #676

Merged
sroussey merged 1 commit into
mainfrom
claude/libs-issues-triage-prs-mh6x2o-576
Aug 5, 2026
Merged

fix(mlx): hide the unavailable MLX provider stub#676
sroussey merged 1 commit into
mainfrom
claude/libs-issues-triage-prs-mh6x2o-576

Conversation

@sroussey

@sroussey sroussey commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes #576

What changed and why

@workglow/mlx is a stub. It bundles no mlx-lm Python runtime, and its single registered run-fn throws "MLX provider not available: Python runtime not bundled." on every call. Despite that, it inherited AiProvider's default isAvailable() of true and registerMlx() registered it unconditionally — so after registration the registry reported it as a real, installed provider:

  • getProvider("LOCAL_MLX") → the provider instance
  • getInstalledProviderIds() → included LOCAL_MLX
  • getProviderIdsForCapabilities(["text.generation"]) → included LOCAL_MLX

That last one is the one that bites: any UI or validation path asking "which providers can do text generation?" was offered a provider whose every invocation fails.

Two changes, both confined to providers/mlx:

  1. MlxProvider overrides isAvailable() to return false. The seam already existed on AiProvider (chrome-ai is the only other override); MLX just wasn't using it. The stale class doc — which claimed the provider "registers cleanly so the UI can list it" — is corrected.
  2. registerMlx() consults isAvailable(), warns, and returns without touching the registry. Its options parameter now defaults to {} so the call reads as a probe.

The throwing run-fn is deliberately kept as a backstop: AiProvider.register() rejects a registration with zero run-fns ("worker is required when no promiseRunFns are provided"), so the provider cannot be made capability-free, and anyone constructing + registering it directly still gets a clear immediate failure rather than silence. No new API was added to packages/ai, and no package was deleted.

⚠️ Downstream behavior change

This changes what registerMlx() does, not just what it reports. Any consumer (builder, sec, or other) that calls registerMlx() and then expects getProvider("LOCAL_MLX") to be defined now gets undefined. The same applies to getInstalledProviderIds() and getProviderIdsForCapabilities(), which no longer list LOCAL_MLX. Nothing inside this repo calls registerMlx(), but @workglow/mlx is a published public package, so downstream code asserting on its presence must be updated. A warn-level log line fires on every skipped registration, so the skip is visible rather than silent.

Tests

New packages/test/src/test/ai/MlxProvider.test.ts (6 tests, registry isolated per-test with setAiProviderRegistry(new AiProviderRegistry())) covering: isAvailable() is false; registerMlx() resolves and registers nothing across all three listing surfaces; the direct-registration backstop still rejects with /Python runtime not bundled/; and a metadata guard on name / displayName / isLocal / supportsBrowser / supportsServer.

Verification

All commands run from the branch, real output:

Command Result
bun run build passTasks: 84 successful, 84 total
bun scripts/test.ts ai vitest passTest Files 39 passed (39), Tests 253 passed (253)
bun run build:types --force passTasks: 41 successful, 41 total, Cached: 0 cached (cache bypassed so this is a real compile)
bun scripts/typecheck-budget.ts --json --no-build passtypecheck-budget: OK (38 packages within budget)
npx prettier --check (3 changed files) passAll matched files use Prettier code style!
eslint src --max-warnings 0 in providers/mlx pass — no output

New test file in isolation:

✓ MlxProvider > reports itself unavailable (no bundled mlx-lm Python runtime)
✓ MlxProvider > declares its runtime placement metadata
✓ MlxProvider > registerMlx > resolves without throwing
✓ MlxProvider > registerMlx > registers no provider while unavailable
✓ MlxProvider > registerMlx > does not offer the provider for text.generation
✓ MlxProvider > direct registration backstop > still throws on inference for anyone registering the provider directly
Test Files  1 passed (1)     Tests  6 passed (6)

The tests were confirmed to fail against the pre-fix code. Stashing only the two source changes and rebuilding produced exactly the bug's three assertions:

AssertionError: expected true to be false                        // isAvailable()
AssertionError: expected MlxProvider{ …(7) } to be undefined     // getProvider("LOCAL_MLX")
AssertionError: expected [ 'LOCAL_MLX' ] to not include 'LOCAL_MLX'
                                     // getProviderIdsForCapabilities(["text.generation"])
Test Files  1 failed (1)     Tests  3 failed | 3 passed (6)

Typecheck budget: providers/mlx measures 578 instantiations against a committed budget of 577. It is not gated — the budget file's floor is 50000, three orders of magnitude above it — and the guard reports OK, so the committed value was left untouched rather than churned for a package the gate ignores.

Follow-up (not fixed here)

chrome-ai has the same latent shape: WebBrowserProvider overrides isAvailable() (probing for the LanguageModel global) but registerWebBrowser() registers unconditionally without consulting it. Outside a Chrome build with the AI globals present, it lands in the registry exactly as MLX did. That is a separate behavior change with a real (non-stub) provider behind it and should get its own issue rather than riding along here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PwyJuFrJnibKvrrk8Fa4Fn


Generated by Claude Code

The MLX provider is a stub: it has no bundled mlx-lm Python runtime and its
only run-fn throws on every call. It nevertheless inherited the default
`isAvailable()` of `true` and `registerMlx()` registered it unconditionally,
so `getProviders()` / `getInstalledProviderIds()` listed it and
`getProviderIdsForCapabilities(["text.generation"])` returned it — offering
users a text-generation provider whose every invocation fails.

`MlxProvider` now overrides `isAvailable()` to return false, and
`registerMlx()` consults it, warns, and returns without touching the
registry. The throwing run-fn stays as a backstop for anyone constructing
and registering the provider directly (the base class rejects a
registration with no run-fns), and all provider metadata is unchanged.

`registerMlx()`'s options parameter now defaults to `{}` so the call reads
as a probe rather than requiring arguments it will usually ignore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PwyJuFrJnibKvrrk8Fa4Fn
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 62.32% 28104 / 45096
🔵 Statements 62.19% 29143 / 46855
🔵 Functions 62.56% 5359 / 8565
🔵 Branches 51.23% 13849 / 27032
File CoverageNo changed files found.
Generated in workflow #2844 for commit cc27a9d by the Vitest Coverage Report Action

@sroussey
sroussey merged commit 95583b8 into main Aug 5, 2026
14 checks passed
@sroussey
sroussey deleted the claude/libs-issues-triage-prs-mh6x2o-576 branch August 13, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Eliminate or hide mlx provider stub

2 participants