mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-19 09:45:07 +00:00
Phase 1 Day 5. Mistral Vibe provider plugin lands as Candidate. STATIC_
REGISTRY now length 3 (anthropic + openai + mistral). vibe CLI not
installed on the orchestrator machine and owner has no Le Chat Pro
subscription, so D8 follows the D6 docs-only authority pattern. D-later
verification (once a tester with a vibe install and subscription is
available) will resolve UNPINNED assumptions A4, A6, A7, A8.
Files:
NEW: lib/providers/mistral.mjs (~720 lines) — Mistral Vibe provider.
Spawns `vibe --prompt PROMPT --output streaming` per docs.
Reads MISTRAL_API_KEY env var with ~/.vibe/.env fallback.
Supports VIBE_HOME env override per docs. Mirrors D4/D6 plugin
structure incl. __setSpawnImpl/__resetSpawnImpl for tests.
MOD: lib/providers/index.mjs (+12/-1) — STATIC_REGISTRY now length 3.
listAllProviderNames returns [anthropic, openai, mistral].
MOD: models-registry.json (+26 lines) — providers.mistral with 2
canonical date-stamped IDs (devstral-2-25-12, devstral-small-2-
25-12) and 4 short-form aliases for user convenience.
MOD: test-features.mjs — Suite 12 with 48 tests covering contract
conformance, IR translation, mock-spawn behaviour, healthCheck,
estimateCost, auth-artifact reading.
Authority citations (all WebFetched and verified during reviewer pass):
DOCS-1: docs.mistral.ai/mistral-vibe/terminal/quickstart — `vibe
--prompt PROMPT --max-turns 5 --max-price 1.0 --output json` example
+ Output Format Options enumeration: text default, json single blob,
streaming NDJSON.
DOCS-2: docs.mistral.ai/mistral-vibe/terminal/configuration — pin
`~/.vibe/.env`, MISTRAL_API_KEY env var, VIBE_HOME override.
DOCS-3: docs.mistral.ai/mistral-vibe/introduction/configuration —
second confirmation of MISTRAL_API_KEY + ~/.vibe/.env (model
selection via config.toml `/config` slash command, NOT --model
flag).
DOCS-4: deepwiki.com/mistralai/mistral-vibe/9.3-cli-commands-reference
— full flag enumeration confirms no --model flag exists. Programmatic
mode trigger is --prompt; flags are: --continue, --max-price,
--max-turns, --output, --prompt, --resume, --setup, --trust,
--upgrade, --version, --workdir, --no-autofill, --no-header,
--no-dev, --enabled-tools, --help.
DOCS-5: mistral.ai/news/devstral-2-vibe-cli — launch announcement,
names Devstral 2 (123B) and Devstral Small 2 (24B), 256K context,
pricing $0.40/$2.00 and $0.10/$0.30 per MTok.
DOCS-6: help.mistral.ai/en/articles/347532 — Vibe included in Le Chat
Pro.
DOCS-7: legal.mistral.ai/terms/usage-policy — no anti-third-party
clauses; ADR 0006 Tier D classification holds.
DOCS-MAIN: docs.mistral.ai/mistral-vibe/overview — main Vibe overview.
DOCS-8: docs.mistral.ai/getting-started/models/models_overview —
canonical Mistral models registry. Pin for date-stamped IDs
devstral-2-25-12 / devstral-small-2-25-12. Caught by D8 review-2.
Architectural decisions:
1. `--output streaming` (NOT `json`). Per DOCS-1 verbatim: streaming
emits NDJSON per message; json emits a single blob at the end.
Original D8 draft used `json` which is incompatible with the
plugin's line-buffered stdout parser. Review-2 caught this; fixed
before commit.
2. No --model flag. Per DOCS-4 full flag enumeration there is no
--model flag in programmatic mode. Model selection happens via
~/.vibe/config.toml. The IR's `model` field is used by OLP for
routing only; Vibe uses whatever model is in user-level config.
Documented in lossy translations + as A5 CONFIRMED-NOT-APPLICABLE.
3. Canonical IDs primary, short forms as aliases. models-registry.json
uses devstral-2-25-12 / devstral-small-2-25-12 as primary `id`s
matching the canonical Mistral models registry; user-facing short
forms (devstral-2, devstral-small-2, devstral, devstral-small) are
aliases. Plugin's models[] array includes both canonical IDs AND
alias keys so getProviderForModel routes either form. Same pattern
codified for Codex aliases in D6.
4. Auth precedence: MISTRAL_API_KEY env > ~/.vibe/.env > null.
Documented in DOCS-2. readAuthArtifact supports
MISTRAL_VIBE_AUTH_PATH env override for testing.
5. Mistral stays Candidate. STATIC_REGISTRY.length === 3 but
loadProviders({}) returns empty Map; only loadProviders({
enabled: { mistral: true }}) loads it. POST /v1/chat/completions
devstral-* still returns 503 until config flag is set and E2E
audit passes.
Reviewer chain (Iron Rule 10):
Implementer: sonnet (general-purpose).
Fresh-context reviewer: opus (ecc:code-reviewer). Verdict round-1:
REQUEST_CHANGES (2 blockers caught).
Reviewer ran npm test (222/222 pass), WebFetched all 8 canonical
Mistral docs URLs, discovered DOCS-8 (models registry) which
sonnet missed — exactly the D6 failure pattern, repeated. Reviewer
independently verified `--output streaming` vs `json` semantics
against the live docs page text, not paraphrases.
Reviewer blocking findings folded in this commit:
B-1 (--output json wrong): Plugin now passes `--output streaming` per
docs verbatim. Test "irToMistral: user message → args with --prompt
and --output streaming" updated to assert the new arg and reject
the old one.
B-2 (canonical models page missed): models-registry.json refactored
to use date-stamped canonical IDs as primary; short forms as
aliases. mistral.mjs header adds DOCS-8 as the new canonical
authority pin for model IDs. Plugin's models[] array merges
canonical + alias keys so existing routing tests pass with either
form.
B-3 (404 claim incorrect): mistral.mjs header DOCS-MAIN updated.
docs.mistral.ai/mistral-vibe/overview is 200 OK; sonnet's 404
claim was a path-normalization mismatch.
Reviewer non-blocking suggestions (deferred to D-later / not D8 scope):
- VIBE_HOME env override has no Suite 12 test (implementation
present at mistral.mjs:262). Parallel gap to D6 CODEX_HOME.
- _extractKeyFromDotenv has no direct unit test.
- config.toml model selection mechanism (Vibe-specific quirk —
no --model flag means OLP can't pass model per request). A D-later
ADR note will discuss whether OLP should write a project-local
./.vibe/config.toml before spawn or accept the user-level config
as authoritative.
Test count: 174 (after D6) → 222 (after D8).
Verification:
node --check on all touched files: clean.
npm test on Node 25.8.0: 222/222 pass in 210ms.
STATIC_REGISTRY = [anthropic, openai, mistral] verified.
hygiene grep: zero personal-name/path/token hits. Fixtures use
<fake-mistral-api-key> placeholders.
Co-Authored-By: Claude Opus 4.7 (noreply@anthropic.com)
116 lines
4.2 KiB
JavaScript
116 lines
4.2 KiB
JavaScript
/**
|
|
* lib/providers/index.mjs — Static provider registry
|
|
*
|
|
* Authority: ADR 0002 § "Loading model"
|
|
*
|
|
* This file is a hand-maintained static enumeration. There is no filesystem
|
|
* scan, no dynamic discovery, no npm-installed plugin loading. Adding a
|
|
* provider requires: (1) write lib/providers/<name>.mjs, (2) add one import +
|
|
* one entry to STATIC_REGISTRY below, (3) README § "Supported Providers",
|
|
* (4) inclusion ADR per ADR 0006.
|
|
*
|
|
* At D4 (Phase 1 Day 2), the anthropic plugin is added to STATIC_REGISTRY
|
|
* as a Candidate. It is NOT Enabled by default — a config with
|
|
* { enabled: { anthropic: true } } is required, which only happens after D5
|
|
* E2E audit passes per ALIGNMENT.md § Provider Inventory.
|
|
*
|
|
* At D6 (Phase 1 Day 4), the openai (Codex) plugin is added to STATIC_REGISTRY
|
|
* as a Candidate. Enabled by { enabled: { openai: true } } after D7 E2E audit.
|
|
*
|
|
* At D8 (Phase 1 Day 5), the mistral (Mistral Vibe) plugin is added to
|
|
* STATIC_REGISTRY as a Candidate. Enabled by { enabled: { mistral: true } }
|
|
* after D-later E2E audit. Authority: ADR 0006 Tier D classification for
|
|
* Mistral Vibe (Le Chat Pro); docs-based authority from
|
|
* https://docs.mistral.ai/mistral-vibe/terminal/quickstart.
|
|
*/
|
|
|
|
import { validateProvider } from './base.mjs';
|
|
import anthropicDefault from './anthropic.mjs';
|
|
import codexDefault from './codex.mjs';
|
|
import mistralDefault from './mistral.mjs';
|
|
|
|
// Normalize default export pattern
|
|
const anthropic = anthropicDefault;
|
|
const codex = codexDefault;
|
|
const mistral = mistralDefault;
|
|
|
|
// ── Static registry ───────────────────────────────────────────────────────
|
|
|
|
const STATIC_REGISTRY = [
|
|
anthropic,
|
|
codex,
|
|
mistral,
|
|
];
|
|
|
|
// ── Registry functions ────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Loads and validates providers, filtering by the enabled set in config.
|
|
* At D4, the anthropic provider is Candidate only — it will not appear in
|
|
* the loaded Map unless config.enabled.anthropic === true (set by D5 after E2E).
|
|
*
|
|
* @param {{ enabled?: Record<string,boolean> }} [config={}]
|
|
* @returns {Map<string, import('./base.mjs').ProviderContractV1>}
|
|
*/
|
|
export function loadProviders(config = {}) {
|
|
const loaded = new Map();
|
|
|
|
for (const p of STATIC_REGISTRY) {
|
|
const { valid, errors } = validateProvider(p);
|
|
if (!valid) {
|
|
throw new Error(`Provider ${p?.name ?? 'unknown'} fails contract validation: ${errors.join('; ')}`);
|
|
}
|
|
|
|
const enabled = config.enabled?.[p.name] === true;
|
|
if (enabled) {
|
|
loaded.set(p.name, p);
|
|
}
|
|
}
|
|
|
|
return loaded;
|
|
}
|
|
|
|
/**
|
|
* Returns the first provider in `loadedProviders` whose `models[]` includes
|
|
* the exact `modelString`. Returns null if no provider is found.
|
|
*
|
|
* This is the naive lookup strategy for D4. Phase 2+ may add prefix/alias
|
|
* matching once models-registry.json is consulted per AGENTS.md SPOT policy.
|
|
*
|
|
* @param {Map<string, import('./base.mjs').ProviderContractV1>} loadedProviders
|
|
* @param {string} modelString
|
|
* @returns {{ provider: import('./base.mjs').ProviderContractV1, name: string }|null}
|
|
*/
|
|
export function getProviderForModel(loadedProviders, modelString) {
|
|
for (const [name, p] of loadedProviders) {
|
|
if (p.models.includes(modelString)) {
|
|
return { provider: p, name };
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Looks up a provider by name in the loaded Map. Returns null if not found.
|
|
* Useful for tests that need to access a specific provider directly.
|
|
*
|
|
* @param {Map<string, import('./base.mjs').ProviderContractV1>} loadedProviders
|
|
* @param {string} name
|
|
* @returns {import('./base.mjs').ProviderContractV1|null}
|
|
*/
|
|
export function getProviderByName(loadedProviders, name) {
|
|
return loadedProviders.get(name) ?? null;
|
|
}
|
|
|
|
/**
|
|
* Returns all provider names in the static registry (whether enabled or not).
|
|
* Used by /health and diagnostics.
|
|
* At D4: returns ['anthropic']; at D6: returns ['anthropic', 'openai'];
|
|
* at D8: returns ['anthropic', 'openai', 'mistral'].
|
|
*
|
|
* @returns {string[]}
|
|
*/
|
|
export function listAllProviderNames() {
|
|
return STATIC_REGISTRY.map(p => p.name);
|
|
}
|