7.8 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| ticket98-opencode-modelsdev-cache-seed | memory note ticket98-opencode-modelsdev-cache-seed |
|
Ticket #98 — Fix spawn OpenCode : seed du cache models.dev isolé
Symptôme
Wizard profil OpenCode first-run : provider catalogue cloud (ex: zai/ZAI Code) + modèle choisi → après sauvegarde l'agent tourne en glm-5.2 quel que soit le modèle. Persistance correcte (profiles.json conserve opencodeProvider.model). glm-5.2 est le fallback interne d'OpenCode, absent du code IdeA.
Cause racine (confirmée + affinée par Architect)
Asymétrie picker↔spawn, pas seulement « pas de bloc models » :
- Picker (
provider_catalogue.rs:79-84,150-153) : lit le cache models.dev viaopencode_models_cache_path()qui résout le vrai cache hôte (XDG_CACHE_HOME ?? ~/.cache). Permet d'afficher zai + ses modèles. - Spawn (
lifecycle.rs:2386,2400-2407;assistant/mod.rs:272,277-285) : IdeA ré-isoleXDG_CACHE_HOME→.opencode/cachevide sousrun_dir. OpenCode ne voit plus le cache. - Rendu (
opencode_provider_config_json,lifecycle.rs:2795-2821/assistant/mod.rs:446-466) : pourconfig.custom == None(défaut pour zai), IdeA n'émet queapiKey+model: "zai/<m>", sans blocmodels. Correct uniquement pour les 3 built-ins hardcodés dans le binaire OpenCode (anthropic,openai,openrouter).
→ OpenCode reçoit model: "zai/<m>", ne reconnaît pas zai comme built-in, ne trouve pas le cache models.dev (isolé vide) → fallback silencieux glm-5.2.
- Local/custom marchent : émettent un bloc
modelsauto-suffisant (assistant/mod.rs:364-381,:447-460). - 3 built-ins marchent : hardcodés dans OpenCode.
- Bug ne touche que les providers connus d'OpenCode exclusivement via models.dev.
Approche figée : B2 — Semer le cache models.dev isolé depuis le cache hôte
Après création du XDG_CACHE_HOME isolé, copier best-effort le fichier cache models.dev hôte (opencode_models_cache_path() → <isolated_cache>/opencode/models.json) via le port FileSystem. Lecture hôte = std::fs (identique au picker) ; écriture dans la home isolée.
Pourquoi pas les autres :
- (A) Émettre un bloc
models+npm+baseURLpour les catalogue → écartée en v1 : IdeA devrait capturer le bonnpmAI-SDK par provider depuis models.dev (@ai-sdk/anthropicvsopenai-compatible…) — dupliquerait la connaissance du registry OpenCode (violation OCP), risque de régression built-ins. Gardée en escalade si B2 défait par refresh. - (B1) Pointer vers le vrai cache hôte (ne plus isoler) → écartée : casse l'isolation en écriture (pollution + race entre sessions).
- (C) Bundler une copie statique models.dev dans IdeA → écartée : staleness + diverge picker/spawn.
Auto-cohérence B2 : la précondition du bug (cache hôte présent au picker) garantit la précondition du fix (fichier à copier au spawn). Cache hôte absent → picker ne montre que les 3 built-ins → bug non atteint → fix non requis.
Contrat figé
Ports / DTO
- Aucun nouveau port / entité domaine / DTO modifié. Réutilise le port
FileSystemdéjà injecté sur le chemin spawn. Lecture source =std::fshôte (mécanisme identique au picker). - Rendre publique
opencode_models_cache_path()(source de vérité unique — encode le workaround du bug upstream #8235 ; ne pas re-dériver).
Fichiers (périmètre DevBackend)
crates/application/src/agent/provider_catalogue.rs— exposeropencode_models_cache_path()enpub(ou ajouterpub fn read_models_dev_cache_bytes() -> Option<Vec<u8>>).crates/application/src/agent/mod.rs— re-export.crates/application/src/agent/lifecycle.rs— brancheapply_mcp_config(≈2382-2408) : aprèscreate_dir_alldu cache + créationxdg_cache, semer<xdg_cache>/opencode/models.jsondepuis le cache hôte, best-effort. Branche commune opencode + opencodeProvider.crates/infrastructure/src/assistant/mod.rs— branche miroir (≈268-285) : même appel.- Factoriser :
application::agent::seed_opencode_models_cache(fs: &dyn FileSystem, isolated_cache_dir: &str)appelée par les deux sites. Partie pure testableseed_from_bytes(fs, dest, src_bytes); wrapper impurstd::fs::readfin.
Invariants (stricts)
- Isolation préservée en écriture : HOME, XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME restent sous
run_dir. Aucune var d'env repointée vers l'hôte. Seul le contenu du cache isolé est semé (lecture seule). - Best-effort, n'échoue jamais le launch : source absente/illisible ou erreur FS → pas d'échec du spawn. Symétrique du contrat best-effort existant de
apply_mcp_config(lifecycle.rs:2422-2425). ≠resolve_opencode_provider_api_key(échec dur). Le seed est mou. - Pas de régression local/custom :
opencode_config_json(llamacpp) etopencode_provider_config_json(custom==Some)byte-identiques (rendu non touché ; seed additif orthogonal). - Pas de régression built-ins : anthropic/openai/openrouter (
custom==None, hardcodés) restent fonctionnels. - Exclusion mutuelle
opencodevsopencodeProvider(#97) : non touchée. - Source de vérité unique du chemin models.dev :
opencode_models_cache_path()réutilisée. - Cohérence picker↔spawn : modèles résolvables au spawn = sur-ensemble de ceux du picker (même fichier source).
Hors périmètre (ne pas faire ici)
- Résolution providers pour projets remote (SSH/WSL) (picker lit déjà le cache hôte — incohérence pré-existante). Fix corrige le cas local, ne régresse pas le remote.
- Déduplication du rendu lifecycle↔infra (
opencode_provider_config_json×2) — on factorise uniquement le seeder. - Aucune modif UI/wizard.
QA — 2 couches
- Unitaire
application(automatisable, déterministe) :- Partie pure
seed_from_bytes(fs, dest, src_bytes): bytes présents → assert<dest>/opencode/models.jsonécrit identique ;None→ aucun fichier et pas d'erreur. - Non-régression rendu : JSON de
opencode_config_jsonetopencode_provider_config_json(custom==Some/None)byte-identiques (tests existantslifecycle.rs:4428+verts). - ⇒ Ne prouve pas la résolution réelle par OpenCode.
- Partie pure
- Intégration / réelle-exécution (gated, propriété QA) : spawn réel d'OpenCode avec profil
zai(custom==None) contre un cache models.dev fixture contenantzai; assert pas de fallback glm-5.2 (modèlezai/<choix>effectivement utilisé). Gater#[ignore]/env (clé API + réseau). C'est ce test qui prouve le bug corrigé.
Risque résiduel + escalade
- Inconnu empirique : est-ce qu'OpenCode au démarrage tente de rafraîchir models.dev (écrase notre copie, ou hang sans réseau) ?
OPENCODE_DISABLE_AUTOUPDATE=1ne vise que l'auto-update du binaire, pas sûr qu'il couvre le refresh models. QA doit vérifier sur le test d'intégration. Si le refresh défait B2 → escalader vers (A) : capturernpm+baseURLréels par provider dans le parseur models.dev et peuplercustom.
Topologie
- Branche :
feature/ticket98-opencode-modelsdev-cache-seeddepuisdevelop@69e5878(#97 exclusion mutuelle prérequis y est fusionné :0f0a76d+ merge5533073).crates/propre. - Carnet #98 version 2 = source de vérité du cadrage.
Lié
- #97 (relatesTo) : exclusion mutuelle provider — prérequis livré.