feat(wp-m1): bury model_tiers, migrate to vanilla OpenCode (v3.0 BREAKING)#107
Conversation
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
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR removes runtime Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟠 MajorAdd
~/.opencode/bin/opencodeto 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 | 🟠 MajorHonor
--dry-runend-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 runsbun install. Dry-run needs to skip both the backup/finalization steps or thread adryRunflag 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_tiersandmodel_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.
_skipBinaryUpdateis now ignored andbinaryUpdatedis permanentlyfalse, so this function still advertises behavior the installer no longer supports. Since v3 is already a breaking release, returning plainUpdateResulthere 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
📒 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.tsCHANGELOG.mdINSTALL.mdKNOWN_LIMITATIONS.mdPAI-Install/cli/install-wizard.tsPAI-Install/cli/quick-install.tsPAI-Install/engine/actions.tsPAI-Install/engine/build-opencode.tsPAI-Install/engine/migrate-legacy-config.tsPAI-Install/engine/steps-fresh.tsPAI-Install/engine/steps-migrate.tsPAI-Install/engine/steps-update.tsPAI-Install/engine/types.tsPAI-Install/engine/update.tsPAI-Install/engine/validate.tsPAI-Install/wrapper-template.shREADME.mdUPGRADE.mddocs/ADVANCED-SETUP.mddocs/MIGRATION.mddocs/OPENCODE-FEATURES.mddocs/PAI-ADAPTATIONS.mddocs/PLATFORM-DIFFERENCES.mddocs/architecture/AgentCapabilityMatrix.mddocs/architecture/Configuration.mddocs/architecture/ToolReference.mddocs/architecture/Troubleshooting.mddocs/architecture/adr/ADR-005-configuration-dual-file-approach.mddocs/architecture/adr/ADR-012-session-registry-custom-tool.mddocs/architecture/adr/ADR-019-vanilla-opencode-migration.mddocs/epic/EPIC-v3.0-Synthesis-Architecture.mdopencode.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
| // 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", | ||
| }); |
There was a problem hiding this comment.
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.
| // 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
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
PAI-Install/engine/update.ts (1)
187-199:⚠️ Potential issue | 🟠 MajorVersion advancement issue persists.
The explanatory comment describes the intended behavior, but with the placeholder implementations of
updateSkills()(always returnsfalse) andupdateCoreFiles()(always returnsfalse), 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.messagestill referencesbinaryUpdated.The message callback signature accepts
binaryUpdated: boolean, but since binary updates no longer happen, this parameter will always befalse. 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 inopencode.jsonthat aren't in the profile will be silently dropped whenopencodeJson.agent = agentBlockis 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
📒 Files selected for processing (19)
.opencode/tools/switch-provider.tsCHANGELOG.mdINSTALL.mdPAI-Install/cli/install-wizard.tsPAI-Install/cli/quick-install.tsPAI-Install/engine/migrate-legacy-config.tsPAI-Install/engine/steps-update.tsPAI-Install/engine/update.tsPAI-Install/engine/validate.tsPAI-Install/wrapper-template.shREADME.mdUPGRADE.mddocs/OPENCODE-FEATURES.mddocs/architecture/AgentCapabilityMatrix.mddocs/architecture/Troubleshooting.mddocs/architecture/adr/ADR-005-configuration-dual-file-approach.mddocs/architecture/adr/ADR-012-session-registry-custom-tool.mddocs/epic/EPIC-v3.0-Synthesis-Architecture.mdopencode.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
There was a problem hiding this comment.
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.agentoverrides are still ignored here.
switch-provider.tsnow writes the selected profile intoopencode.json.agent, butgetModelConfig()still returns onlyPROVIDER_PRESETS[provider]. As a result, helpers likegetModel()andgetAgentModels()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 settingstate.collected.backupPathin dry-run mode.When dry-run is enabled,
stepCreateBackupis skipped sostate.collected.backupPathis never set. However,stepMigrate(line 292) passesstate.collected.backupPathtomigrateV2ToV3. While this likely works because dry-run doesn't write files, it creates a state inconsistency between the localbackupResultvariable 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
📒 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.tsPAI-Install/cli/install-wizard.tsPAI-Install/cli/quick-install.tsPAI-Install/engine/steps-update.tsPAI-Install/engine/update.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- .opencode/profiles/zen-paid.yaml
| 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 |
There was a problem hiding this comment.
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
Summary
WP-M1 Vanilla OpenCode Migration — Remove the
model_tiersfeature and the dependency on theSteffen025/opencodefork. PAI-OpenCode now runs on vanilla OpenCode from opencode.ai. Each agent has exactly onemodelconfigured inopencode.json.Target release: PAI-OpenCode v3.0 (currently unreleased, tracked in
CHANGELOG.md[3.0.0] - Unreleased).Status: BREAKING change for users with
model_tiersin their localopencode.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-freewere all retired from Zen's free tierzen-paid.yaml(paid tier):glm-4.7→glm-5,minimax-m2.1→minimax-m2.5,gemini-3-pro→gemini-3.1-prolocal.yaml(Ollama):qwen2.5-coder:{1.5b,7b,32b}retired from Ollama library → upgraded toqwen3.5:{2b,9b,27b}family, Writer agent now usesmistral-small3.2:24bmodel-config.ts:grok-codeno longer exists on Zen free tierWhat 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-freesuggestions 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 addedopencode/qwen3-coder-480bto the paid tier'sCodexResearcherslot (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/opencodefork existed solely to cherry-pick an unmerged upstream PR (95fd6ea7by external contributor Mohammed Muzammil Anwar) that provided themodel_tierruntime parameter for routing Task tool calls to different models per agent.The investigation revealed:
upstream/dev(~4 months stale)variants(PRs #19488, #19534, #19537) — which is orthogonal to tiers (variants = same-model configurations, tiers = cross-model routing)What Changes
Runtime Code (13 files)
PAI-Install/engine/build-opencode.ts(245 LOC of fork build logic)opencode.json: 16 agents stripped ofmodel_tiersblocks → flat"model"per agentPAI-Install/engine/{actions,steps-fresh,steps-migrate,steps-update,update}.ts: Fork clone/build replaced withcurl -fsSL https://opencode.ai/install | bashPAI-Install/cli/{install-wizard,quick-install}.ts: Dead build prompts removed,--skip-buildflag deprecated (silently ignored with warning)PAI-Install/engine/validate.ts: Error message updated for vanilla install pathPAI-Install/wrapper-template.sh: Rewritten from custom-build launcher to vanilla wrapper;--rebuildreplaced with--install.opencode/plugins/handlers/agent-execution-guard.ts:modelTierreading + advanced-tier check removed.opencode/plugins/handlers/session-registry.ts:modelTierfield removed from interface, extractor, registry entries, and tool output.opencode/tools/switch-provider.ts:tiers?field removed fromAgentConfig,model_tiersgeneration removed.coderabbit.yaml:model_tier mappingremoved from agent frontmatter validationProfile YAMLs (5 files)
.opencode/profiles/{anthropic,openai,zen,zen-paid,local}.yaml— alltiers:sub-sections strippedNew Files (2)
PAI-Install/engine/migrate-legacy-config.ts— Automated converter for useropencode.jsonfiles with legacymodel_tiersblocks. Preserves each agent'sstandardtier model as the new canonicalmodel. Creates.pre-v3.0.bakbackup. Handles both object ({model: "..."}) and legacy string tier formats.docs/architecture/adr/ADR-019-vanilla-opencode-migration.md— Architectural decision recordDocumentation (19 files)
Rewritten in parallel via 5 Writer subagents with a shared replacement rulebook for consistency:
README.md,INSTALL.md,UPGRADE.md,KNOWN_LIMITATIONS.md,CHANGELOG.mddocs/(5):MIGRATION.md,ADVANCED-SETUP.md,OPENCODE-FEATURES.md,PAI-ADAPTATIONS.md,PLATFORM-DIFFERENCES.mddocs/architecture/(4):Configuration.md,ToolReference.md,Troubleshooting.md,AgentCapabilityMatrix.mddocs/architecture/adr/(2):ADR-005andADR-012gained> [!warning] PARTIAL SUPERSESSIONnotices at the top + inline historical notes preserving decision historydocs/epic/(1):EPIC-v3.0-Synthesis-Architecture.mdMigration Path for End Users
After this ships in v3.0, existing v2.x users with
model_tiersin their localopencode.jsoncan migrate automatically:bun run PAI-Install/engine/migrate-legacy-config.ts ~/.opencode/opencode.jsonThe script:
.pre-v3.0.bakbackup before any changesstandardtier model as the new canonicalmodelfieldmodel, thenquick, thenadvancedif no standard tier exists{model: "..."}) and legacy string tier formatsmodel_tiersblock)Or manually, delete all
model_tiersblocks fromopencode.json, keep only the top-level"model"field per agent.For Task tool callers, use agent selection instead of tier selection:
Verification
bun build --target=bun --outdir=...bash -nconfirmswrapper-template.shshell syntaxopencode.jsonparses as valid JSON with 0model_tiersblockstiers:sub-keysgrepacross runtime code returns 0model_tier/modelTier/Steffen025/opencode/feature/model-tiers/buildOpenCodeBinarymatches (only legitimate historical ADR notes, deprecation callouts, CHANGELOG entries, and migration-script data references remain)install.sh --help,install-wizard.ts --help, andquick-install.ts --helpall boot cleanStats
Rejected Alternatives (see ADR-019)
variantsschema for a feature the maintainers chose not to merge.Engineer-Quick,Engineer-Advanced) — Rejected. Violates the single-source-of-truth principle; models would be spread across 3x agent definitions instead of centralized inopencode.json.model_tiersto 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/opencodefork should be archived on GitHub:Full archival steps are documented in private working notes (not part of this PR).
Related
docs/architecture/adr/ADR-019-vanilla-opencode-migration.md95fd6ea7by Mohammed Muzammil Anwarvariantssystem (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 overwrittenopencode.json—GeminiResearcher,GrokResearcher,PerplexityResearcherdefault toanthropic/claude-sonnet-4-5so fresh Anthropic-only installs don't fail on missing Google/xAI/Perplexity keysinstall-wizard.ts—--rebuildnow accepted as a deprecated alias of--skip-build(was crashing with strictparseArgs)validate.ts— added~/.opencode/bin/opencodeand/opt/homebrew/bin/opencodeto the binary-detection list so vanilla installs are recognizedmigrate-legacy-config.ts— unique backup filenames (.pre-v3.0.bak.1,.2, ...) so repeated runs never overwrite an earlier backupquick-install.ts— dry-run mode now skipsstepCreateBackupandstepMigrationDone(no filesystem writes, no shell rc edits, nobun install)wrapper-template.sh— realpath-resolve~/.opencodebefore trusting~/.opencode/bin/opencode; when the symlink points at the PAI repo (--fix-symlinkmode), fall through to~/.local/bin/opencodeand Homebrew candidates instead of a false-negativeDocumentation bugs fixed:
CHANGELOG.md— json fence changed to jsonc so the//BEFORE/AFTER comments are validADR-005— removed duplicate "model_tiers removed" noteADR-012— removed "model tier" text from thesession_resultstool descriptionAgentCapabilityMatrix.md— stripped tier labels (advanced/standard/quick) from the Mermaid hierarchyEPIC-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 noteTroubleshooting.md— clarified that the migration script should be run against the actualopencode.jsonlocation (repo root for standard installs that symlink~/.opencode)INSTALL.md— removed incorrect claim that OpenCode is built withBun.build; use proper Obsidian> [!NOTE]callouts withbun --version/bun upgradehintsREADME.md— normalized agent IDs to lowercase runtime names (explore,general) and dropped hardcoded Model column; added a> [!NOTE]pointing atopencode.jsonand.opencode/profiles/*.yamlas source of truthOPENCODE-FEATURES.md— labeled model column as "Example Model" with explicit source-of-truth noteUPGRADE.md— grep for bothmodel_tiersANDmodel_tierlegacy keysCode cleanup:
steps-update.ts— removed unused_skipBinaryUpdateparameter and vestigialbinaryUpdatedreturn field (dead state after vanilla migration)update.ts— clarified in-source comment thatupdateBinaryIfNeededreturningfalseis 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→ returntrue: rejected. Returningtruewould make every update run incorrectly claim "binary updated" and advance the version marker even when nothing actually changed. The existingfalsereturn is semantically correct and was preserved with a clearer explanatory comment. (CodeRabbit raised this again in round 2 as a "duplicate comment" — see theupdatesAttemptedfix below for the proper resolution.)ADVANCED-SETUP.md, ASCII + Mermaid diagrams forADR-019): deferred — ADR-019 is already comprehensive, and frontmatter conventions for individual docs are out of scope for this migration PR.steps-fresh.tsrepository step adding actualwhich opencodeprobe: deferred — introduces new runtime behavior beyond the migration scope.actions.tsPATH/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 3rdfalseargument fromstepApplyUpdate()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 intoagentBlock, we now iterateexistingAgentBlockand copy any user-defined agents that are not in the profile. Previously, a user who added a custom agent toopencode.jsonwould have lost it every time they ranswitch-provider. Extracted the legacymodel_tiersstripping into astripLegacy()helper shared by both passes.steps-update.ts— removed the unusedbinaryUpdatedboolean parameter fromUPDATE_UI_TEXT.complete.message. Grep confirmed zero external callers, so this was dead state. Simplified signature to(version: string) => string.update.ts— added aupdatesAttemptedflag to the version-advancement gate inupdateV3. CodeRabbit correctly identified a pre-existing bug (unrelated to the vanilla migration):updateSkills()andupdateCoreFiles()are placeholder functions that always returnfalse, so without this flag the version marker would NEVER advance when a user runspai update. The new gate isskillsUpdated || coreUpdated || binaryUpdated || updatesAttemptedwhereupdatesAttempted = changes.length > 0fromdetectChanges(). 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 fromglm-4.7/minimax-m2.1toglm-5/glm-5.1/minimax-m2.5,CodexResearchernow uses the newqwen3-coder-480b.opencode/profiles/local.yaml— Kevin's Ollama model updates applied wholesale (qwen2.5-coderfamily →qwen3.5family, Writer usesmistral-small3.2:24b).opencode/plugins/lib/model-config.ts—zenpreset default changed from retiredgrok-codetoqwen3.6-plus-free;openaipreset updated from retiredgpt-4ofamily togpt-5.1family;anthropicreviewer upgraded toclaude-opus-4-6Summary by CodeRabbit
Breaking Changes
quick/standard/advanced); each agent now has a single model in opencode.json.model_tierruntime parameter removed—use agent selection instead.Installer / CLI
Agent-Based Routing
Configuration Migration
Documentation