Repository navigation
Description
Design correction (2026-11-04, Drew): the original ACP-client-host design on this issue is withdrawn. Drew: "the other direct providers do not use acp they emulate exactly how the binary works." This issue now specifies a native wire-protocol provider, like antigravity and mistral in packages/ai. No vibe subprocess, no ACP host, no vibe-acp.
What to build
A provider package @opencode/ai/providers/vibe (provider id vibe) in this repo:
packages/ai/src/providers/vibe.ts— the package (mirrorsproviders/mistral.ts: sameMistralChatprotocol route, same endpoint), registered inproviders/index.tsand in thebuiltinsmap inpackages/core/src/provider.ts.packages/ai/src/providers/vibe-auth.ts— the credential resolver (mirrorsproviders/antigravity-auth.ts:secret-toollookup, in-memory cache +invalidate(),MissingCredentialErrorwhen absent).- Users select it like the other direct providers: declare provider
vibein opencode config withnpm: "@opencode/ai/providers/vibe"and the models below (models.dev carries novibeentry; same contract asantigravity).
Wire spec — verified from the installed mistral-vibe 3.1.0 source on amd-6
Package: ~/.local/share/uv/tools/mistral-vibe/lib/python3.12/site-packages/vibe/ (+ mistralai_vibe_local_harness/).
Completion request (the only call the provider makes)
POST https://api.mistral.ai/v1/chat/completions, streamed (SSE), via the officialmistralaiSDK:Mistral(api_key, server_url = api_base minus any /vN suffix); vibe's presetapi_baseishttps://api.mistral.ai/v1— identical to this repo'sMistralChat.DEFAULT_BASE_URL, so the existingMistralChatprotocol route is reused unchanged.- Headers:
Authorization: Bearer <key>is the ONLY auth header on the API-key path. No tenant/workspace/org headers exist anywhere in the binary. Providerextra_headersdefault empty;x-affinity: <id>appears only when a session affinity id exists (not applicable here). - Body:
model(route model id),messages,temperature(top_p omitted when temperature is set — Mistral rejects both),reasoning_effortwhen the model reasons (off is sent only to known-reasoning models),max_tokenswhen capped,tools+parallel_tool_calls: truewhen tools are present. Usage comes from the stream's usage object (prompt/completion/total + cached input tokens) — the route's existing usage mapping covers this.
Models (Vibe's own presets)
Default: mistral-vibe-cli-latest (alias mistral-medium-3.5, "Mistral Medium 3.5"), temperature 1.0, thinking high (levels off/high), context 262144, image input, cost in/out/cache-read 1.5 / 7.5 / 0.15 per M tokens.
Selectable: mistral-medium-latest, mistral-large-latest, mistral-small-latest, codestral-latest, ministral-14b-latest, ministral-8b-latest, ministral-3b-latest. (Vibe's third-party GLM presets ride a different provider preset and stay out of scope; the zai provider covers them.)
Credential resolution — mirror the binary exactly
The binary's resolve_api_key_with_origin order (first hit wins):
- Explicit provider config (
apiKey/ customauth) — fork-level override for tests and offline use, mirroringantigravity's Config. - Environment
MISTRAL_API_KEY— the binary's process env already includes~/.vibe/.envvalues (loaded under the real env; real env wins), so the resolver checks envMISTRAL_API_KEY, then the~/.vibe/.envdotenv file itself (0600; the binary writes it only when no keyring backend exists). - OS keyring:
secret-tool lookup service ai.mistral.vibe username MISTRAL_API_KEY; legacy servicevibeas fallback (the binary migrates legacy entries forward). The browser sign-in (vibe --setup, PKCE against console.mistral.ai) stores a plain workspace API key there — sent as Bearer verbatim; no refresh, no exchange (unlike antigravity's OAuth JSON).
Keyring read mechanism (must work from this repo's TS runtime on amd-6): exec secret-tool located via SECRET_TOOL_PATH -> PATH -> /nix/store scan for libsecret (the antigravity-auth pattern). Python keyring is broken on amd-6 (kwalletd6 owns org.freedesktop.secrets and its Secret Service cannot do the DH session python-secretstorage mandates; gnome-keyring runs pkcs11-only) — do not use it. DBUS_SESSION_BUS_ADDRESS defaults to unix:path=/run/user/<uid>/bus when unset; 15s timeout; any failure = credential absent, never a hang.
Out of scope (the binary's first-party side calls, not the provider's)
- Model catalog
GET {api_base}/model-catalog?surface=vibe-code(fail-open, cached) andGET https://console.mistral.ai/api/vibe/whoami— the provider ships Vibe's preset table in code instead. - The experimental OIDC sign-in (
VIBE_CLI_OIDC_ENABLED=1, issuer auth.mistral.ai, keyring bundle under serviceai.mistral.vibe.oidc) — the default browser sign-in provisions an API key; OIDC is not the shipped flow.
Acceptance
- Unit tests: resolution order (config > env > .env file > keyring primary > keyring legacy; absent everywhere -> the not-signed-in error) and request shaping (endpoint, Bearer-only auth, default model carries temperature 1.0 +
reasoning_effort: "high", caller-supplied values win). - Live: after the one-time desktop sign-in lands the keyring credential, an opencode session using provider
vibecompletes a real task end-to-end with novibeprocess involved. - Not signed in: any
viberequest fails fast with a clear error naming the missing credential and the fix (runvibe --setuponce, or setMISTRAL_API_KEY).
Acceptance criteria
- Provider package
@opencode/ai/providers/vibe(idvibe) exists inpackages/ai/src/providers/vibe.ts, mirroring theMistralChatprotocol route and endpoint — novibesubprocess, no ACP host. -
vibe-auth.tsresolves credentials like the other direct providers (secret-tool lookup, in-memory cache +invalidate(),MissingCredentialErrorwhen absent). - Provider is registered in
providers/index.tsand thebuiltinsmap inpackages/core/src/provider.ts, and is selectable via config as specified in the body. - PR feat(ai): vibe provider — native emulation of the Mistral Vibe binary (#1653) #1661 (already open, implementing this issue) is shepherded to merge or superseded by an equivalent PR that closes this issue.
Activity
[-]vibe provider: host vibe-acp (Mistral Vibe) as an ACP-client provider seat[/-][+]vibe provider: native wire-protocol emulation of the Mistral Vibe binary (no ACP host, no subprocess)[/+]dezren39 commentedon Nov 4, 2026
🤖 Feeder triage · issue-sweep-rotating (Make triage feeder) · 2026-11-04
Checked (fresh, verified twice): current main
ea2202ef5a65f3839a48c6b3e87918f4b7a7720a. The gap this issue specifies is still present on main:packages/ai/src/providers/(viagh apicontents listing) containsmistral.ts,antigravity.ts, andantigravity-auth.ts— the two mirror patterns this issue names — but novibe.tsand novibe-auth.ts.packages/ai/src/providers/index.tsexportsAntigravity(line 4) andMistral(line 33); there is noVibeexport.repo-health-opencode-pilotqueue (queued/claimed/parked lists checked) — it has not been picked up yet.Verdict: CONFIRMED — not started. The spec is complete and self-contained (wire spec verified from the installed mistral-vibe 3.1.0 source, credential resolution order, model presets, acceptance tests).
What "done" looks like: the issue's own Acceptance section —
vibe+vibe-authproviders mirroringmistral/antigravity-auth, registered inproviders/index.tsand thebuiltinsmap inpackages/core/src/provider.ts; unit tests for resolution order (config > env >~/.vibe/.env> keyring primary > keyring legacy; absent everywhere → the not-signed-in error) and request shaping (Bearer-only auth, default model temperature 1.0 +reasoning_effort: "high"); a live end-to-end session once the one-time desktop sign-in lands the keyring credential. Novibesubprocess, no ACP host.Enqueuing as a triage packet on
repo-health-opencode-pilotfrom this feeder run.dezren39 commentedon Nov 4, 2026
agent-note:v1
🤖 Agent note · state: queued (task 9709)
dezren39 commentedon Nov 4, 2026
Triage verdict — task 9709, lane opencode-a, 2026-11-04 (fresh main ac0ada4)
CONFIRMED not-started; implementation shape confirmed against current main; fix packet enqueued as task 9711.
packages/ai/src/providers/has novibe.ts/vibe-auth.ts;packages/ai/src/providers/index.tsexports Antigravity (:4) and Mistral (:33) with no Vibe export; the builtins map inpackages/core/src/provider.tsregisters@opencode/ai/providers/antigravity(:70) and.../mistral(:113) with no vibe entry.vibe.ts+vibe-auth.ts, exported from providers/index.ts, registered in the core builtins map; no vibe subprocess, no ACP host. The secret-tool pattern to mirror already exists inantigravity-auth.ts(:88-99 — PATH scan, /nix/store candidates, SECRET_TOOL_PATH override).vibe --setup/ MISTRAL_API_KEY) and request shaping (endpoint, Bearer-only auth, default model + temperature 1.0 / reasoning_effort high, caller values win), via the packages/ai test suite.vibe --setupsign-in landing the keyring credential. Triage could NOT verify keyring presence from the sandbox shell (secret-tool is not on PATH there — lookup exit 127; where runnable, the legacy service returned absent), so the live leg stays pending and the implementation packet explicitly does not block on it.agent-note:v1 — repo-queue-worker-shift-opencode lane A, task 9709 triaged, fix = task 9711
dezren39 commentedon Nov 4, 2026
🤖 Agent note · bound
packages/ai/src/providers/vibe.ts+ tests). Pickup was out of band: PR feat(ai): vibe provider — native emulation of the Mistral Vibe binary (#1653) #1661 was opened 2026-11-04T18:13:23Z by a GitHub-app session (authorapp/developing-today) on branchvibe-provider-1653; the picker never applied queue/work labels and never posted an agent note (also recorded as a Duty 4 protocol gap). The queue claim/park verbs are unavailable this run (repo-queue broker tunnel failure + no claim-by-id in the helper build), so task 9711 staysqueuedwith this note as the binding record; the lane that claims it must resume as PR feat(ai): vibe provider — native emulation of the Mistral Vibe binary (#1653) #1661's shepherd — review CI, merge when green, do NOT re-implement.9 remaining items
fix(ai): stamp vibe requests with the binary attribution metadata (#1653
Merge pull request #1661 from developing-today-forks/vibe-provider-1653
dezren39 commentedon Nov 5, 2026
Task 9711 picked up by repo-worker-opencode on lane 3fe37e8a-c61b-419c-9e97-769ecd264d5d. Fresh GitHub state revealed PR #1661 was already bound to this task and has merged. I verified its merged diff includes the provider, credential resolution, provider registration, tests, and the Vibe attribution metadata correction. No code or PR changes were made by this lane.
dezren39 commentedon Nov 5, 2026
Closing after PR #1661 merged at 2026-11-05T00:30:03Z. Fresh review verified merged commit 7774bf0 includes the native Vibe provider and auth, registration, request-shaping and credential tests, and the follow-up metadata.call_source stamp plus caller-override coverage. The PR commit reports 1932 packages/ai tests passing and clean typecheck; no CI checks were reported by gh pr checks.
developing-today commentedon Nov 5, 2026
Live acceptance for the provider spec'd here: PASS against PR #1661 — full proof in #1661 (comment)
An opencode session (standalone, PR-branch build) completed a real task through provider
vibe/mistral-vibe-cli-latestwith no interactive step, authenticated solely by the keyring credential from the one-timevibe --setupsign-in (env carried noMISTRAL_API_KEY). The two failures on the way were acceptance-harness defects (Bun CWD tsconfig discovery; a context-only modellimitdropped at config normalization), documented in the linked comment — no provider changes were needed.dezren39 commentedon Nov 6, 2026
Root cause found + fixed (2026-11-06): vibe-direct was spending the wrong KEY
vibe-direct's wire emulation was faithful — endpoint, stamp, presets all as designed. The failure was credential resolution: Mistral meters per key, not by call metadata, and the server environment handed vibe-direct the API-budget key.
Evidence
ai.mistral.vibesign-in key (sha256 fpb782943c6ef9f380…) and the server envMISTRAL_API_KEY(fp144cdb68d8d89c4b…) — the latter is byte-identical to the enrolledmistralintegration credential (the account API key).vibeCLI (3.1.0) on the keyring key returnsPONGwhile the API budget is exhausted. The same CLI forced onto the env key returns the exact production error:402 {"message":"API budget exhausted.","type":"billing_api_budget_exhausted","code":"2303"}.call_source: "vibe_code"stamp neither moves billing nor is required for plan billing. (The header comment inpackages/ai/src/providers/vibe.tsclaiming the stamp is "the metering key" was wrong — corrected in docs(vibe): billing follows the key, not the metadata stamp #1808.)vibe-auth.tsfaithfully mirrors the binary: env →~/.vibe/.env→ keyring. The trap was upstream: the code repo's home-manager config materializedMISTRAL_API_KEY(from sops, the Bitwardenmistral.api-key) into the opencode server unit'sEnvironmentFile, so tier-1 env resolution always won and the keyring plan key was never reached.Attribution for the 2026-11-05 API burn
Yes — our own traffic. Route history: 8,693 vibe-direct attempts on 2026-11-05, 8,660 of them inside the window in which the API pool went 15.986% → 100% (walled 22:45Z), while vibe-direct carried the deepseek-flash-plan load. The pool burn was the mis-keyed vibe leg, not external API use.
Fix
opencode.service.d/10-vibe-keyring-fix.confswaps the unit's ExecStartPre for a scrub that deletes only theMISTRAL_API_KEY=line from the materialized env file (DEEPSEEK line preserved). Verified: server env carries noMISTRAL_API_KEY;vibe-direct/mistral-small-latest→ OK andvibe-direct/mistral-vibe-cli-latest→ OK with the API pool still walled;mistral-plan/mistral-small-latest→ served by the vibe leg (after the stale pre-fix quota cooldown/skip adjustments from the mis-keyed era expired/were cleared).01c97fe2) — the Nix materializer no longer writesMISTRAL_API_KEY; config comments corrected. Lands on the next switch; the drop-in + scrub script must be removed at that point (noted in the PR).8cf16377) — vibe.ts header comment corrected.The
mistralprovider leg is unaffected by the env removal: its enrolled credential is its source (verified live: it returns the budget 402, not an auth error, with the env var absent).One residual, by design: the router's attempt-reported quota adjustments written during the mis-keyed era (30-min skip on both
vibe-direct/andmistral/, refreshed by any 402 drawn during restarts) can briefly re-wall the route after a restart until they expire; they do not renew now that the vibe leg succeeds.dezren39 commentedon Nov 6, 2026
Verification timeline correction + one residual quirk (2026-11-06, later)
Two corrections to the comment above, for the record:
Route recovery landed at ~07:50Z, not 07:19Z. The direct vibe-direct leg verified immediately after the env fix (attempt successes 07:19:46Z and 07:20:34Z), but the mistral-plan route stayed walled until ~07:50Z by attempt-reported quota adjustments written during the mis-keyed era and refreshed by two restart-transition 402s at 07:19:16Z (30-min skips on
vibe-direct/andmistral/, route_note 5685/5686), plus the stale 05:15Z quota cooldown (cleared by cooldown-end rows in route_health). Since 07:50:08Z the route has been drawn repeatedly in live fleet traffic (routespeed→ deepseek-flash-plan → mistral-plan), choosing vibe-direct every time with successful attempts; the mistral leg is excluded by its own credential quota cooldown, which is correct — its budget is genuinely full. (An earlier progress note of mine reported a 07:50Z success prematurely via a corrupted background-tool result; the figures in this comment are re-read from route_decision/route_attempt directly.)Residual quirk (independent of the credential bug): a bare
opencode run -m mistral-plan/mistral-small-latest(with or without a#variant) fails instantly withSessionRunnerModel.ModelUnavailableErrorand writes no route_decision — the model never reaches route resolution on that path. Route groups serve normally when drawn through route-context sessions (the claimant/fleet path, and as members of other routes). Observed consistently before and after every credential gate cleared, so it is not the budget bug; flagging it here in case a human ever pins a route group directly from the CLI.