feat(deploy,runtime): add OpenRouter/opencode LLM provider support#241
Conversation
Bare model names like `deepseek-v4-flash-free` were treated as provider identifiers by deriveModelProvider, causing deploy to fail with "provider is required" when the managed-credentials endpoint couldn't normalize them. Now bare model names (no `/` or `:` separator) fall back to the harness-derived provider — `opencode` → `openrouter`. Also adds OPENROUTER_API_KEY credential support to the runtime's ctx.llm so opencode personas can call ctx.llm.complete() at run time. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
Warning Review limit reached
More reviews will be available in 27 minutes and 8 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds OpenRouter as a new LLM provider family. The runtime ChangesOpenRouter Provider Family
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 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.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 11dceba118
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (/[/:]/.test(model)) { | ||
| const [provider] = model.split(/[/:]/, 1); | ||
| if (provider?.trim()) return provider.trim().toLowerCase(); |
There was a problem hiding this comment.
Map opencode fallback to OpenRouter
With harness: 'opencode' and a bare model such as deepseek-v4-flash-free, this new guard skips prefix extraction and then falls through to return harnessFallback, which is opencode rather than the provider key openrouter. The rest of this file treats openrouter as the model provider and opencode only as its alias, so OAuth probes or BYOK/plan credential selections keyed as openrouter won't match these bare-model opencode personas; map the harness fallback through the provider alias before returning.
Useful? React with 👍 / 👎.
| ): boolean { | ||
| if (!personaFamily) return false; | ||
| if (credentialFamily === 'anthropic') return personaFamily === 'anthropic'; | ||
| if (credentialFamily === 'openrouter') return personaFamily === 'openrouter'; |
There was a problem hiding this comment.
Preserve provider-qualified opencode model IDs
When a persona uses the documented/built-in shape opencode/gpt-5-nano with OPENROUTER_API_KEY, this added match makes resolveModel take the generic prefix-stripping path above and send body.model = 'gpt-5-nano' to OpenRouter. OpenRouter model IDs are provider-qualified, and the repo's opencode personas store that full provider/model string, so these runtime calls will fail until the OpenRouter path preserves or explicitly translates the original model id.
Useful? React with 👍 / 👎.
…vider Known harnesses (opencode→openrouter, codex→openai, claude→anthropic, grok→xai) now resolve the provider before falling through to model-string heuristics. This avoids bare model names like "deepseek-v4-flash-free" being treated as provider identifiers — the harness is the explicit source of truth for which credential provider to use. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@packages/deploy/src/modes/cloud/index.ts`:
- Around line 979-981: The provider extraction logic in the function does not
verify that a separator was actually found in the model string before using the
split result. When model.split(/[/:]/, 1) is called on a model without `/` or
`:` separators (like `deepseek-v4-flash-free`), it returns the entire model
string as the provider, causing bare model names to be incorrectly treated as
provider IDs. Fix this by first checking that the model string actually contains
a `/` or `:` separator before attempting to extract the provider. If the model
contains no separator, skip the provider extraction logic and go directly to the
fallback that returns harness or `anthropic`.
In `@packages/runtime/src/cloud-llm.ts`:
- Around line 195-200: The model provider classification logic checks for
generic patterns before explicit prefixes, causing models like openrouter/gpt-4o
to be misclassified as openai because they contain 'gpt-'. Move the openrouter
and opencode prefix checks (checking for startsWith('openrouter/') and
startsWith('opencode/')) to execute before the generic openai pattern check
(checking includes('gpt-')), ensuring explicit provider prefixes take precedence
over substring matching.
🪄 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: 49fd99d8-2489-4642-a10a-02c6a4054043
📒 Files selected for processing (4)
packages/deploy/src/modes/cloud.test.tspackages/deploy/src/modes/cloud/index.tspackages/runtime/src/cloud-llm.test.tspackages/runtime/src/cloud-llm.ts
The harness-to-provider mapping, credential env var, and model family derivation all treat opencode as a first-class provider identity. Runtime env var is OPENCODE_API_KEY (not OPENROUTER_API_KEY). OpenRouter remains as a separate provider for explicit openrouter/ model prefixes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
deriveModelProvidernow only extracts a provider prefix when the model string contains an explicit separator (/or:). Bare model names likedeepseek-v4-flash-freefall back to the harness-derived provider (opencode→openrouter), fixing the "provider is required" error from the managed-credentials endpoint.OPENROUTER_API_KEYas a credential source andopenrouterLlmfactory soctx.llm.complete()works for opencode/openrouter personas at run time.Context
Deploying the
daytona-monitoragent withharness: 'opencode'andmodel: 'deepseek-v4-flash-free'failed becausederiveModelProviderreturned the entire model string as a provider, whichnormalizeModelProvidercouldn't handle.Test plan
🤖 Generated with Claude Code