Skip to content

feat(cliproxyapi): support multiple OpenCode API keys - #2331

Merged
shunkakinoki merged 2 commits into
mainfrom
codex/opencode-multiple-api-tokens
Aug 10, 2026
Merged

feat(cliproxyapi): support multiple OpenCode API keys#2331
shunkakinoki merged 2 commits into
mainfrom
codex/opencode-multiple-api-tokens

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • add OPENCODE_API_KEYS as a comma-separated credential pool for the existing OpenCode Go endpoint
  • preserve OPENCODE_API_KEY as the backward-compatible fallback
  • trim empty entries, deduplicate tokens, and safely render YAML-quoted key entries without temporary secret-bearing config files
  • document the setting and cover plural/fallback behavior

Validation

  • shellspec spec/cliproxyapi_spec.sh spec/llm_update_spec.sh (87 examples)
  • make shell-test (2,037 ShellSpec + 454 Fish tests)
  • make shell-lint
  • make build (Galactica Darwin configuration)

Summary by cubic

Adds multi-key support for the OpenCode Go provider in cliproxyapi via OPENCODE_API_KEYS, rendering a safe YAML token pool with no temp secret files. Also repairs shell/format checks to stabilize CI.

  • New Features

    • OPENCODE_API_KEYS supports a comma-separated pool; trims blanks, dedupes, YAML-quotes keys, and renders api-key-entries or []. Takes precedence over legacy OPENCODE_API_KEY.
    • Switched config templates to __OPENCODE_API_KEY_ENTRIES__ for the opencode provider and render inline in start.sh (no temp files).
    • Updated docs and tests; selection uses the configured routing strategy.
  • Bug Fixes

    • Repaired CI shell and format checks.
    • Stabilized specs/scripts (shellcheck suppression, env var handling, minor formatting).

Written for commit 326439e. Summary will update on new commits.

Review in cubic

@indent-zero

indent-zero Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Warning

Indent Zero is shutting down on August 7th. Please migrate over to Indent 2.0 to continue getting PR reviews.

PR Summary

Adds support for configuring multiple OpenCode API keys via a new OPENCODE_API_KEYS (comma-separated, plural) env var that takes precedence over the legacy singular OPENCODE_API_KEY. start.sh renders the opencode provider's api-key-entries: YAML block itself — trimming, deduplicating, and YAML-escaping each key — before piping the result into the existing sed substitution chain. The follow-up commit reshapes the placeholder to api-key-entries: __OPENCODE_API_KEY_ENTRIES__ so the unsubstituted config.example.yaml copy installed by home-manager remains parseable YAML, plus assorted CI/lint cleanup.

  • Introduced __OPENCODE_API_KEY_ENTRIES__ placeholder (now on an api-key-entries: __OPENCODE_API_KEY_ENTRIES__ line) in config.template.yaml and config.tpl.yaml, replacing the prior single-entry sed substitution.
  • Added render_opencode_api_key_entries in home-manager/services/cliproxyapi/scripts/start.sh that expands the placeholder into either api-key-entries: [] or a multi-entry YAML list with per-entry trim, dedup, and backslash/quote escaping; rewired config generation to pipe the rendered template into sed.
  • Documented the new plural env var and precedence rules in .env.example and home-manager/services/cliproxyapi/README.md, added shellspec coverage in spec/cliproxyapi_spec.sh for placeholder shape, plural rendering with dedup/trim, and singular fallback.
  • CI/lint cleanup: minor formatting tweaks in home-manager/services/hermes/activate.sh, spec/cass_indexer_spec.sh (unset XDG_CONFIG_HOME in the render harness), and spec/coverage_spec.sh.

Issues

2 potential issues found:

  • Placeholder line match is still exact-string ( api-key-entries: __OPENCODE_API_KEY_ENTRIES__); any indent drift, extra whitespace, or template rewording in either config template will silently pass the placeholder through into the generated config.yaml and break cliproxyapi startup. → Autofix
  • When both OPENCODE_API_KEYS and OPENCODE_API_KEY are unset, the new code emits api-key-entries: [] instead of the previous single empty-string entry; if cliproxyapi rejects providers with an empty entry list, an unconfigured environment will now fail to load the opencode provider entirely. → Autofix

CI Checks

Waiting for CI checks...


⚡ Autofix All Issues

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for configuring multiple OpenCode Go accounts with comma-separated API keys.
    • Requests can be distributed across configured credentials.
    • Empty and duplicate keys are ignored automatically.
    • The plural API-key setting takes precedence over the legacy single-key setting.
  • Documentation

    • Updated environment and service configuration guidance for multi-account OpenCode support.

Walkthrough

The OpenCode provider now supports multiple comma-separated API keys. Startup rendering trims, deduplicates, escapes, and converts keys into YAML entries, with fallback to the legacy singular variable. Documentation and shell tests cover the behavior.

Changes

OpenCode API-key pool

Layer / File(s) Summary
Key-entry template contract
.env.example, config/cliproxyapi/config.template.yaml, config/cliproxyapi/config.tpl.yaml, home-manager/services/cliproxyapi/README.md
The templates use __OPENCODE_API_KEY_ENTRIES__. Documentation defines plural-key precedence, filtering, and routing behavior.
Key-entry rendering and validation
home-manager/services/cliproxyapi/scripts/start.sh, spec/cliproxyapi_spec.sh
The startup script renders trimmed, deduplicated, escaped YAML entries and falls back to OPENCODE_API_KEY. Tests cover plural keys, fallback, empty values, and placeholder removal.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Environment
  participant render_opencode_api_key_entries
  participant OpenCodeTemplates
  participant CLIProxyAPI
  Environment->>render_opencode_api_key_entries: provide comma-separated API keys
  render_opencode_api_key_entries->>render_opencode_api_key_entries: trim, deduplicate, and escape keys
  render_opencode_api_key_entries->>OpenCodeTemplates: replace __OPENCODE_API_KEY_ENTRIES__
  OpenCodeTemplates->>CLIProxyAPI: generate api-key-entries configuration
Loading

Possibly related PRs

Poem

I’m a rabbit with keys in a row,
Trimmed and unique, ready to go.
Into YAML they hop with delight,
Escaped safely and rendered just right.
Many accounts now share the flow—
CLIProxyAPI knows where to go.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly and concisely identifies the main change: support for multiple OpenCode API keys.
Description check ✅ Passed The description directly explains the multi-key feature, fallback behavior, rendering details, documentation, and validation.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 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-multiple-api-tokens

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.

@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.

🧹 Nitpick comments (1)
spec/cliproxyapi_spec.sh (1)

286-310: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add runtime assertions for the complete key-pool contract.

The test at lines 296-303 passes if first-key occurs more than once. The test suite also does not set both variables to verify that plural keys exclude the legacy key. Add runtime cases for plural precedence, an empty pool, and exact deduplicated output.

🤖 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 `@spec/cliproxyapi_spec.sh` around lines 286 - 310, Extend the runtime key-pool
specs around the plural-key rendering tests to assert exact deduplicated output
rather than merely checking key presence. Add cases verifying non-empty
OPENCODE_API_KEYS takes precedence over OPENCODE_API_KEY, both variables empty
produce an empty pool, and repeated or blank plural entries render exactly once
with no legacy key included; preserve the existing success and placeholder
assertions.
🤖 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.

Nitpick comments:
In `@spec/cliproxyapi_spec.sh`:
- Around line 286-310: Extend the runtime key-pool specs around the plural-key
rendering tests to assert exact deduplicated output rather than merely checking
key presence. Add cases verifying non-empty OPENCODE_API_KEYS takes precedence
over OPENCODE_API_KEY, both variables empty produce an empty pool, and repeated
or blank plural entries render exactly once with no legacy key included;
preserve the existing success and placeholder assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 126aa59e-d6db-4a80-bb4f-5ce8bf1a6f05

📥 Commits

Reviewing files that changed from the base of the PR and between 624ec5a and 7547726.

📒 Files selected for processing (6)
  • .env.example
  • config/cliproxyapi/config.template.yaml
  • config/cliproxyapi/config.tpl.yaml
  • home-manager/services/cliproxyapi/README.md
  • home-manager/services/cliproxyapi/scripts/start.sh
  • spec/cliproxyapi_spec.sh

done

while IFS= read -r line || [ -n "$line" ]; do
if [ "$line" != " __OPENCODE_API_KEY_ENTRIES__" ]; then

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.

Brittle placeholder matching: [ "$line" != " __OPENCODE_API_KEY_ENTRIES__" ] requires exactly 4 leading spaces and no trailing whitespace. If someone edits config.template.yaml/config.tpl.yaml and changes the indent (or an editor adds trailing whitespace), the placeholder passes through untouched and ends up in the rendered config.yaml, producing invalid YAML that cliproxyapi will refuse to load. Consider matching with a regex that tolerates surrounding whitespace, e.g. if [[ "$line" =~ ^[[:space:]]*__OPENCODE_API_KEY_ENTRIES__[[:space:]]*$ ]], and preserve the captured leading indent when emitting the block.

fi

if [ "${#api_keys[@]}" -eq 0 ]; then
printf '%s\n' ' api-key-entries: []'

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.

Empty-pool behavior change: Previously, if OPENCODE_API_KEY was unset, the sed substitution rendered - api-key: "" — cliproxyapi still saw one (bogus) entry and loaded the provider. With this change, an unconfigured environment now yields api-key-entries: []. If cliproxyapi requires ≥1 entry per provider, the opencode provider will fail to load entirely, which is a silent regression. Worth confirming against the cliproxyapi config validator; if it does reject empty lists, either preserve the empty-string fallback or skip the provider block when no keys are configured.

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="config/cliproxyapi/config.template.yaml">

<violation number="1" location="config/cliproxyapi/config.template.yaml:137">
P3: This file is installed twice by config/cliproxyapi/default.nix — as the render template AND as `~/.cli-proxy-api/config.example.yaml` consumed by CLIProxy's object-backed config bootstrap. The new placeholder line is only expanded by `render_opencode_api_key_entries` in start.sh when producing config.yaml; in the example copy it is never substituted, so the opencode provider there silently loses its `api-key-entries` block (replaced by a literal `__OPENCODE_API_KEY_ENTRIES__` null key) where previously it carried an `api-key-entries` entry. Consider rendering/expanding the placeholder from the shared source, or giving the example config its own opencode `api-key-entries` block.</violation>
</file>

<file name="home-manager/services/cliproxyapi/scripts/start.sh">

<violation number="1" location="home-manager/services/cliproxyapi/scripts/start.sh:45">
P3: The placeholder match is a hardcoded, indentation-sensitive literal (`    __OPENCODE_API_KEY_ENTRIES__`) that must stay byte-for-byte in sync between start.sh and both config template files. If the template indentation is ever adjusted, the placeholder will silently pass through into the generated config.yaml instead of being replaced, and the service will ship an invalid/placeholder config without any failure being raised at generation time. A small guard (e.g., echo a warning and abort if the placeholder remains in the output) would make this coupling self-checking.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread config/cliproxyapi/config.template.yaml Outdated
base-url: "https://opencode.ai/zen/go/v1"
api-key-entries:
- api-key: "__OPENCODE_API_KEY__"
__OPENCODE_API_KEY_ENTRIES__

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.

P3: This file is installed twice by config/cliproxyapi/default.nix — as the render template AND as ~/.cli-proxy-api/config.example.yaml consumed by CLIProxy's object-backed config bootstrap. The new placeholder line is only expanded by render_opencode_api_key_entries in start.sh when producing config.yaml; in the example copy it is never substituted, so the opencode provider there silently loses its api-key-entries block (replaced by a literal __OPENCODE_API_KEY_ENTRIES__ null key) where previously it carried an api-key-entries entry. Consider rendering/expanding the placeholder from the shared source, or giving the example config its own opencode api-key-entries block.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At config/cliproxyapi/config.template.yaml, line 137:

<comment>This file is installed twice by config/cliproxyapi/default.nix — as the render template AND as `~/.cli-proxy-api/config.example.yaml` consumed by CLIProxy's object-backed config bootstrap. The new placeholder line is only expanded by `render_opencode_api_key_entries` in start.sh when producing config.yaml; in the example copy it is never substituted, so the opencode provider there silently loses its `api-key-entries` block (replaced by a literal `__OPENCODE_API_KEY_ENTRIES__` null key) where previously it carried an `api-key-entries` entry. Consider rendering/expanding the placeholder from the shared source, or giving the example config its own opencode `api-key-entries` block.</comment>

<file context>
@@ -134,8 +134,7 @@ openai-compatibility:
     base-url: "https://opencode.ai/zen/go/v1"
-    api-key-entries:
-      - api-key: "__OPENCODE_API_KEY__"
+    __OPENCODE_API_KEY_ENTRIES__
     models:
       - name: "deepseek-v4-pro"
</file context>

done

while IFS= read -r line || [ -n "$line" ]; do
if [ "$line" != " __OPENCODE_API_KEY_ENTRIES__" ]; then

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.

P3: The placeholder match is a hardcoded, indentation-sensitive literal ( __OPENCODE_API_KEY_ENTRIES__) that must stay byte-for-byte in sync between start.sh and both config template files. If the template indentation is ever adjusted, the placeholder will silently pass through into the generated config.yaml instead of being replaced, and the service will ship an invalid/placeholder config without any failure being raised at generation time. A small guard (e.g., echo a warning and abort if the placeholder remains in the output) would make this coupling self-checking.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/services/cliproxyapi/scripts/start.sh, line 45:

<comment>The placeholder match is a hardcoded, indentation-sensitive literal (`    __OPENCODE_API_KEY_ENTRIES__`) that must stay byte-for-byte in sync between start.sh and both config template files. If the template indentation is ever adjusted, the placeholder will silently pass through into the generated config.yaml instead of being replaced, and the service will ship an invalid/placeholder config without any failure being raised at generation time. A small guard (e.g., echo a warning and abort if the placeholder remains in the output) would make this coupling self-checking.</comment>

<file context>
@@ -16,6 +16,51 @@ MANAGEMENT_PASSWORD="${CLIPROXY_MANAGEMENT_PASSWORD:-}"
+  done
+
+  while IFS= read -r line || [ -n "$line" ]; do
+    if [ "$line" != "    __OPENCODE_API_KEY_ENTRIES__" ]; then
+      printf '%s\n' "$line"
+      continue
</file context>

@shunkakinoki
shunkakinoki merged commit cf0e5a6 into main Aug 10, 2026
32 of 33 checks passed
@shunkakinoki
shunkakinoki deleted the codex/opencode-multiple-api-tokens branch August 10, 2026 03:55
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