Skip to content

feat(wp-m1): bury model_tiers, migrate to vanilla OpenCode (v3.0 BREAKING)#107

Merged
Steffen025 merged 4 commits intomainfrom
feat/wp-m1-vanilla-opencode-migration
Apr 11, 2026
Merged

feat(wp-m1): bury model_tiers, migrate to vanilla OpenCode (v3.0 BREAKING)#107
Steffen025 merged 4 commits intomainfrom
feat/wp-m1-vanilla-opencode-migration

Conversation

@Steffen025
Copy link
Copy Markdown
Owner

@Steffen025 Steffen025 commented Apr 11, 2026

Summary

WP-M1 Vanilla OpenCode Migration — Remove the model_tiers feature and the dependency on the Steffen025/opencode fork. PAI-OpenCode now runs on vanilla OpenCode from opencode.ai. Each agent has exactly one model configured in opencode.json.

Target release: PAI-OpenCode v3.0 (currently unreleased, tracked in CHANGELOG.md [3.0.0] - Unreleased).
Status: BREAKING change for users with model_tiers in their local opencode.json. Migration script provided.
ADR: ADR-019 Vanilla OpenCode Migration

Closes

Community Credit — @ktneely (PR #103)

Huge thanks to @ktneely for PR #103 "Updating some model profiles", which identified that several of the OpenCode Zen models we were recommending had been retired. Kevin's roster update is integrated into this PR in commit 55adca0.

What Kevin caught:

  • zen.yaml (free tier): kimi-k2.5-free, glm-4.7-free, minimax-m2.1-free were all retired from Zen's free tier
  • zen-paid.yaml (paid tier): glm-4.7glm-5, minimax-m2.1minimax-m2.5, gemini-3-progemini-3.1-pro
  • local.yaml (Ollama): qwen2.5-coder:{1.5b,7b,32b} retired from Ollama library → upgraded to qwen3.5:{2b,9b,27b} family, Writer agent now uses mistral-small3.2:24b
  • model-config.ts: grok-code no longer exists on Zen free tier

What I adjusted when integrating:

Kevin's PR was opened in January 2026. I re-verified his roster against the April 2026 OpenCode Zen pricing page and found that the free tier had rotated again — his mimo-v2-pro-free/mimo-v2-omni-free suggestions are no longer offered. I went with the current April 2026 free set instead: big-pickle, qwen3.6-plus-free, nemotron-3-super-free, minimax-m2.5-free, gpt-5-nano. Same spirit as Kevin's PR — just aligned to today's menu. Also added opencode/qwen3-coder-480b to the paid tier's CodexResearcher slot (a new Zen addition that's a great fit for a code-specialist researcher).

Kevin is credited in-line in every file he touched (zen.yaml, zen-paid.yaml, local.yaml) with a header comment acknowledging his contribution. Community contributions like this — carefully-researched fixes from domain experts — are exactly what a healthy OSS project needs. Thank you, Kevin! 🙏

Why

The Steffen025/opencode fork existed solely to cherry-pick an unmerged upstream PR (95fd6ea7 by external contributor Mohammed Muzammil Anwar) that provided the model_tier runtime parameter for routing Task tool calls to different models per agent.

The investigation revealed:

  • Fork was 980 commits behind upstream/dev (~4 months stale)
  • Fork had only 2 custom commits, both duplicate cherry-picks of the same orphaned commit
  • The cherry-picked commit was never merged to upstream — it lives only in a PR branch
  • Upstream built a different feature instead: variants (PRs #19488, #19534, #19537) — which is orthogonal to tiers (variants = same-model configurations, tiers = cross-model routing)
  • Every user install cloned 980-commit-stale source and compiled for 5-10 minutes
  • The unclear cost-optimization benefit of tiers never justified the monthly rebase maintenance burden

What Changes

Runtime Code (13 files)

  • Deleted: PAI-Install/engine/build-opencode.ts (245 LOC of fork build logic)
  • opencode.json: 16 agents stripped of model_tiers blocks → flat "model" per agent
  • PAI-Install/engine/{actions,steps-fresh,steps-migrate,steps-update,update}.ts: Fork clone/build replaced with curl -fsSL https://opencode.ai/install | bash
  • PAI-Install/cli/{install-wizard,quick-install}.ts: Dead build prompts removed, --skip-build flag deprecated (silently ignored with warning)
  • PAI-Install/engine/validate.ts: Error message updated for vanilla install path
  • PAI-Install/wrapper-template.sh: Rewritten from custom-build launcher to vanilla wrapper; --rebuild replaced with --install
  • .opencode/plugins/handlers/agent-execution-guard.ts: modelTier reading + advanced-tier check removed
  • .opencode/plugins/handlers/session-registry.ts: modelTier field removed from interface, extractor, registry entries, and tool output
  • .opencode/tools/switch-provider.ts: tiers? field removed from AgentConfig, model_tiers generation removed
  • .coderabbit.yaml: model_tier mapping removed from agent frontmatter validation

Profile YAMLs (5 files)

  • .opencode/profiles/{anthropic,openai,zen,zen-paid,local}.yaml — all tiers: sub-sections stripped

New Files (2)

  • PAI-Install/engine/migrate-legacy-config.ts — Automated converter for user opencode.json files with legacy model_tiers blocks. Preserves each agent's standard tier model as the new canonical model. Creates .pre-v3.0.bak backup. Handles both object ({model: "..."}) and legacy string tier formats.
  • docs/architecture/adr/ADR-019-vanilla-opencode-migration.md — Architectural decision record

Documentation (19 files)

Rewritten in parallel via 5 Writer subagents with a shared replacement rulebook for consistency:

  • Top-level (5): README.md, INSTALL.md, UPGRADE.md, KNOWN_LIMITATIONS.md, CHANGELOG.md
  • docs/ (5): MIGRATION.md, ADVANCED-SETUP.md, OPENCODE-FEATURES.md, PAI-ADAPTATIONS.md, PLATFORM-DIFFERENCES.md
  • docs/architecture/ (4): Configuration.md, ToolReference.md, Troubleshooting.md, AgentCapabilityMatrix.md
  • docs/architecture/adr/ (2): ADR-005 and ADR-012 gained > [!warning] PARTIAL SUPERSESSION notices at the top + inline historical notes preserving decision history
  • docs/epic/ (1): EPIC-v3.0-Synthesis-Architecture.md

Migration Path for End Users

After this ships in v3.0, existing v2.x users with model_tiers in their local opencode.json can migrate automatically:

bun run PAI-Install/engine/migrate-legacy-config.ts ~/.opencode/opencode.json

The script:

  • Creates a .pre-v3.0.bak backup before any changes
  • Preserves each agent's standard tier model as the new canonical model field
  • Falls back to top-level model, then quick, then advanced if no standard tier exists
  • Handles both object ({model: "..."}) and legacy string tier formats
  • Skips agents that are already flat (no model_tiers block)

Or manually, delete all model_tiers blocks from opencode.json, keep only the top-level "model" field per agent.

For Task tool callers, use agent selection instead of tier selection:

// BEFORE (v2.x)
Task({ subagent_type: "Engineer", model_tier: "quick", prompt: "..." })

// AFTER (v3.0+) — match the agent to the workload
Task({ subagent_type: "explore", prompt: "..." })     // cheap work
Task({ subagent_type: "Engineer", prompt: "..." })    // standard work
Task({ subagent_type: "Architect", prompt: "..." })   // heavy work

Verification

  • ✅ All edited TypeScript files bundle cleanly via bun build --target=bun --outdir=...
  • bash -n confirms wrapper-template.sh shell syntax
  • opencode.json parses as valid JSON with 0 model_tiers blocks
  • ✅ All 5 profile YAMLs have 0 tiers: sub-keys
  • grep across runtime code returns 0 model_tier / modelTier / Steffen025/opencode / feature/model-tiers / buildOpenCodeBinary matches (only legitimate historical ADR notes, deprecation callouts, CHANGELOG entries, and migration-script data references remain)
  • install.sh --help, install-wizard.ts --help, and quick-install.ts --help all boot clean
  • ✅ Migration script tested with a synthetic fixture containing mixed object/string tier formats — 3/4 agents migrated correctly, standard tier preserved, already-flat agents untouched

Stats

40 files changed, 1040 insertions(+), 1566 deletions(-)
Net: -526 LOC

Rejected Alternatives (see ADR-019)

  • Alt-A: Rebase the fork onto upstream/dev — Rejected. Would require monthly rebases against upstream's conflicting variants schema for a feature the maintainers chose not to merge.
  • Alt-B: Keep tiers via multiple agents per role (e.g., Engineer-Quick, Engineer-Advanced) — Rejected. Violates the single-source-of-truth principle; models would be spread across 3x agent definitions instead of centralized in opencode.json.
  • Alt-C: Submit model_tiers to upstream as a vanilla PR — Deferred. Not a prerequisite for resolving our tech debt; can be revisited later if the community wants the feature.

Post-Merge Action Required

After this PR merges, the Steffen025/opencode fork should be archived on GitHub:

gh repo archive Steffen025/opencode --yes

Full archival steps are documented in private working notes (not part of this PR).

Related

  • ADR-019 (new): docs/architecture/adr/ADR-019-vanilla-opencode-migration.md
  • Partial supersession: ADR-005 (dual-file config), ADR-012 (session registry)
  • Upstream orphaned commit: 95fd6ea7 by Mohammed Muzammil Anwar
  • Upstream's alternative: variants system (PRs #19488, #19534, #19537)

Follow-up Commit — CodeRabbit Review Feedback (Apr 2026)

A second commit on this branch (fix(wp-m1): address coderabbit review feedback) addresses 18 of the 22 findings raised by CodeRabbit's review:

Code bugs fixed:

  • switch-provider.ts — merge new model into existing per-agent config so non-model fields (permission, prompts, tool restrictions) are preserved instead of being overwritten
  • opencode.jsonGeminiResearcher, GrokResearcher, PerplexityResearcher default to anthropic/claude-sonnet-4-5 so fresh Anthropic-only installs don't fail on missing Google/xAI/Perplexity keys
  • install-wizard.ts--rebuild now accepted as a deprecated alias of --skip-build (was crashing with strict parseArgs)
  • validate.ts — added ~/.opencode/bin/opencode and /opt/homebrew/bin/opencode to the binary-detection list so vanilla installs are recognized
  • migrate-legacy-config.ts — unique backup filenames (.pre-v3.0.bak.1, .2, ...) so repeated runs never overwrite an earlier backup
  • quick-install.ts — dry-run mode now skips stepCreateBackup and stepMigrationDone (no filesystem writes, no shell rc edits, no bun install)
  • wrapper-template.sh — realpath-resolve ~/.opencode before trusting ~/.opencode/bin/opencode; when the symlink points at the PAI repo (--fix-symlink mode), fall through to ~/.local/bin/opencode and Homebrew candidates instead of a false-negative

Documentation bugs fixed:

  • CHANGELOG.md — json fence changed to jsonc so the // BEFORE/AFTER comments are valid
  • ADR-005 — removed duplicate "model_tiers removed" note
  • ADR-012 — removed "model tier" text from the session_results tool description
  • AgentCapabilityMatrix.md — stripped tier labels (advanced/standard/quick) from the Mermaid hierarchy
  • EPIC-v3.0-Synthesis-Architecture.md — added language identifier to bare code fence, merged extra table cell into preceding cell, added blank lines around markdown tables for markdownlint compliance, reframed "Production Agent Models" as "Example" with source-of-truth note
  • Troubleshooting.md — clarified that the migration script should be run against the actual opencode.json location (repo root for standard installs that symlink ~/.opencode)
  • INSTALL.md — removed incorrect claim that OpenCode is built with Bun.build; use proper Obsidian > [!NOTE] callouts with bun --version / bun upgrade hints
  • README.md — normalized agent IDs to lowercase runtime names (explore, general) and dropped hardcoded Model column; added a > [!NOTE] pointing at opencode.json and .opencode/profiles/*.yaml as source of truth
  • OPENCODE-FEATURES.md — labeled model column as "Example Model" with explicit source-of-truth note
  • UPGRADE.md — grep for both model_tiers AND model_tier legacy keys

Code cleanup:

  • steps-update.ts — removed unused _skipBinaryUpdate parameter and vestigial binaryUpdated return field (dead state after vanilla migration)
  • update.ts — clarified in-source comment that updateBinaryIfNeeded returning false is semantically correct (the binary was not updated by PAI; the version-advancement gate still fires when skills or core files change)

Findings intentionally skipped:

  • updateBinaryIfNeeded → return true: rejected. Returning true would make every update run incorrectly claim "binary updated" and advance the version marker even when nothing actually changed. The existing false return is semantically correct and was preserved with a clearer explanatory comment. (CodeRabbit raised this again in round 2 as a "duplicate comment" — see the updatesAttempted fix below for the proper resolution.)
  • Nitpicks (frontmatter additions to ADVANCED-SETUP.md, ASCII + Mermaid diagrams for ADR-019): deferred — ADR-019 is already comprehensive, and frontmatter conventions for individual docs are out of scope for this migration PR.
  • steps-fresh.ts repository step adding actual which opencode probe: deferred — introduces new runtime behavior beyond the migration scope.
  • actions.ts PATH/alias wiring for ~/.opencode/bin: deferred — that's a new installer feature, not a migration fix.

Follow-up Commit — CodeRabbit Review Round 2 + PR #103 Integration (commit 55adca0)

A third commit on this branch addresses CodeRabbit's second review pass AND integrates Kevin (@ktneely)'s model-roster corrections from PR #103 (see "Community Credit" section above).

CodeRabbit round 2 fixes:

  • quick-install.ts + install-wizard.ts — removed the 3rd false argument from stepApplyUpdate() calls. The previous fix commit simplified the function signature to (state, onProgress) but missed these two call sites, which CodeRabbit correctly flagged as a TypeScript type error.
  • switch-provider.ts — after writing the profile's agents into agentBlock, we now iterate existingAgentBlock and copy any user-defined agents that are not in the profile. Previously, a user who added a custom agent to opencode.json would have lost it every time they ran switch-provider. Extracted the legacy model_tiers stripping into a stripLegacy() helper shared by both passes.
  • steps-update.ts — removed the unused binaryUpdated boolean parameter from UPDATE_UI_TEXT.complete.message. Grep confirmed zero external callers, so this was dead state. Simplified signature to (version: string) => string.
  • update.ts — added a updatesAttempted flag to the version-advancement gate in updateV3. CodeRabbit correctly identified a pre-existing bug (unrelated to the vanilla migration): updateSkills() and updateCoreFiles() are placeholder functions that always return false, so without this flag the version marker would NEVER advance when a user runs pai update. The new gate is skillsUpdated || coreUpdated || binaryUpdated || updatesAttempted where updatesAttempted = changes.length > 0 from detectChanges(). When the placeholder functions are replaced with real implementations later, their return values still drive the decision — this is a superset.

PR #103 model updates integrated (details in Community Credit section above):

  • .opencode/profiles/zen.yaml — free tier model roster rewritten against the April 2026 Zen pricing page
  • .opencode/profiles/zen-paid.yaml — paid tier migrated from glm-4.7/minimax-m2.1 to glm-5/glm-5.1/minimax-m2.5, CodexResearcher now uses the new qwen3-coder-480b
  • .opencode/profiles/local.yaml — Kevin's Ollama model updates applied wholesale (qwen2.5-coder family → qwen3.5 family, Writer uses mistral-small3.2:24b)
  • .opencode/plugins/lib/model-config.tszen preset default changed from retired grok-code to qwen3.6-plus-free; openai preset updated from retired gpt-4o family to gpt-5.1 family; anthropic reviewer upgraded to claude-opus-4-6

Summary by CodeRabbit

  • Breaking Changes

    • Removed model-tier routing (quick/standard/advanced); each agent now has a single model in opencode.json.
    • model_tier runtime parameter removed—use agent selection instead.
  • Installer / CLI

    • Installer now uses the official vanilla OpenCode installer; legacy build flags are deprecated/ignored.
  • Agent-Based Routing

    • Use lightweight agents (explore/Intern) for simple tasks and heavier agents (Architect/Engineer) for complex work.
  • Configuration Migration

    • Added an automated migration tool to convert legacy tiered configs to single-model format.
  • Documentation

    • Docs, changelog, and guides updated to reflect agent-based routing and vanilla install.

BREAKING CHANGE: Removes the model_tiers feature and the dependency on
the Steffen025/opencode fork. PAI-OpenCode now runs on vanilla OpenCode
from opencode.ai. Each agent has exactly one model configured in
opencode.json (no more quick/standard/advanced tier sub-blocks). The
model_tier parameter is removed from Task tool invocations.

Context:
The feature/model-tiers fork was 980 commits behind upstream with only
2 custom commits, both duplicate cherry-picks of an orphaned upstream
PR (95fd6ea7 by Mohammed Muzammil Anwar) that was never merged to
upstream/dev. Upstream built a different 'variants' system instead,
which is orthogonal to model_tiers (variants = same-model configs,
tiers = cross-model routing). Maintaining the fork delivered unclear
value at enormous cost — every user install cloned 980-commit-stale
source and compiled for 5-10 minutes before use.

Runtime changes:
- Deleted PAI-Install/engine/build-opencode.ts (245 LOC fork build)
- Stripped model_tiers blocks from opencode.json (16 agents → flat)
- Stripped tiers: sections from all 5 profile YAML files
- Replaced fork-clone with curl https://opencode.ai/install in
  PAI-Install/engine/actions.ts, steps-fresh.ts, steps-migrate.ts,
  steps-update.ts, wrapper-template.sh
- Removed modelTier reading from agent-execution-guard.ts and
  session-registry.ts plugin handlers
- Removed tiers? field from AgentConfig in switch-provider.ts
- Removed model_tier mapping requirement from .coderabbit.yaml
- Deprecated --skip-build CLI flag (silently ignored with warning)

New tooling:
- PAI-Install/engine/migrate-legacy-config.ts — automated converter
  for user opencode.json files with legacy model_tiers blocks.
  Preserves each agent's standard-tier model as the new canonical
  model. Creates .pre-v3.0.bak backup before any changes. Handles
  both object and legacy string tier formats.

Documentation:
- New ADR-019 (docs/architecture/adr/ADR-019-vanilla-opencode-migration.md)
  documents the decision, rejected alternatives, and implementation plan
- ADR-005 and ADR-012 gained PARTIAL SUPERSESSION notices at the top
  plus inline historical notes preserving their decision history
- CHANGELOG.md [3.0.0] - Unreleased entry gained a WP-M1 subsection
  plus full Breaking/Removed/Changed/Deprecated detail
- 17 documentation files rewritten via 5 parallel Writer subagents
  with a shared REPLACEMENT-RULEBOOK for consistency:
  - Top-level: README, INSTALL, UPGRADE, KNOWN_LIMITATIONS
  - docs/: MIGRATION, ADVANCED-SETUP, OPENCODE-FEATURES,
    PAI-ADAPTATIONS, PLATFORM-DIFFERENCES
  - docs/architecture/: Configuration, ToolReference,
    Troubleshooting, AgentCapabilityMatrix
  - docs/architecture/adr/: ADR-005, ADR-012 (supersession notes)
  - docs/epic/: EPIC-v3.0-Synthesis-Architecture

Verification:
- All edited TypeScript files bundle cleanly via bun build --target=bun
- Shell script syntax verified via bash -n
- opencode.json parses as valid JSON with zero model_tiers blocks
- All 5 profile YAMLs have zero tiers: sub-keys (grep verified)
- install.sh, install-wizard.ts, and quick-install.ts all boot clean
  and show updated help text for --skip-build deprecation
- Migration script tested with synthetic fixture containing mixed
  object/string tier formats — 3/4 agents migrated correctly,
  standard-tier preserved, flat-only agents untouched

Target: ships in PAI-OpenCode v3.0 (currently unreleased).
See CHANGELOG.md [3.0.0] - Unreleased and ADR-019 for full details.

After this PR merges, the Steffen025/opencode fork should be archived
on GitHub. See private handoff notes for the manual steps.

Refs: ADR-019, WP-M1
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

Warning

Rate limit exceeded

@Steffen025 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 3 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 18 minutes and 3 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 37fd58ac-0c58-4015-9148-75288fa1545e

📥 Commits

Reviewing files that changed from the base of the PR and between 55adca0 and e6ccbc0.

📒 Files selected for processing (3)
  • .opencode/plugins/lib/model-config.ts
  • .opencode/tools/switch-provider.ts
  • PAI-Install/cli/quick-install.ts
📝 Walkthrough

Walkthrough

This PR removes runtime model_tiers/model_tier support, collapses per-agent configuration to a single model, replaces the forked source-build installer with the vanilla opencode installer, adds a migration script to convert legacy configs, and updates installer flows, types, and docs to reflect agent-based routing. (34 words)

Changes

Cohort / File(s) Summary
Handlers & Plugin Types
​.opencode/plugins/handlers/agent-execution-guard.ts, ​.opencode/plugins/handlers/session-registry.ts
Removed modelTier extraction/usage and associated quick-tier check; updated types/returns (SubagentEntry, extractTaskInfo) and logs to omit modelTier.
Provider/Agent Config Tooling
​.opencode/tools/switch-provider.ts, opencode.json, ​.opencode/plugins/lib/model-config.ts
Removed tiers/model_tiers fields and logic; generator/apply logic now emits/retains a single model per agent and preserves other agent fields.
Provider Profiles
​.opencode/profiles/.../{anthropic,local,openai,zen-paid,zen}.yaml
Deleted per-agent tiers blocks across profiles and updated model selections/comments so each agent specifies one model.
Installer Core & Build Removal
PAI-Install/engine/build-opencode.ts, PAI-Install/engine/actions.ts, PAI-Install/engine/steps-fresh.ts, PAI-Install/engine/steps-migrate.ts, PAI-Install/engine/steps-update.ts, PAI-Install/engine/types.ts, PAI-Install/engine/update.ts, PAI-Install/engine/validate.ts
Removed source-build engine and exported build/status APIs; replaced build-from-fork behavior with installVanillaOpenCode() calling the official installer; stripped modelTier/models from InstallState and removed binary build/update control flow and failure paths.
Installer CLI & Wrapper
PAI-Install/cli/install-wizard.ts, PAI-Install/cli/quick-install.ts, PAI-Install/wrapper-template.sh
Removed interactive build/update branching, deprecated --skip-build/--rebuild as no-ops, added --install wrapper command, and changed binary discovery to check vanilla install locations and PATH.
Migration Tooling
PAI-Install/engine/migrate-legacy-config.ts
Added migration script that backs up opencode.json and converts model_tiers → single model per agent using priority rules; returns structured MigrationResult and CLI-friendly report.
Installer UX / CLI Flow
PAI-Install/cli/*, PAI-Install/engine/* (various)
Adjusted CLI/wizard/update/migration flows to eliminate build-fallbacks, simplify progress, and change messaging to reflect vanilla installer management.
Docs, Changelog & Guides
CHANGELOG.md, INSTALL.md, KNOWN_LIMITATIONS.md, README.md, UPGRADE.md, docs/**, docs/architecture/**, docs/architecture/adr/**, docs/epic/**
Removed tier-based examples/guides and fork references; introduced agent-based routing language, migration guidance, ADR-019 documenting the vanilla migration, and extensive documentation updates.
Profiles & Default Models
​.opencode/profiles/*, opencode.json, ​.opencode/plugins/lib/model-config.ts
Updated default/validation models and many per-agent model selections across provider profiles to align with single-model-per-agent approach.
Meta / CI / Review Config
.coderabbit.yaml
Adjusted frontmatter verification requirements to expect (name, description, model) rather than a model_tier mapping.

Sequence Diagram(s)

sequenceDiagram
  participant User as "User / CLI"
  participant Installer as "PAI-Install CLI"
  participant Migrator as "migrate-legacy-config.ts"
  participant Config as "opencode.json"
  participant Vanilla as "opencode.ai installer"

  rect rgba(173,216,230,0.5)
  User->>Installer: run migration/install command
  Installer->>Migrator: invoke migrateLegacyConfig(path)
  Migrator->>Config: read & backup opencode.json
  Migrator->>Config: convert model_tiers -> model, write file
  Migrator-->>Installer: return MigrationResult
  end

  rect rgba(144,238,144,0.5)
  Installer->>Vanilla: call curl https://opencode.ai/install | bash
  Vanilla-->>Installer: installer completes (binary installed)
  Installer-->>User: report migration + install complete
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through tiers now gone,

One model per role at dawn,
Vanilla curl sings bright,
Migration hops to make it right,
A simpler burrow, code rewritten.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main breaking change: removal of model_tiers and migration to vanilla OpenCode (v3.0).
Linked Issues check ✅ Passed All coding requirements from #105 (remove fork dependency, use vanilla OpenCode) and #31 (v3.0 migration with single-model-per-agent routing) are met: fork build logic deleted, vanilla installer implemented, model_tiers removed from code/config, migration script added.
Out of Scope Changes check ✅ Passed All changes are directly scoped to removing model_tiers and migrating to vanilla OpenCode. Documentation updates, profile YAML consolidation, installer refactoring, and new migration tooling are all aligned with v3.0 objectives.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wp-m1-vanilla-opencode-migration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
PAI-Install/engine/validate.ts (1)

85-96: ⚠️ Potential issue | 🟠 Major

Add ~/.opencode/bin/opencode to validation locations.

The vanilla install flow can place the binary in ~/.opencode/bin/opencode, but this validator currently won’t count that location, causing a false critical failure.

✅ Minimal fix
   const opencodeLocations = [
+    join(homedir(), ".opencode", "bin", "opencode"),
     join(homedir(), ".local", "bin", "opencode"),
     "/usr/local/bin/opencode",
     join(paiDir, "tools", "opencode"),
   ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PAI-Install/engine/validate.ts` around lines 85 - 96, The validator misses
the vanilla install path ~/.opencode/bin/opencode causing false failures; update
the opencodeLocations array to include join(homedir(), ".opencode", "bin",
"opencode") so opencodeInstalled (the some check) can detect that location, and
the checks entry (name: "OpenCode binary") will then report the correct found
path via opencodeLocations.find(...).
PAI-Install/cli/quick-install.ts (1)

262-300: ⚠️ Potential issue | 🟠 Major

Honor --dry-run end-to-end.

Line 263 promises "No changes will be made", but this path still creates a real backup and always calls stepMigrationDone(), which writes the version marker, installs the wrapper, edits shell rc files, and runs bun install. Dry-run needs to skip both the backup/finalization steps or thread a dryRun flag into them.

Proposed guard
-	// Step 2: Backup
-	onProgress(10, "Creating backup...");
-	const backupResult = await stepCreateBackup(
-		state,
-		values["backup-dir"] || "",
-		onProgress
-	);
-
-	if (!backupResult.success) {
-		console.error("❌ Backup failed:", backupResult.error);
-		process.exit(1);
-	}
-
-	console.log("📦 Backup created:", backupResult.backupPath);
+	let backupResult: { success: boolean; backupPath: string; error?: string } | null = null;
+	if (!values["dry-run"]) {
+		onProgress(10, "Creating backup...");
+		backupResult = await stepCreateBackup(
+			state,
+			values["backup-dir"] || "",
+			onProgress
+		);
+
+		if (!backupResult.success) {
+			console.error("❌ Backup failed:", backupResult.error);
+			process.exit(1);
+		}
+
+		console.log("📦 Backup created:", backupResult.backupPath);
+	}-	// Step 5: Done
-	await stepMigrationDone(state, migrationResult, onProgress);
+	// Step 5: Done
+	if (!values["dry-run"]) {
+		await stepMigrationDone(state, migrationResult, onProgress);
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PAI-Install/cli/quick-install.ts` around lines 262 - 300, The dry-run path
currently still performs real backup and finalization; change the flow so when
values["dry-run"] is true you do not call stepCreateBackup and do not call
stepMigrationDone (or alternatively pass a dryRun flag into both functions and
have them no-op side-effectful behavior). Specifically, wrap the call to
stepCreateBackup (and its logging/exit-on-failure) in a guard checking
values["dry-run"] and skip it in dry-run mode, and either skip calling
stepMigrationDone or extend stepMigrationDone to accept a dryRun boolean and
early-return without writing markers/installing wrappers/editing shell rc or
running bun install when dryRun is true; keep stepMigrate usage as-is since it
already accepts the dry-run flag.
🧹 Nitpick comments (5)
docs/architecture/adr/ADR-005-configuration-dual-file-approach.md (1)

112-121: Consolidate duplicated migration note.

The same “model_tiers removed in April 2026” note appears twice in close proximity. Keeping one instance is clearer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture/adr/ADR-005-configuration-dual-file-approach.md` around
lines 112 - 121, Duplicate migration note "model_tiers removed in the April 2026
vanilla migration — see ADR-019." appears twice; remove one occurrence so the
document only includes a single instance of that note near the "Key Changes"
section. Locate the two identical lines containing "model_tiers removed in the
April 2026 vanilla migration — see ADR-019." and delete the redundant one,
leaving the other untouched to preserve context.
UPGRADE.md (1)

80-81: Tighten the legacy-key verification command.

Consider checking both legacy keys (model_tiers and model_tier) in one command to avoid false confidence.

Suggested doc tweak
-# Confirm no legacy model_tiers remain (should return nothing)
-cat ~/.opencode/opencode.json | grep model_tiers
+# Confirm no legacy model_tiers/model_tier remain (should return nothing)
+grep -E '"model_tiers"|"model_tier"' ~/.opencode/opencode.json || true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@UPGRADE.md` around lines 80 - 81, Update the legacy-key verification so it
checks for both legacy keys "model_tiers" and "model_tier" in one command to
avoid false confidence; modify the instruction that currently greps for
model_tiers to search for either key (e.g., use grep -E or an equivalent
jq-based check) so the presence of either "model_tiers" or "model_tier" will be
detected.
docs/ADVANCED-SETUP.md (1)

1-3: Add frontmatter to this setup guide.

This is one of the main setup docs under docs/, but it still starts without the metadata block used by the rest of the Obsidian docs.

As per coding guidelines "Frontmatter for important documents".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/ADVANCED-SETUP.md` around lines 1 - 3, This doc lacks the required
Obsidian YAML frontmatter; add a top-of-file frontmatter block (--- YAML ---)
containing at minimum keys used across docs such as title, description (or
summary), tags, and slug (and date if your docs template expects it) so the page
conforms to "Frontmatter for important documents" conventions; update the page
header in ADVANCED-SETUP.md to include that frontmatter before the "# Advanced
PAI-OpenCode Setup" heading.
PAI-Install/engine/steps-update.ts (1)

53-73: Drop the binary-update placeholders before shipping v3.

_skipBinaryUpdate is now ignored and binaryUpdated is permanently false, so this function still advertises behavior the installer no longer supports. Since v3 is already a breaking release, returning plain UpdateResult here would let callers remove the stale rebuild/binary branches instead of carrying dead state forward.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PAI-Install/engine/steps-update.ts` around lines 53 - 73, The function
stepApplyUpdate currently accepts the unused _skipBinaryUpdate parameter and
always returns a binaryUpdated: false field, which is dead state; remove the
unused parameter and change the signature to return Promise<UpdateResult> (not
UpdateResult & { binaryUpdated: boolean }), then return the updateResult
directly. Update the call to updateV3 by removing or not setting the
skipBinaryUpdate flag (or call with its default) so there is no placeholder for
binary updates, and remove any references to binaryUpdated and _skipBinaryUpdate
in stepApplyUpdate to eliminate the stale rebuild/binary branches; keep the
onProgress mapping logic and the call to updateV3 as-is otherwise.
docs/architecture/adr/ADR-019-vanilla-opencode-migration.md (1)

12-20: Add a quick visual summary to this ADR.

A tiny before/after ASCII overview plus a collapsed Mermaid migration flow would make the fork-to-vanilla decision much easier to scan than prose alone.

As per coding guidelines, docs/**: Documentation — Markdown with Obsidian conventions. Verify: ASCII diagrams for quick overview; Mermaid diagrams in collapsible sections for detail.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture/adr/ADR-019-vanilla-opencode-migration.md` around lines 12
- 20, The ADR lacks a concise visual summary: add a small ASCII "Before / After"
diagram showing the forked Steffen025/opencode (feature/model-tiers) state vs
the vanilla opencode.ai state and then include a collapsible Mermaid flowchart
that outlines the migration steps (fork -> decision -> removal of model_tiers ->
single-model per agent -> release v3.0). Update ADR-019 content to place the
ASCII overview directly after the title/summary and embed the Mermaid diagram
inside a collapsible section (e.g., HTML <details>/<summary> or Obsidian
collapse syntax) labeled "Migration flow", keeping Obsidian Markdown conventions
for docs/**.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.opencode/tools/switch-provider.ts:
- Around line 228-234: The current loop replaces opencodeJson.agent entries with
only { model } and discards other per-agent fields; instead, merge the new model
into any existing per-agent config so non-model fields are preserved: for each
[agentName, agentConfig] in finalAgentModels, lookup
opencodeJson.agent[agentName] (if present) and create agentBlock[agentName] by
shallow-copying that existing object and then setting/overwriting its model
property to agentConfig.model; finally assign opencodeJson.agent = agentBlock so
only the model is updated while other agent-specific keys remain intact.

In `@CHANGELOG.md`:
- Around line 28-43: The code block showing the "Engineer" config object
contains JavaScript-style comments and is labeled ```json, which makes it
invalid for copy-paste; either change the fence language to ```jsonc to allow //
comments or remove the inline // BEFORE/AFTER comments and place those labels
outside the fenced block so the "Engineer" JSON object remains valid; update the
fenced block around the snippet that includes the "Engineer" object and its
"model" and "model_tiers" examples accordingly.

In `@docs/architecture/adr/ADR-012-session-registry-custom-tool.md`:
- Around line 352-353: The doc still mentions "model tier" in the description of
session_results; update the phrasing to remove any reference to the deprecated
model_tiers field and instead return accurate current fields (e.g., "agent type,
description, model information, status, and resume instructions" or similar),
and apply the same change to the other occurrence noted (the similar string at
the later occurrence). Ensure you update the text strings that build the
session_results return description so they no longer reference "model tier" or
"model_tiers" (search for "session_results" and the concatenated string
fragments shown).

In `@docs/architecture/AgentCapabilityMatrix.md`:
- Around line 18-20: The Mermaid hierarchy still shows removed runtime tier
names ("advanced", "standard", "quick") which contradicts the prose stating each
agent has exactly one configured model in opencode.json; update the Mermaid
diagram and any related labels to remove tier terminology and instead reflect
the single-model-per-agent design (e.g., label nodes with "agent" or the actual
agent type keys from opencode.json and annotate that the model comes from the
agent key), and ensure any captions or legend text referencing tiers are removed
or rewritten to state "model configured in opencode.json (no runtime tier
overrides)".

In `@docs/architecture/Troubleshooting.md`:
- Around line 161-162: Update the Troubleshooting note to instruct running the
migration script against the repository root opencode.json (not just
~/.opencode/opencode.json): change the example command reference to run bun run
PAI-Install/engine/migrate-legacy-config.ts ./opencode.json (or mention
accepting an explicit repo path) and clarify that standard installs symlink only
~/.opencode and keep opencode.json at the project root so users must point
migrate-legacy-config.ts at the repo root file.

In `@docs/epic/EPIC-v3.0-Synthesis-Architecture.md`:
- Line 369: The markdown table starting with the header line "| Agent |
Configured Model | Use Case |" needs an empty blank line inserted immediately
before and immediately after the table block to satisfy markdownlint rules; edit
the EPIC-v3.0-Synthesis-Architecture.md content and add one blank line above
that table header and one blank line below the table rows so the table is
separated from surrounding text.
- Line 62: Update the empty fenced code block (the triple backticks) in
EPIC-v3.0-Synthesis-Architecture.md by adding a language identifier (e.g.,
change ``` to ```text) so the markdown linter recognizes the block; locate the
solitary ``` block and annotate it with "text" as shown in the review comment.
- Line 108: The table row contains one extra cell (the line starting with
"**Agent-Based Routing** | ✅ **IMPLEMENTED** via vanilla opencode.json | 🚀
**READY** | Each agent has one model; match agent to task complexity |
~~REMOVED: Dynamic Model Tiers (April 2026 vanilla migration)~~") causing a
column count mismatch; fix it by removing or merging the extra cell so the pipe
count matches the table header (e.g., remove the trailing "~~REMOVED: Dynamic
Model Tiers...~~" cell or combine that note into the preceding cell), and ensure
the number of '|' separators in the row equals the header's column count.

In `@docs/OPENCODE-FEATURES.md`:
- Around line 23-35: The Agent-Based Routing docs currently hardcode model names
in the table for agents (Architect, Engineer, explore, Intern, DeepResearcher,
Writer, Pentester); update the section to stop presenting preset-specific
examples as "Configured Model" and instead indicate that models are resolved
from opencode.json (refer to opencode.json and
docs/architecture/ToolReference.md). Replace the hardcoded model column with
either a placeholder like "Resolved from opencode.json" or an example note that
model names are illustrative and must be resolved at runtime, and adjust the
Agent-Based Routing paragraph to explicitly reference opencode.json as the
source of truth.

In `@INSTALL.md`:
- Around line 185-190: Replace the plain blockquote after the install command
with an Obsidian callout using > [!NOTE] and keep the same content but phrase it
to say "Bun 1.3.9+ is required only for PAI-OpenCode's tooling" and include the
`bun --version` / `bun upgrade` hints; then update the earlier prerequisite note
that currently claims OpenCode is compiled locally with `Bun.build` to the
synced wording that Bun is not required to compile OpenCode itself but is only
needed for PAI-related tooling (remove or correct the `Bun.build` assertion).
Ensure both places use the exact `> [!NOTE]` callout syntax and mention the `bun
--version` check.

In `@opencode.json`:
- Around line 48-55: The defaults pinning agents "GeminiResearcher",
"GrokResearcher", and "PerplexityResearcher" to google/xai/perplexity models
will break fresh installs that only provision Anthropic/OpenAI keys; update
opencode.json so these agent entries do not require extra provider credentials
by either (a) replacing the model values with provider-neutral or
pre-provisioned defaults (e.g., OpenAI/Anthropic equivalents used by the
installer), (b) setting the model field to null/empty and disabling the agent by
default, or (c) adding a simple provider fallback mechanism; make the change for
"GeminiResearcher", "GrokResearcher", and "PerplexityResearcher" so a stock
Anthropic/OpenAI install won’t error on first use.

In `@PAI-Install/cli/install-wizard.ts`:
- Around line 104-105: The help output and argument parsing currently only
mention and accept --skip-build, but callers using the legacy --rebuild now fail
because parseArgs is strict; update the help text (the print lines that list
flags) to include a deprecation entry for --rebuild and modify the argument
parsing logic (the parseArgs handling for build-related flags) to accept
"--rebuild" as an alias for "--skip-build" and route it to the same
deprecation/migration handling path. Also apply the same change to the other
help/handling block referenced around the 349-357 region so both the printed
usage and parseArgs recognize and warn about --rebuild instead of erroring.

In `@PAI-Install/engine/actions.ts`:
- Around line 130-135: The installer now includes ~/.opencode/bin in the
candidates array in actions.ts but the shell wiring/alias logic must be updated
to prefer and expose that path; update the code that performs command wiring
(the logic that adds opencode to PATH / creates aliases or symlinks) to first
check candidates[0] (the ~/.opencode/bin path) and if it exists ensure it is
exported into the user shell (prepend export PATH="$HOME/.opencode/bin:$PATH" to
the appropriate profile) or create a symlink/shim in a PATH directory (e.g.,
~/.local/bin) pointing to ~/.opencode/bin/opencode so the opencode command
resolves consistently. Ensure the wiring step runs after resolving the selected
candidate and uses the resolved path from the candidates array.

In `@PAI-Install/engine/migrate-legacy-config.ts`:
- Around line 140-145: The backup creation currently always writes to
result.backupPath (the fixed ".pre-v3.0.bak"), which can overwrite the original
pre-v3 file; modify the backup logic in migrate-legacy-config (around where
copyFileSync(configPath, result.backupPath) is called) to detect an existing
backup (use fs.existsSync on result.backupPath) and if present generate a new
unique backup filename (e.g., append a timestamp or incrementing suffix using
path.parse to build "<name>.pre-v3.0.bak.<ts>" or ".bak.1") and update
result.backupPath to that new path before calling copyFileSync; keep the
existing error handling but ensure the unique path is used so repeated runs do
not overwrite the original backup.

In `@PAI-Install/engine/steps-fresh.ts`:
- Around line 571-579: The current "repository" step just logs guidance but
never verifies opencode exists; update the step where emit is called for step
"repository" to actually check for the opencode binary on PATH (e.g., attempt to
resolve it via a spawn/which call or fs access to child_process.execSync('which
opencode')/command runner), and if missing either invoke the vanilla installer
(run the curl -fsSL https://opencode.ai/install | bash flow in a controlled
child_process) or immediately emit an error message via emit({event: "error",
step: "repository", ...}) and stop the process (throw or process.exit(1));
ensure you keep the existing emit calls but only emit the success/completion
message after the presence/installation check passes.

In `@PAI-Install/engine/update.ts`:
- Around line 187-190: The function updateBinaryIfNeeded currently always
returns false for the "Vanilla OpenCode binary" branch which prevents the later
version-advancement logic from running; change that branch so after calling
onProgress?.("OpenCode binary managed by vanilla installer — skipping") it
returns true (or otherwise signals success) so the caller's subsequent logic
that advances .version (the code at the later line that updates the version)
will execute; updateBinaryIfNeeded and its use sites should treat the
external-installer skip as a successful update condition.

In `@PAI-Install/wrapper-template.sh`:
- Around line 28-45: The detection logic for OPENCODE_BIN fails when ~/.opencode
is a symlink to the repo (created by --fix-symlink) because
~/.opencode/bin/opencode no longer contains the binary; update the OPENCODE_BIN
resolution to first resolve the real target of "${HOME}/.opencode" (use realpath
or readlink -f) and check both the resolved path's bin/opencode and the original
installer location (~/.opencode/bin/opencode) plus the existing fallback
candidates, and if the resolved path points inside the repository prefer
checking the installer prefix (e.g., "${HOME}/.local/bin/opencode" or PATH)
instead; ensure the same symlink-aware check is applied in the other detection
blocks referenced (the blocks that set OPENCODE_BIN around lines 61-71 and
79-112) so --fix-symlink no longer causes a false-negative.

In `@README.md`:
- Around line 200-221: The README table presents profile-specific model
assignments as canonical and uses non-runtime agent IDs (e.g.,
"Explore"/"General" vs runtime "explore"/"general"); update the table to either
(A) clearly label it as "Sample profile — example model assignments (see
opencode.json for runtime mappings)" and keep the Model column as example
suggestions, or (B) remove the Model column and make the table role-only (Agent
/ Best For) so it describes capability without claiming canonical model
mappings; also normalize the Agent IDs to the runtime identifiers used in
opencode.json (use "explore", "general", etc.) or add a short note pointing
readers to opencode.json for authoritative, profile-specific mappings.

---

Outside diff comments:
In `@PAI-Install/cli/quick-install.ts`:
- Around line 262-300: The dry-run path currently still performs real backup and
finalization; change the flow so when values["dry-run"] is true you do not call
stepCreateBackup and do not call stepMigrationDone (or alternatively pass a
dryRun flag into both functions and have them no-op side-effectful behavior).
Specifically, wrap the call to stepCreateBackup (and its
logging/exit-on-failure) in a guard checking values["dry-run"] and skip it in
dry-run mode, and either skip calling stepMigrationDone or extend
stepMigrationDone to accept a dryRun boolean and early-return without writing
markers/installing wrappers/editing shell rc or running bun install when dryRun
is true; keep stepMigrate usage as-is since it already accepts the dry-run flag.

In `@PAI-Install/engine/validate.ts`:
- Around line 85-96: The validator misses the vanilla install path
~/.opencode/bin/opencode causing false failures; update the opencodeLocations
array to include join(homedir(), ".opencode", "bin", "opencode") so
opencodeInstalled (the some check) can detect that location, and the checks
entry (name: "OpenCode binary") will then report the correct found path via
opencodeLocations.find(...).

---

Nitpick comments:
In `@docs/ADVANCED-SETUP.md`:
- Around line 1-3: This doc lacks the required Obsidian YAML frontmatter; add a
top-of-file frontmatter block (--- YAML ---) containing at minimum keys used
across docs such as title, description (or summary), tags, and slug (and date if
your docs template expects it) so the page conforms to "Frontmatter for
important documents" conventions; update the page header in ADVANCED-SETUP.md to
include that frontmatter before the "# Advanced PAI-OpenCode Setup" heading.

In `@docs/architecture/adr/ADR-005-configuration-dual-file-approach.md`:
- Around line 112-121: Duplicate migration note "model_tiers removed in the
April 2026 vanilla migration — see ADR-019." appears twice; remove one
occurrence so the document only includes a single instance of that note near the
"Key Changes" section. Locate the two identical lines containing "model_tiers
removed in the April 2026 vanilla migration — see ADR-019." and delete the
redundant one, leaving the other untouched to preserve context.

In `@docs/architecture/adr/ADR-019-vanilla-opencode-migration.md`:
- Around line 12-20: The ADR lacks a concise visual summary: add a small ASCII
"Before / After" diagram showing the forked Steffen025/opencode
(feature/model-tiers) state vs the vanilla opencode.ai state and then include a
collapsible Mermaid flowchart that outlines the migration steps (fork ->
decision -> removal of model_tiers -> single-model per agent -> release v3.0).
Update ADR-019 content to place the ASCII overview directly after the
title/summary and embed the Mermaid diagram inside a collapsible section (e.g.,
HTML <details>/<summary> or Obsidian collapse syntax) labeled "Migration flow",
keeping Obsidian Markdown conventions for docs/**.

In `@PAI-Install/engine/steps-update.ts`:
- Around line 53-73: The function stepApplyUpdate currently accepts the unused
_skipBinaryUpdate parameter and always returns a binaryUpdated: false field,
which is dead state; remove the unused parameter and change the signature to
return Promise<UpdateResult> (not UpdateResult & { binaryUpdated: boolean }),
then return the updateResult directly. Update the call to updateV3 by removing
or not setting the skipBinaryUpdate flag (or call with its default) so there is
no placeholder for binary updates, and remove any references to binaryUpdated
and _skipBinaryUpdate in stepApplyUpdate to eliminate the stale rebuild/binary
branches; keep the onProgress mapping logic and the call to updateV3 as-is
otherwise.

In `@UPGRADE.md`:
- Around line 80-81: Update the legacy-key verification so it checks for both
legacy keys "model_tiers" and "model_tier" in one command to avoid false
confidence; modify the instruction that currently greps for model_tiers to
search for either key (e.g., use grep -E or an equivalent jq-based check) so the
presence of either "model_tiers" or "model_tier" will be detected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c9ded84a-d208-494d-ac4e-880a9e5b93c6

📥 Commits

Reviewing files that changed from the base of the PR and between 372a751 and f814f94.

📒 Files selected for processing (40)
  • .coderabbit.yaml
  • .opencode/plugins/handlers/agent-execution-guard.ts
  • .opencode/plugins/handlers/session-registry.ts
  • .opencode/profiles/anthropic.yaml
  • .opencode/profiles/local.yaml
  • .opencode/profiles/openai.yaml
  • .opencode/profiles/zen-paid.yaml
  • .opencode/profiles/zen.yaml
  • .opencode/tools/switch-provider.ts
  • CHANGELOG.md
  • INSTALL.md
  • KNOWN_LIMITATIONS.md
  • PAI-Install/cli/install-wizard.ts
  • PAI-Install/cli/quick-install.ts
  • PAI-Install/engine/actions.ts
  • PAI-Install/engine/build-opencode.ts
  • PAI-Install/engine/migrate-legacy-config.ts
  • PAI-Install/engine/steps-fresh.ts
  • PAI-Install/engine/steps-migrate.ts
  • PAI-Install/engine/steps-update.ts
  • PAI-Install/engine/types.ts
  • PAI-Install/engine/update.ts
  • PAI-Install/engine/validate.ts
  • PAI-Install/wrapper-template.sh
  • README.md
  • UPGRADE.md
  • docs/ADVANCED-SETUP.md
  • docs/MIGRATION.md
  • docs/OPENCODE-FEATURES.md
  • docs/PAI-ADAPTATIONS.md
  • docs/PLATFORM-DIFFERENCES.md
  • docs/architecture/AgentCapabilityMatrix.md
  • docs/architecture/Configuration.md
  • docs/architecture/ToolReference.md
  • docs/architecture/Troubleshooting.md
  • docs/architecture/adr/ADR-005-configuration-dual-file-approach.md
  • docs/architecture/adr/ADR-012-session-registry-custom-tool.md
  • docs/architecture/adr/ADR-019-vanilla-opencode-migration.md
  • docs/epic/EPIC-v3.0-Synthesis-Architecture.md
  • opencode.json
💤 Files with no reviewable changes (4)
  • PAI-Install/engine/types.ts
  • .opencode/profiles/zen-paid.yaml
  • PAI-Install/engine/build-opencode.ts
  • .opencode/profiles/zen.yaml

Comment on lines +571 to 579
// Step 5: OpenCode Install Check
//
// Vanilla OpenCode is installed via the official opencode.ai installer
// (or by Homebrew / package manager). PAI does not build OpenCode from source.
await emit({ event: "step_start", step: "repository" });
const buildResult = await buildOpenCodeBinary({
onProgress: async (message, percent) => {
emit({ event: "progress", step: "repository", percent, detail: message });
},
skipIfExists: true, // Skip rebuild if binary already exists — prevents 20-30min hang on re-installs
await emit({
event: "message",
content: "OpenCode install is managed by the vanilla opencode.ai installer. If opencode is not on your PATH, run: curl -fsSL https://opencode.ai/install | bash",
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This “install check” never actually checks.

Lines 576-579 always print guidance and mark the step complete, even when opencode is missing. That means a fresh install can finish successfully while the generated wrapper still has nothing to launch. Either invoke the vanilla installer here or fail fast when the binary is absent.

🔎 Minimal check-before-continue
   await emit({ event: "step_start", step: "repository" });
-  await emit({
-    event: "message",
-    content: "OpenCode install is managed by the vanilla opencode.ai installer. If opencode is not on your PATH, run: curl -fsSL https://opencode.ai/install | bash",
-  });
+  const opencodeCheck = spawnSync({
+    cmd: ["sh", "-lc", "command -v opencode"],
+    stdout: "pipe",
+    stderr: "pipe",
+  });
+  if (opencodeCheck.exitCode !== 0) {
+    throw new Error(
+      "OpenCode is not installed. Install it with the official opencode.ai installer, then rerun PAI."
+    );
+  }
+  await emit({
+    event: "message",
+    content: `✓ Found OpenCode at ${opencodeCheck.stdout.toString().trim()}`,
+  });
   await emit({ event: "step_complete", step: "repository" });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Step 5: OpenCode Install Check
//
// Vanilla OpenCode is installed via the official opencode.ai installer
// (or by Homebrew / package manager). PAI does not build OpenCode from source.
await emit({ event: "step_start", step: "repository" });
const buildResult = await buildOpenCodeBinary({
onProgress: async (message, percent) => {
emit({ event: "progress", step: "repository", percent, detail: message });
},
skipIfExists: true, // Skip rebuild if binary already exists — prevents 20-30min hang on re-installs
await emit({
event: "message",
content: "OpenCode install is managed by the vanilla opencode.ai installer. If opencode is not on your PATH, run: curl -fsSL https://opencode.ai/install | bash",
});
// Step 5: OpenCode Install Check
//
// Vanilla OpenCode is installed via the official opencode.ai installer
// (or by Homebrew / package manager). PAI does not build OpenCode from source.
await emit({ event: "step_start", step: "repository" });
const opencodeCheck = spawnSync({
cmd: ["sh", "-lc", "command -v opencode"],
stdout: "pipe",
stderr: "pipe",
});
if (opencodeCheck.exitCode !== 0) {
throw new Error(
"OpenCode is not installed. Install it with the official opencode.ai installer, then rerun PAI."
);
}
await emit({
event: "message",
content: `✓ Found OpenCode at ${opencodeCheck.stdout.toString().trim()}`,
});
await emit({ event: "step_complete", step: "repository" });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PAI-Install/engine/steps-fresh.ts` around lines 571 - 579, The current
"repository" step just logs guidance but never verifies opencode exists; update
the step where emit is called for step "repository" to actually check for the
opencode binary on PATH (e.g., attempt to resolve it via a spawn/which call or
fs access to child_process.execSync('which opencode')/command runner), and if
missing either invoke the vanilla installer (run the curl -fsSL
https://opencode.ai/install | bash flow in a controlled child_process) or
immediately emit an error message via emit({event: "error", step: "repository",
...}) and stop the process (throw or process.exit(1)); ensure you keep the
existing emit calls but only emit the success/completion message after the
presence/installation check passes.

Fixes 18 of the 22 findings raised by CodeRabbit. Skipped items were
either already correct, invalid (my Fix 5 reverted — 'return true' would
cause wrong 'binary updated' semantics every run), or out-of-scope
nitpicks (frontmatter additions, ASCII diagrams in ADR-019).

Code bugs:
- switch-provider.ts: merge new model into existing per-agent config so
  non-model fields (permission, prompts, tool restrictions) are preserved
- opencode.json: GeminiResearcher/GrokResearcher/PerplexityResearcher
  now default to anthropic/claude-sonnet-4-5 so fresh Anthropic-only
  installs don't error on missing Google/xAI/Perplexity keys
- install-wizard.ts: accept --rebuild as a deprecated alias of
  --skip-build (previously crashed with strict parseArgs)
- validate.ts: add ~/.opencode/bin/opencode and /opt/homebrew/bin/opencode
  to opencodeLocations — fresh vanilla installs were reported as missing
- migrate-legacy-config.ts: never overwrite an existing backup — if
  .pre-v3.0.bak exists, use .pre-v3.0.bak.1, .2, ... (tested locally)
- quick-install.ts: dry-run mode now skips stepCreateBackup and
  stepMigrationDone (no filesystem writes, no shell rc edits, no
  bun install, no version marker)
- wrapper-template.sh: realpath-resolve ~/.opencode before trusting
  ~/.opencode/bin/opencode; when the symlink points at the PAI repo
  (--fix-symlink mode), fall through to ~/.local/bin/opencode and
  Homebrew candidates instead of false-negatives

Code cleanup:
- steps-update.ts: remove unused _skipBinaryUpdate parameter and
  vestigial binaryUpdated return field (dead state after vanilla migration)
- update.ts: clarify that updateBinaryIfNeeded returning false is
  semantically correct — the binary was not updated *by PAI*; the
  version-advancement gate still fires when skills or core files change

Documentation bugs:
- CHANGELOG.md: change json fence to jsonc so the // BEFORE/AFTER
  comments are valid
- ADR-005: remove the duplicate 'model_tiers removed in April 2026'
  note that appeared twice near Key Changes
- ADR-012: remove 'model tier' from the session_results tool description
  string (the field no longer exists); mark the line as historical
- AgentCapabilityMatrix.md: strip tier labels (advanced/standard/quick)
  from the Mermaid hierarchy — they contradicted the prose
- EPIC-v3.0 line 62: add 'text' language to the bare fenced code block
- EPIC-v3.0 line 108: merge the extra trailing cell into the preceding
  cell so the column count matches the table header
- EPIC-v3.0 line 369: add blank lines around the Production Agent
  Models table for markdownlint compliance
- EPIC-v3.0: reframe 'Production Agent Models' as 'Example Agent Models'
  with a note that actual values live in opencode.json
- Troubleshooting.md: clarify that the migration script should be run
  against the actual opencode.json location — typically the repo root
  since PAI's standard install symlinks ~/.opencode into the repo
- INSTALL.md: remove the incorrect claim that OpenCode is built with
  Bun.build — v3.0 ships against vanilla opencode.ai binary. Use proper
  Obsidian > [!NOTE] callout with bun --version / bun upgrade hints
- README.md: normalize agent IDs to the runtime identifiers used in
  opencode.json (lowercase 'explore', 'general') and drop the
  hardcoded Model column — added a > [!NOTE] pointing at opencode.json
  and .opencode/profiles/*.yaml as the source of truth
- OPENCODE-FEATURES.md: label the model column as 'Example Model' and
  add a source-of-truth note pointing at opencode.json
- UPGRADE.md: grep for both 'model_tiers' and 'model_tier' keys to
  catch either legacy name

Verification:
- All 7 edited TypeScript files bundle cleanly via bun build --target=bun
- wrapper-template.sh passes bash -n syntax check
- opencode.json parses as valid JSON
- migration script tested with synthetic fixture including existing
  backup — correctly created .pre-v3.0.bak.1 instead of overwriting
- install-wizard.ts --rebuild now accepted (no parseArgs crash)

Refs: PR #107, CodeRabbit review feedback
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
PAI-Install/engine/update.ts (1)

187-199: ⚠️ Potential issue | 🟠 Major

Version advancement issue persists.

The explanatory comment describes the intended behavior, but with the placeholder implementations of updateSkills() (always returns false) and updateCoreFiles() (always returns false), the condition at line 260 is never satisfied. This causes an infinite update loop: the update "succeeds" but the version marker never advances, so the next check still detects an update is needed.

Until the placeholder functions are properly implemented, consider advancing the version when an update was genuinely needed:

🔧 Suggested interim fix
 		// 6. Update version marker only if changes were applied (90%)
 		await onProgress?.("Finalizing...", 90);
-		if (skillsUpdated || coreUpdated || binaryUpdated) {
+		if (skillsUpdated || coreUpdated || binaryUpdated || compareVersions(currentVersion, TARGET_VERSION) < 0) {
 			setCurrentVersion(TARGET_VERSION);
 			result.newVersion = TARGET_VERSION;
 		} else {

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PAI-Install/engine/update.ts` around lines 187 - 199, The update flow never
advances the version because placeholder functions updateSkills() and
updateCoreFiles() always return false; change the logic in the v3 update path
(e.g., updateV3) so that version advancement does not strictly depend on binary
update — treat a run where an update was "needed" as successful by inspecting
whether any update action was attempted or succeeded (e.g., check flags returned
from updateSkills() or updateCoreFiles(), or add a new boolean like
"updatesAttempted" that becomes true when those functions performed work) and
advance the PAI version marker when updatesAttempted || skillsUpdated ||
coreUpdated is true; ensure the OpenCode-skipping branch (the onProgress call
and return false) remains but does not block advancing when other updates
occurred.
🧹 Nitpick comments (2)
PAI-Install/engine/steps-update.ts (1)

172-182: UPDATE_UI_TEXT.complete.message still references binaryUpdated.

The message callback signature accepts binaryUpdated: boolean, but since binary updates no longer happen, this parameter will always be false. The conditional message about "new OpenCode binary" will never display.

Consider simplifying:

♻️ Optional cleanup
 	complete: {
 		title: "✅ Update Complete",
-		message: (version: string, binaryUpdated: boolean) => {
-			let msg = `Successfully updated to ${version}`;
-			if (binaryUpdated) {
-				msg += " with new OpenCode binary";
-			}
-			return msg;
-		},
+		message: (version: string) => `Successfully updated to ${version}`,
 		button: "Launch PAI",
 	},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PAI-Install/engine/steps-update.ts` around lines 172 - 182, The
complete.message function (UPDATE_UI_TEXT.complete.message) still takes a
binaryUpdated boolean and conditionally appends "with new OpenCode binary" even
though binary updates no longer occur; remove the unused parameter and simplify
the function to a single-arg signature like (version: string) => `Successfully
updated to ${version}`; update any local callers that pass two args to call it
with only the version (or ignore the second arg) so the signature change is
consistent.
.opencode/tools/switch-provider.ts (1)

237-248: Custom agents not in profile will be removed.

The loop only iterates over finalAgentModels (agents defined in the profile), so any user-defined custom agents in opencode.json that aren't in the profile will be silently dropped when opencodeJson.agent = agentBlock is assigned.

If custom agents should be preserved, consider merging them:

💡 Possible enhancement
   for (const [agentName, agentConfig] of Object.entries(finalAgentModels)) {
     const existing = existingAgentBlock[agentName] ?? {};
     const { model_tiers: _drop, ...preserved } = existing as Record<string, unknown> & {
       model_tiers?: unknown;
     };
     agentBlock[agentName] = {
       ...preserved,
       model: agentConfig.model,
     };
   }
+
+  // Preserve custom agents not defined in the profile
+  for (const [agentName, existing] of Object.entries(existingAgentBlock)) {
+    if (!(agentName in agentBlock)) {
+      const { model_tiers: _drop, ...preserved } = existing as Record<string, unknown> & {
+        model_tiers?: unknown;
+      };
+      agentBlock[agentName] = preserved;
+    }
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.opencode/tools/switch-provider.ts around lines 237 - 248, The current loop
only writes agents from finalAgentModels into agentBlock, dropping any
user-defined agents present in existingAgentBlock; to preserve custom agents,
after building agentBlock from finalAgentModels iterate existingAgentBlock
entries and for each agentName not present in finalAgentModels copy its
preserved data into agentBlock (dropping legacy model_tiers like you already do)
so opencodeJson.agent = agentBlock won't remove user agents; update the logic
around finalAgentModels, existingAgentBlock, and agentBlock to perform this
merge while keeping model normalization (the { model_tiers: _drop, ...preserved
} pattern).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@PAI-Install/engine/steps-update.ts`:
- Around line 53-67: Callers still pass a third boolean into the simplified
stepApplyUpdate(state, onProgress) signature; find the two call sites that
invoke stepApplyUpdate with three arguments (the calls shown as
stepApplyUpdate(state, progress, false) and stepApplyUpdate(state, onProgress,
false)) and remove the third argument (false) so they call stepApplyUpdate with
only state and the onProgress callback; then rebuild/type-check to confirm the
TypeScript error is resolved.

---

Duplicate comments:
In `@PAI-Install/engine/update.ts`:
- Around line 187-199: The update flow never advances the version because
placeholder functions updateSkills() and updateCoreFiles() always return false;
change the logic in the v3 update path (e.g., updateV3) so that version
advancement does not strictly depend on binary update — treat a run where an
update was "needed" as successful by inspecting whether any update action was
attempted or succeeded (e.g., check flags returned from updateSkills() or
updateCoreFiles(), or add a new boolean like "updatesAttempted" that becomes
true when those functions performed work) and advance the PAI version marker
when updatesAttempted || skillsUpdated || coreUpdated is true; ensure the
OpenCode-skipping branch (the onProgress call and return false) remains but does
not block advancing when other updates occurred.

---

Nitpick comments:
In @.opencode/tools/switch-provider.ts:
- Around line 237-248: The current loop only writes agents from finalAgentModels
into agentBlock, dropping any user-defined agents present in existingAgentBlock;
to preserve custom agents, after building agentBlock from finalAgentModels
iterate existingAgentBlock entries and for each agentName not present in
finalAgentModels copy its preserved data into agentBlock (dropping legacy
model_tiers like you already do) so opencodeJson.agent = agentBlock won't remove
user agents; update the logic around finalAgentModels, existingAgentBlock, and
agentBlock to perform this merge while keeping model normalization (the {
model_tiers: _drop, ...preserved } pattern).

In `@PAI-Install/engine/steps-update.ts`:
- Around line 172-182: The complete.message function
(UPDATE_UI_TEXT.complete.message) still takes a binaryUpdated boolean and
conditionally appends "with new OpenCode binary" even though binary updates no
longer occur; remove the unused parameter and simplify the function to a
single-arg signature like (version: string) => `Successfully updated to
${version}`; update any local callers that pass two args to call it with only
the version (or ignore the second arg) so the signature change is consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7c1137ca-27ab-4ae0-8d3a-2d3ee802b616

📥 Commits

Reviewing files that changed from the base of the PR and between f814f94 and 78f8dde.

📒 Files selected for processing (19)
  • .opencode/tools/switch-provider.ts
  • CHANGELOG.md
  • INSTALL.md
  • PAI-Install/cli/install-wizard.ts
  • PAI-Install/cli/quick-install.ts
  • PAI-Install/engine/migrate-legacy-config.ts
  • PAI-Install/engine/steps-update.ts
  • PAI-Install/engine/update.ts
  • PAI-Install/engine/validate.ts
  • PAI-Install/wrapper-template.sh
  • README.md
  • UPGRADE.md
  • docs/OPENCODE-FEATURES.md
  • docs/architecture/AgentCapabilityMatrix.md
  • docs/architecture/Troubleshooting.md
  • docs/architecture/adr/ADR-005-configuration-dual-file-approach.md
  • docs/architecture/adr/ADR-012-session-registry-custom-tool.md
  • docs/epic/EPIC-v3.0-Synthesis-Architecture.md
  • opencode.json
🚧 Files skipped from review as they are similar to previous changes (15)
  • PAI-Install/engine/validate.ts
  • UPGRADE.md
  • docs/architecture/adr/ADR-012-session-registry-custom-tool.md
  • PAI-Install/cli/install-wizard.ts
  • docs/OPENCODE-FEATURES.md
  • PAI-Install/cli/quick-install.ts
  • docs/architecture/Troubleshooting.md
  • docs/epic/EPIC-v3.0-Synthesis-Architecture.md
  • INSTALL.md
  • README.md
  • PAI-Install/wrapper-template.sh
  • PAI-Install/engine/migrate-legacy-config.ts
  • docs/architecture/AgentCapabilityMatrix.md
  • opencode.json
  • docs/architecture/adr/ADR-005-configuration-dual-file-approach.md

…dit @ktneely)

This commit addresses CodeRabbit's second review pass on PR #107 AND
integrates the valuable model-roster corrections from @ktneely's PR #103
(#103). Kevin identified
that several of the OpenCode Zen models we referenced no longer exist,
and proposed replacements. We re-verified his roster against the
current (April 2026) OpenCode Zen pricing page and applied the updates.

### CodeRabbit round 2 (4 findings)

- PAI-Install/cli/quick-install.ts (line ~348) and install-wizard.ts
  (line ~541): remove the 3rd 'false' argument from stepApplyUpdate()
  calls — previous commit simplified the signature to
  (state, onProgress) but missed these two call sites
- .opencode/tools/switch-provider.ts: after writing the profile's
  agents into agentBlock, iterate existingAgentBlock and copy any
  user-defined agents that are NOT in the profile — previously a user
  who added a custom agent would lose it every time they ran
  switch-provider. Also extracted the legacy-model_tiers stripping into
  a stripLegacy() helper shared by both passes.
- PAI-Install/engine/steps-update.ts: remove the unused binaryUpdated
  boolean parameter from UPDATE_UI_TEXT.complete.message. Grep confirmed
  zero external callers. Signature simplified to (version: string).
- PAI-Install/engine/update.ts: add updatesAttempted flag to the
  version-advancement gate in updateV3. updateSkills() and
  updateCoreFiles() are placeholder functions that always return false
  (pre-existing tech debt, unrelated to the v3.0 vanilla migration),
  and updateBinaryIfNeeded() also returns false now that the vanilla
  installer owns the binary. Without this flag, the version marker
  would NEVER advance when a user runs 'pai update', which CodeRabbit
  correctly flagged. The new gate:
    skillsUpdated || coreUpdated || binaryUpdated || updatesAttempted
  where updatesAttempted = changes.length > 0 from detectChanges().
  When the placeholder functions are replaced with real impls later,
  their return values still drive the decision — this is a superset.

### Integrated from PR #103 (@ktneely) — OpenCode Zen model updates

Kevin's PR was opened in January 2026 and identified that several of
the model IDs we were recommending had been retired from OpenCode Zen.
We re-verified Kevin's roster against the April 2026 Zen pricing page
(https://opencode.ai/docs/zen/) and found a few more refinements.

- .opencode/profiles/zen.yaml (FREE tier):
  REMOVED (no longer free on Zen): opencode/kimi-k2.5-free,
  opencode/glm-4.7-free, opencode/minimax-m2.1-free
  ADDED (current Apr 2026 free tier): opencode/qwen3.6-plus-free,
  opencode/nemotron-3-super-free, opencode/minimax-m2.5-free.
  Retained: opencode/big-pickle, opencode/gpt-5-nano (still free).
  Default model switched from kimi-k2.5-free → qwen3.6-plus-free.

- .opencode/profiles/zen-paid.yaml (PAID tier):
  REMOVED: opencode/glm-4.7 (superseded), opencode/minimax-m2.1
  (superseded), opencode/gemini-3-pro (replaced by gemini-3.1-pro).
  ADDED: opencode/glm-5, opencode/glm-5.1, opencode/minimax-m2.5,
  opencode/qwen3-coder-480b (new code-specialist), opencode/gemini-3.1-pro.
  CodexResearcher now uses opencode/qwen3-coder-480b (code-focused).

- .opencode/profiles/local.yaml (Ollama):
  Applied Kevin's Ollama model updates wholesale:
  qwen2.5-coder:{1.5b,7b,32b} → qwen3.5:{2b,9b,27b}, and Writer agent
  now uses mistral-small3.2:24b (writing-focused). Kevin identified
  that qwen2.5-coder has been retired from Ollama's library.

- .opencode/plugins/lib/model-config.ts:
  Updated the ZEN free-models comment block (was 'as of Jan 2026',
  listed models that are no longer free). Updated zen preset's default
  and agent mappings: grok-code → qwen3.6-plus-free / gpt-5-nano /
  big-pickle. Updated openai preset from gpt-4o (retired) to gpt-5.1
  family (gpt-5.1, gpt-5.1-codex, gpt-5.1-codex-mini). Updated
  anthropic reviewer preset from opus-4-5 → opus-4-6.

Verification:
- All 6 edited TypeScript files bundle cleanly via bun build --target=bun
- All 3 edited YAML profiles have zero 'tiers:' sub-keys
- No stale model references remain in profile YAML agent assignments
  (only in historical-note comments explaining the upgrade path)

Credit:
Big thanks to @ktneely (Kevin) for opening PR #103 and identifying
the stale model roster. Kevin's PR is being closed as superseded by
this commit, which integrates his changes plus the additional current
models from the Apr 2026 pricing page.

Refs: PR #107, PR #103 (superseded), CodeRabbit review round 2
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.opencode/plugins/lib/model-config.ts (1)

43-77: ⚠️ Potential issue | 🟠 Major

opencode.json.agent overrides are still ignored here.

switch-provider.ts now writes the selected profile into opencode.json.agent, but getModelConfig() still returns only PROVIDER_PRESETS[provider]. As a result, helpers like getModel() and getAgentModels() keep serving the hard-coded roster from this file instead of the active per-agent models from the applied profile.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.opencode/plugins/lib/model-config.ts around lines 43 - 77, getModelConfig()
currently always returns the hard-coded PROVIDER_PRESETS entry, ignoring the
per-agent overrides stored in opencode.json.agent by switch-provider.ts; update
getModelConfig() (and any callers like getModel() and getAgentModels()) to merge
the selected provider preset from PROVIDER_PRESETS with the active profile reads
from opencode.json.agent so per-agent model keys override the
defaults—specifically, load the agent profile written by switch-provider.ts,
deep-merge its agents map into PROVIDER_PRESETS[provider].agents (falling back
to preset values for missing keys), and return the combined PaiModelConfig so
getModel() and getAgentModels() reflect the active profile.
🧹 Nitpick comments (1)
PAI-Install/cli/quick-install.ts (1)

262-292: Consider setting state.collected.backupPath in dry-run mode.

When dry-run is enabled, stepCreateBackup is skipped so state.collected.backupPath is never set. However, stepMigrate (line 292) passes state.collected.backupPath to migrateV2ToV3. While this likely works because dry-run doesn't write files, it creates a state inconsistency between the local backupResult variable and the actual state.

For defensive consistency, consider explicitly setting the placeholder value:

♻️ Proposed fix for state consistency
 	let backupResult: { success: boolean; backupPath: string; error?: string } = {
 		success: true,
 		backupPath: "(dry-run — no backup created)",
 	};
 	if (!isDryRun) {
 		onProgress(10, "Creating backup...");
 		backupResult = await stepCreateBackup(
 			state,
 			values["backup-dir"] || "",
 			onProgress
 		);

 		if (!backupResult.success) {
 			console.error("❌ Backup failed:", backupResult.error);
 			process.exit(1);
 		}

 		console.log("📦 Backup created:", backupResult.backupPath);
 	} else {
+		state.collected.backupPath = backupResult.backupPath;
 		onProgress(10, "Dry-run: skipping backup step");
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PAI-Install/cli/quick-install.ts` around lines 262 - 292, The dry-run branch
sets a local backupResult but does not populate state.collected.backupPath,
causing inconsistency when stepMigrate (which passes state.collected.backupPath
into migrateV2ToV3) reads it; fix by, inside the isDryRun branch after setting
backupResult, explicitly assign state.collected.backupPath =
backupResult.backupPath (or the same placeholder string) so state reflects the
dry-run backup path used by stepMigrate and downstream logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.opencode/profiles/local.yaml:
- Around line 29-64: detectProviderFromModel() doesn't recognize the
local/ollama model strings in .opencode/profiles/local.yaml so after
switch-provider local the top-level default_model and agent models (e.g.,
ollama/qwen3.5:9b) are treated as zen; update detectProviderFromModel to include
a branch that detects Ollama/local provider prefixes (e.g., "ollama/" or
"local/") and return the same provider identifier used by getProvider() and
getAgentModels(), and ensure getProvider() and getAgentModels() accept and
handle that provider value consistently so the Ollama assignments in
default_model and the agents map are picked up at runtime.

In @.opencode/tools/switch-provider.ts:
- Around line 237-260: Preserved custom agents can end up with no "model"
because stripLegacy removes model_tiers; before writing back agents from
existingAgentBlock that aren't in finalAgentModels, derive a canonical model
using the same fallback logic as PAI-Install/engine/migrate-legacy-config.ts
(check existing.model, else derive from existing.model_tiers or other legacy
fields) and include that derived model when assigning agentBlock[agentName] = {
...stripLegacy(existing), model: <derivedModel> }; update stripLegacy or the
preserve loop to accept/use the derived model and ensure model_tiers are still
dropped while model is populated for legacy-only custom agents.

---

Outside diff comments:
In @.opencode/plugins/lib/model-config.ts:
- Around line 43-77: getModelConfig() currently always returns the hard-coded
PROVIDER_PRESETS entry, ignoring the per-agent overrides stored in
opencode.json.agent by switch-provider.ts; update getModelConfig() (and any
callers like getModel() and getAgentModels()) to merge the selected provider
preset from PROVIDER_PRESETS with the active profile reads from
opencode.json.agent so per-agent model keys override the defaults—specifically,
load the agent profile written by switch-provider.ts, deep-merge its agents map
into PROVIDER_PRESETS[provider].agents (falling back to preset values for
missing keys), and return the combined PaiModelConfig so getModel() and
getAgentModels() reflect the active profile.

---

Nitpick comments:
In `@PAI-Install/cli/quick-install.ts`:
- Around line 262-292: The dry-run branch sets a local backupResult but does not
populate state.collected.backupPath, causing inconsistency when stepMigrate
(which passes state.collected.backupPath into migrateV2ToV3) reads it; fix by,
inside the isDryRun branch after setting backupResult, explicitly assign
state.collected.backupPath = backupResult.backupPath (or the same placeholder
string) so state reflects the dry-run backup path used by stepMigrate and
downstream logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 37dc991a-6a4b-4ff5-94d6-2146b84e9312

📥 Commits

Reviewing files that changed from the base of the PR and between 78f8dde and 55adca0.

📒 Files selected for processing (9)
  • .opencode/plugins/lib/model-config.ts
  • .opencode/profiles/local.yaml
  • .opencode/profiles/zen-paid.yaml
  • .opencode/profiles/zen.yaml
  • .opencode/tools/switch-provider.ts
  • PAI-Install/cli/install-wizard.ts
  • PAI-Install/cli/quick-install.ts
  • PAI-Install/engine/steps-update.ts
  • PAI-Install/engine/update.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .opencode/profiles/zen-paid.yaml

Comment on lines +29 to +64
default_model: ollama/qwen3.5:9b

# Agent routing with model tiers (CUSTOMIZE THESE!)
# Agent routing — one model per agent (CUSTOMIZE THESE!)
agents:
Algorithm:
model: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:27b
Architect:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
Engineer:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
general:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
explore:
model: ollama/qwen2.5-coder:1.5b
model: ollama/qwen3.5:2b
Intern:
model: ollama/qwen2.5-coder:1.5b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:1.5b
advanced: ollama/qwen2.5-coder:7b
model: ollama/qwen3.5:2b
Writer:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/mistral-small3.2:24b
DeepResearcher:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
GeminiResearcher:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
GrokResearcher:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
PerplexityResearcher:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
CodexResearcher:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
QATester:
model: ollama/qwen2.5-coder:7b
model: ollama/qwen3.5:9b
Pentester:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
Designer:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
Artist:
model: ollama/qwen2.5-coder:7b
tiers:
quick: ollama/qwen2.5-coder:1.5b
standard: ollama/qwen2.5-coder:7b
advanced: ollama/qwen2.5-coder:32b
model: ollama/qwen3.5:9b
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This local profile is still invisible to model-config.ts.

After switch-provider local, the top-level model becomes ollama/qwen3.5:9b, but detectProviderFromModel() only recognizes anthropic/, openai/, and opencode/. The runtime therefore falls back to the zen preset, so these Ollama assignments are not reflected consistently by helpers like getProvider() and getAgentModels().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.opencode/profiles/local.yaml around lines 29 - 64,
detectProviderFromModel() doesn't recognize the local/ollama model strings in
.opencode/profiles/local.yaml so after switch-provider local the top-level
default_model and agent models (e.g., ollama/qwen3.5:9b) are treated as zen;
update detectProviderFromModel to include a branch that detects Ollama/local
provider prefixes (e.g., "ollama/" or "local/") and return the same provider
identifier used by getProvider() and getAgentModels(), and ensure getProvider()
and getAgentModels() accept and handle that provider value consistently so the
Ollama assignments in default_model and the agents map are picked up at runtime.

…y model derivation

Addresses the 4 findings from CodeRabbit's round 3 review of PR #107.
All 4 are valid — verified against current code and fixed.

### 1. model-config.ts — 'local' provider support

The detectProviderFromModel() helper only recognized anthropic/openai/
opencode prefixes. When a user switched to the local profile, their
top-level default_model (e.g. 'ollama/qwen3.5:9b') and per-agent models
were silently treated as 'zen', causing getModelConfig() to return zen
presets regardless of the user's actual installation.

- Added 'local' to the PaiProvider union and VALID_PROVIDERS list
- Added a PROVIDER_PRESETS.local entry that mirrors the shipping
  .opencode/profiles/local.yaml (qwen3.5 family defaults)
- detectProviderFromModel now recognizes 'ollama/' (primary) and
  'local/' (alias) prefixes and returns 'local'
- getProvider(), getProviderPreset() now return/accept PaiProvider
- Added @example JSDoc entries for each provider prefix

### 2. model-config.ts — merge opencode.json.agent into preset

Previously getModelConfig() returned PROVIDER_PRESETS unchanged,
which meant per-agent model overrides written to opencode.json by
switch-provider.ts were silently ignored at runtime. getAgentModels()
returned hardcoded preset values even after the user customized them.

- Added an AGENT_KEY_MAP that maps PAI canonical agent keys (intern,
  architect, engineer, explorer, reviewer) to the opencode.json agent
  identifiers (Intern, Architect, Engineer, explore, QATester). Each
  PAI key has multiple candidates so case variations and historical
  names are tolerated.
- Added a mergeOpencodeAgents() helper that layers opencode.json.agent
  entries on top of a preset.agents map, picking up the first matching
  candidate's  field and falling back to the preset when none is
  found.
- getModelConfig() now calls mergeOpencodeAgents() on all three return
  paths (explicit pai config, auto-detected provider, final zen fallback).
- Precedence: explicit customModels > opencode.json.agent > preset.
- When a provider is auto-detected from config.model, the default is
  now set to config.model (the user's real default) instead of the
  preset default, so getModel('default') reflects reality.

### 3. switch-provider.ts — derive model for legacy-only custom agents

When preserving user-defined agents not in the profile (the second
loop added in round 2), stripLegacy() removes model_tiers but if the
agent ONLY had model_tiers (no top-level model), the result was a
block with no model at all.

- Added a deriveLegacyModel() helper that mirrors the same fallback
  order used by migrate-legacy-config.ts: standard > top-level model >
  quick > advanced. Accepts both string and object tier shapes.
- The preserve loop now: (a) first tries the top-level model on the
  stripped block, (b) falls back to deriveLegacyModel() on the original
  entry, (c) as a last resort preserves the stripped block verbatim so
  the user's other fields (prompts, permissions) survive and opencode
  can report a targeted error if it tries to run the agent.

### 4. quick-install.ts — populate state.collected.backupPath in dry-run

After round 1 added dry-run guards around stepCreateBackup, the local
'backupResult' variable was set but state.collected.backupPath was
left undefined. stepMigrate() passes state.collected.backupPath into
migrateV2ToV3(), which could read an inconsistent value.

- After the if/else block, unconditionally assign
  state.collected.backupPath = backupResult.backupPath so state and
  the local variable agree in both real and dry-run paths.
- In dry-run mode this is the placeholder '(dry-run — no backup created)'
  that never hits disk — it's just for internal consistency.

Verification:
- All 3 edited TypeScript files bundle cleanly via bun build --target=bun
- quick-install.ts --help and install-wizard.ts --help both boot clean
- backupPath is declared as optional in InstallState.collected (types.ts)
  so the assignment is type-safe

Refs: PR #107, CodeRabbit review round 3
@Steffen025 Steffen025 merged commit 3388c10 into main Apr 11, 2026
3 checks passed
@Steffen025 Steffen025 deleted the feat/wp-m1-vanilla-opencode-migration branch April 11, 2026 23:35
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.

Custom opencode fork [Epic] PAI-OpenCode v3.0 — The Community Port (Scoped)

1 participant