Skip to content

feat(agent): link all OpenCode main providers - #110

Draft
clouds56 wants to merge 2 commits into
mainfrom
codex/opencode-main-switch-providers
Draft

clouds56 wants to merge 2 commits into
mainfrom
codex/opencode-main-switch-providers

Conversation

@clouds56

@clouds56 clouds56 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Make OpenCode main-account switch and passthrough links publish every enabled provider unless --provider explicitly pins one.
  • Give every unpinned raw provider a dedicated /opencode-<provider>/v1 profile, pinned to that provider, so it executes the requested verbatim mode instead of falling back to route.
  • Keep the binding profile valid with a deterministic fallback while ensuring generated OpenCode provider entries never target it.
  • Preserve the DeepSeek V4 Flash rewrite through the generated tokn-router-deepseek namespace.
  • Rename unlinked migration manifests to dot-prefixed inactive files so they cannot be selected as active migrations.

Validation

  • cargo test --locked -p tokn-agent-migration
  • cargo test --locked -p tokn-router
  • cargo test --locked -p tokn-gateway-cli --test e2e_proxy
  • cargo clippy --locked --workspace --all-targets --all-features -- -D warnings
  • cargo build --locked --workspace --all-features

cargo test --locked --workspace --all-features additionally reaches an unrelated pre-existing Z.ai price-ratio assertion that reads the local runtime catalogue cache; the changed packages pass independently.

Summary by CodeRabbit

  • New Features

    • Unpinned passthrough and switch links can now generate profiles for all enabled providers.
    • Use --provider to restrict a link to one provider; multi-provider selection remains available through provider filters.
    • Provider-specific routes and profiles now support main-account links without account IDs.
  • Bug Fixes

    • Improved provider selection and validation for existing bindings.
    • Unlinked manifests are archived and excluded from active results.
    • Added safeguards against archive name collisions and invalid paths.
  • Documentation

    • Updated linking, provider publication, profile layout, and unlinking guidance.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

OpenCode agent links now support optional provider pins and per-provider profile generation. Status validation and CLI layout handling reflect these profiles. Successful unlink operations archive manifests in inactive paths.

Changes

Agent link provider materialization

Layer / File(s) Summary
Provider materialization and profile generation
crates/agent-migration/src/reconcile.rs
Unpinned main-account links generate provider-specific routes, profiles, and gateway URLs. Pinned links use one provider.
Status validation and interface updates
crates/agent-migration/src/status.rs, crates/gateway-cli/src/cli/agent.rs, crates/router/src/api/mod.rs, README.md, crates/config/src/lib.rs
Status checks, CLI layout selection, router tests, and documentation now validate and describe provider-scoped profiles.
Manifest archiving during unlink
crates/agent-migration/src/manifest.rs, crates/agent-migration/src/reconcile.rs
Successful unlink operations archive manifests under inactive dot-prefixed paths and exclude them from active discovery.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • agentic-rs/tokn#108: Extends provider-aware OpenCode link handling in the same reconciliation and CLI areas.
  • agentic-rs/tokn#76: Shares OpenCode migration, manifest lifecycle, and provider-route behavior.
  • agentic-rs/tokn#58: Introduces related agent migration functionality.

Sequence Diagram(s)

sequenceDiagram
  participant AgentCLI
  participant Reconcile
  participant ProviderRoutes
  participant OpenCode
  AgentCLI->>Reconcile: create link with optional provider
  Reconcile->>ProviderRoutes: resolve enabled providers
  ProviderRoutes->>OpenCode: write profiles and publish routes
  OpenCode-->>AgentCLI: expose provider-specific layout
Loading

Poem

A rabbit hops through profiles bright,
One provider, or many in flight.
Manifests hide when links depart,
Routes bloom with a careful heart.
Provider pins keep paths just right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: linking all OpenCode main providers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/opencode-main-switch-providers

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.

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.21411% with 32 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/agent-migration/src/reconcile.rs 93.75% 12 Missing and 4 partials ⚠️
crates/agent-migration/src/manifest.rs 68.57% 7 Missing and 4 partials ⚠️
crates/agent-migration/src/status.rs 94.80% 4 Missing ⚠️
crates/gateway-cli/src/cli/agent.rs 87.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
crates/agent-migration/src/reconcile.rs (1)

876-904: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep manifest archiving resumable after partial archive failure.

manifest_chain starts from the selected path and follows previous_manifest, so chain.first() is the latest manifest. The final loops first write unlinked = true for every manifest, then rename every manifest in latest-first order. If manifest::archive_manifest fails after the latest manifest is renamed, a retry re-reads either the archived latest or another unlinked manifest and hits bail!("manifest ... has already been unlinked"). Pair the unlinked write with that manifest’s archive result, or defer marking until the archive step succeeds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent-migration/src/reconcile.rs` around lines 876 - 904, Make
manifest archiving resumable in the unlink flow around manifest_chain and
archive_manifest: do not mark every manifest unlinked before any rename
succeeds. Process each manifest in latest-first order, archive it first, then
persist unlinked = true for that same manifest, while ensuring retries recognize
already archived manifests and continue with remaining ancestors without
triggering the initial already-unlinked bail.
🧹 Nitpick comments (1)
crates/gateway-cli/src/cli/agent.rs (1)

319-357: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an explicit "all effective providers" indicator for unpinned verbatim main-account links.

For an unpinned main-account switch/passthrough binding, binding.provider is None, so the provider: line at line 329-331 is skipped. The provider_filter: line at line 332-342 is also skipped because mode.is_verbatim() is true. The result: agent show/agent list output shows profile_layout: PerProvider but nothing tells the user this means every enabled provider is linked, unlike the provider_filter case which explicitly prints "(all effective providers)" when unset.

Print an explicit line for this case to match the clarity already given to provider_filter.

♻️ Proposed clarity improvement
       if let Some(provider) = binding.provider.as_deref() {
         println!("  provider: {provider}");
+      } else if binding.account_source == AgentAccountSource::Main && binding.mode.is_verbatim() {
+        println!("  provider: (all effective providers)");
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/gateway-cli/src/cli/agent.rs` around lines 319 - 357, Update the
binding display in the status match to explicitly indicate “all effective
providers” when AgentAccountSource::Main uses a verbatim mode and
binding.provider is unset. Add this indicator alongside the existing
provider/provider_filter output, while preserving current output for pinned
providers and non-verbatim bindings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/agent-migration/src/manifest.rs`:
- Around line 76-90: Update archive_manifest to perform the
destination-existence check atomically with the rename, using no-replace rename
semantics such as renameat2 with RENAME_NOREPLACE or an equivalent mechanism.
Remove reliance on the separate archived.exists() check so a concurrently
created destination cannot be overwritten, while preserving the existing error
context and return behavior.
- Around line 83-90: Update the unlink_inner/archive_manifest flow so archiving
cannot leave earlier marked manifests undiscoverable when a later archive fails:
preflight all archive destinations before moving any manifest, or implement
rollback/idempotent recovery for partial failures. Preserve the existing
unlinked marking while ensuring retries can still discover and recover every
source manifest.

---

Outside diff comments:
In `@crates/agent-migration/src/reconcile.rs`:
- Around line 876-904: Make manifest archiving resumable in the unlink flow
around manifest_chain and archive_manifest: do not mark every manifest unlinked
before any rename succeeds. Process each manifest in latest-first order, archive
it first, then persist unlinked = true for that same manifest, while ensuring
retries recognize already archived manifests and continue with remaining
ancestors without triggering the initial already-unlinked bail.

---

Nitpick comments:
In `@crates/gateway-cli/src/cli/agent.rs`:
- Around line 319-357: Update the binding display in the status match to
explicitly indicate “all effective providers” when AgentAccountSource::Main uses
a verbatim mode and binding.provider is unset. Add this indicator alongside the
existing provider/provider_filter output, while preserving current output for
pinned providers and non-verbatim bindings.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 833ac553-8fd1-4218-aa6f-5f02ef13a95b

📥 Commits

Reviewing files that changed from the base of the PR and between 8795330 and 1cdae2d.

📒 Files selected for processing (7)
  • README.md
  • crates/agent-migration/src/manifest.rs
  • crates/agent-migration/src/reconcile.rs
  • crates/agent-migration/src/status.rs
  • crates/config/src/lib.rs
  • crates/gateway-cli/src/cli/agent.rs
  • crates/router/src/api/mod.rs

Comment on lines +76 to +90
if archived.exists() {
bail!(
"cannot archive migration manifest {} because {} already exists",
path.display(),
archived.display()
);
}
std::fs::rename(path, &archived).with_context(|| {
format!(
"archiving migration manifest {} to {}",
path.display(),
archived.display()
)
})?;
Ok(archived)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Expect: every archive destination is protected by the same lock,
# or archive_manifest uses a no-replace operation.
rg -n -C 12 \
  '\barchive_manifest\s*\(|\btry_lock_agent_in\s*\(|\bmanifest_chain\s*\(' \
  crates/agent-migration/src/manifest.rs \
  crates/agent-migration/src/reconcile.rs

Repository: agentic-rs/tokn

Length of output: 14698


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Inspect manifest path helpers and archive implementations, plus lock placement around callers.
sed -n '68,130p' crates/agent-migration/src/manifest.rs
printf '\n--- reconcile archive call site ---\n'
sed -n '863,910p' crates/agent-migration/src/reconcile.rs
printf '\n--- inactive_manifest_path usages ---\n'
rg -n "inactive_manifest_path" crates/agent-migration/src

Repository: agentic-rs/tokn

Length of output: 6275


Make the archive destination check atomic.

archive_manifest checks archived.exists() before calling std::fs::rename, so a destination created after the check can be overwritten. Use atomic no-replace rename semantics such as renameat2(..., RENAME_NOREPLACE), or ensure the same lock covers every possible archive destination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent-migration/src/manifest.rs` around lines 76 - 90, Update
archive_manifest to perform the destination-existence check atomically with the
rename, using no-replace rename semantics such as renameat2 with
RENAME_NOREPLACE or an equivalent mechanism. Remove reliance on the separate
archived.exists() check so a concurrently created destination cannot be
overwritten, while preserving the existing error context and return behavior.

Comment on lines +83 to +90
std::fs::rename(path, &archived).with_context(|| {
format!(
"archiving migration manifest {} to {}",
path.display(),
archived.display()
)
})?;
Ok(archived)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== files =="
git ls-files | rg 'crates/agent-migration/src/manifest.rs|agent-migration' || true

echo "== outline manifest =="
ast-grep outline crates/agent-migration/src/manifest.rs --view expanded || true

echo "== relevant manifest lines =="
wc -l crates/agent-migration/src/manifest.rs
cat -n crates/agent-migration/src/manifest.rs | sed -n '1,160p'

echo "== search related symbols =="
rg -n "fn archive_manifest|archiving migration manifest|fn unlink_inner|unlinked|active|discovery|migration manifest|archived" crates/agent-migration/src -S

Repository: agentic-rs/tokn

Length of output: 28177


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== reconcile manifest archive lines =="
sed -n '863,908p' crates/agent-migration/src/reconcile.rs | cat -n

echo "== success tests around multi-manifest cleanup =="
sed -n '5135,5200p' crates/agent-migration/src/reconcile.rs | cat -n
sed -n '5848,5900p' crates/agent-migration/src/reconcile.rs | cat -n
sed -n '6662,6698p' crates/agent-migration/src/reconcile.rs | cat -n

echo "== test archive failures / coverage search =="
rg -n "archive|unlink_inner|chain|previous_manifest|unlinked|inactive" crates/agent-migration/src/reconcile.rs crates/agent-migration/src -S | rg -n "test|#\\[test\\]|fn .*archive|archive_manifest|rename|rename_failed|archiving|chain::" || true

echo "== static diff =="
git diff -- crates/agent-migration/src/manifest.rs crates/agent-migration/src/reconcile.rs | sed -n '1,240p'

Repository: agentic-rs/tokn

Length of output: 11702


Preserve chain manifests when archiving fails after marking.

unlink_inner writes unlinked = true for every manifest before archiving any of them. If a later archive_manifest call fails, earlier manifests have been moved out of the active namespace while their unlinked state persists, and normal retry cannot discover those source manifests. Preflight archive paths or add rollback/idempotent recovery for partial archive failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent-migration/src/manifest.rs` around lines 83 - 90, Update the
unlink_inner/archive_manifest flow so archiving cannot leave earlier marked
manifests undiscoverable when a later archive fails: preflight all archive
destinations before moving any manifest, or implement rollback/idempotent
recovery for partial failures. Preserve the existing unlinked marking while
ensuring retries can still discover and recover every source manifest.

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.

1 participant