Skip to content

feat(deploy,runtime): add OpenRouter/opencode LLM provider support#241

Merged
khaliqgant merged 3 commits into
mainfrom
fix/openrouter-deploy-and-runtime
Jun 16, 2026
Merged

feat(deploy,runtime): add OpenRouter/opencode LLM provider support#241
khaliqgant merged 3 commits into
mainfrom
fix/openrouter-deploy-and-runtime

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jun 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Deploy fix: deriveModelProvider now only extracts a provider prefix when the model string contains an explicit separator (/ or :). Bare model names like deepseek-v4-flash-free fall back to the harness-derived provider (opencodeopenrouter), fixing the "provider is required" error from the managed-credentials endpoint.
  • Runtime support: Adds OPENROUTER_API_KEY as a credential source and openrouterLlm factory so ctx.llm.complete() works for opencode/openrouter personas at run time.
  • Test coverage: 2 new deploy tests (bare model fallback + explicit separator), 2 new runtime tests (OpenRouter routing + preference over other credentials).

Context

Deploying the daytona-monitor agent with harness: 'opencode' and model: 'deepseek-v4-flash-free' failed because deriveModelProvider returned the entire model string as a provider, which normalizeModelProvider couldn't handle.

Test plan

  • Deploy package tests pass (206/206)
  • Runtime package tests pass (113/113)
  • Redeploy daytona-monitor with opencode harness and verify no "provider is required" error

🤖 Generated with Claude Code

Review in cubic

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>
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@khaliqgant, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans 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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7b13e343-5374-4bdf-8f0d-499e407ba051

📥 Commits

Reviewing files that changed from the base of the PR and between 3e5c391 and 5844f9c.

📒 Files selected for processing (4)
  • packages/deploy/src/modes/cloud.test.ts
  • packages/deploy/src/modes/cloud/index.ts
  • packages/runtime/src/cloud-llm.test.ts
  • packages/runtime/src/cloud-llm.ts
📝 Walkthrough

Walkthrough

Adds OpenRouter as a new LLM provider family. The runtime cloud-llm.ts gains openrouterLlm, credential plumbing for OPENROUTER_API_KEY, and persona classification for openrouter/ and opencode/ prefixes. The deploy deriveModelProvider gains a HARNESS_TO_PROVIDER lookup table that maps harness aliases (including opencodeopenrouter) to canonical providers for BYOK credential flows.

Changes

OpenRouter Provider Family

Layer / File(s) Summary
OpenRouter types, constants, and credential plumbing
packages/runtime/src/cloud-llm.ts
Adds OPENROUTER_BASE_URL, extends provider family type unions to include openrouter, reads OPENROUTER_API_KEY as a credential candidate with Authorization: Bearer, extends preferredCredential and credentialMatchesPersonaFamily for openrouter, classifies openrouter/ and opencode/ personas via personaModelFamily, and updates resolveModel to use the persona model for the openrouter family.
openrouterLlm implementation and createDefaultLlm wiring
packages/runtime/src/cloud-llm.ts, packages/runtime/src/cloud-llm.test.ts
Wires createDefaultLlm to dispatch to openrouterLlm for openrouter-family credentials. Implements openrouterLlm as a POST to /v1/chat/completions extracting choices[0].message.content. Tests verify routing to the OpenRouter endpoint and that OPENROUTER_API_KEY takes precedence over ANTHROPIC_API_KEY for opencode personas.
Deploy deriveModelProvider harness mapping and BYOK tests
packages/deploy/src/modes/cloud/index.ts, packages/deploy/src/modes/cloud.test.ts
Adds HARNESS_TO_PROVIDER lookup (opencodeopenrouter, claudeanthropic, codexopenai, grokxai) and rewrites deriveModelProvider to return immediately on a known mapping before falling back to model-string derivation. Updates the BYOK test to expect openai for a bare harness and adds a test verifying opencode harness resolves to openrouter in the BYOK POST body.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • AgentWorkforce/workforce#228: Adds characterization tests for the BYOK POST body/response contract in packages/deploy/src/modes/cloud/index.ts, the same flow this PR modifies by changing how modelProvider is derived.
  • AgentWorkforce/workforce#236: Extends deriveModelProvider/harnessAliasForModelProvider in packages/deploy/src/modes/cloud/index.ts to map grok/xai-family personas, directly preceding the HARNESS_TO_PROVIDER table introduced in this PR.

Poem

🐇 A new route through the router appears,
opencode maps to openrouter's gears.
The harness lookup table now complete,
OPENROUTER_API_KEY takes its seat.
With Bearer tokens and choices to parse,
the rabbit hops forward, fast and sparse! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Title check ✅ Passed The title accurately summarizes the main change: adding OpenRouter/opencode LLM provider support across deploy and runtime packages.
Description check ✅ Passed The description is directly related to the changeset, detailing the deploy fix, runtime support, test coverage, and context for the OpenRouter/opencode provider additions.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/openrouter-deploy-and-runtime

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

❤️ Share

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment on lines +973 to +975
if (/[/:]/.test(model)) {
const [provider] = model.split(/[/:]/, 1);
if (provider?.trim()) return provider.trim().toLowerCase();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread packages/runtime/src/cloud-llm.ts Outdated
): boolean {
if (!personaFamily) return false;
if (credentialFamily === 'anthropic') return personaFamily === 'anthropic';
if (credentialFamily === 'openrouter') return personaFamily === 'openrouter';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5836135 and 3e5c391.

📒 Files selected for processing (4)
  • packages/deploy/src/modes/cloud.test.ts
  • packages/deploy/src/modes/cloud/index.ts
  • packages/runtime/src/cloud-llm.test.ts
  • packages/runtime/src/cloud-llm.ts

Comment thread packages/deploy/src/modes/cloud/index.ts
Comment thread packages/runtime/src/cloud-llm.ts Outdated
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>
@khaliqgant khaliqgant merged commit 48d2b26 into main Jun 16, 2026
3 checks passed
@khaliqgant khaliqgant deleted the fix/openrouter-deploy-and-runtime branch June 16, 2026 22:49
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