Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ OPENCLAW_TELEGRAM_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
OPENCLAW_ANTHROPIC_API_KEY=sk-ant-api03-your-key-here
# Gateway token for remote mode clients (from kyber: cat ~/.config/openclaw/gateway-token)
OPENCLAW_GATEWAY_TOKEN=your-gateway-token-here
# OpenCode API key for OpenCode Go access via cliproxyapi
# OpenCode API key for OpenCode Go access via cliproxyapi.
# For multiple accounts, set OPENCODE_API_KEYS to a comma-separated list instead;
# it takes precedence over this legacy singular value.
OPENCODE_API_KEY=your-opencode-api-key-here
# OPENCODE_API_KEYS=first-opencode-api-key,second-opencode-api-key
# xAI API key for Grok access via x.ai subscription (used by opencode xai provider)
XAI_API_KEY=xai-your-key-here
# Roborev CI repos (comma-separated)
Expand Down
3 changes: 1 addition & 2 deletions config/cliproxyapi/config.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,7 @@ openai-compatibility:
- name: "opencode"
priority: 300
base-url: "https://opencode.ai/zen/go/v1"
api-key-entries:
- api-key: "__OPENCODE_API_KEY__"
api-key-entries: __OPENCODE_API_KEY_ENTRIES__
models:
- name: "deepseek-v4-pro"
alias: "deepseek-v4-pro"
Expand Down
3 changes: 1 addition & 2 deletions config/cliproxyapi/config.tpl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,7 @@ openai-compatibility:
- name: "opencode"
priority: 300
base-url: "https://opencode.ai/zen/go/v1"
api-key-entries:
- api-key: "__OPENCODE_API_KEY__"
api-key-entries: __OPENCODE_API_KEY_ENTRIES__
models:
- name: "__DEEPSEEK_PRO__"
alias: "__DEEPSEEK_PRO__"
Expand Down
10 changes: 10 additions & 0 deletions home-manager/services/cliproxyapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ QWEN_API_KEY="sk-..."
ALIYUN_TOKEN_PLAN_API_KEY="sk-sp-..."
```

For more than one OpenCode Go account behind the same upstream endpoint, set a
comma-separated credential pool. The plural value takes precedence over the
legacy singular value, and empty or duplicate entries are ignored:

```bash
OPENCODE_API_KEYS="first-opencode-api-key,second-opencode-api-key"
```

CLIProxyAPI selects from these entries using the configured routing strategy.

## Usage

```bash
Expand Down
51 changes: 47 additions & 4 deletions home-manager/services/cliproxyapi/scripts/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,51 @@ MANAGEMENT_PASSWORD="${CLIPROXY_MANAGEMENT_PASSWORD:-}"
MANAGEMENT_KEY="${CLIPROXY_MANAGEMENT_PASSWORD:-${CLIPROXY_MANAGEMENT_KEY:-}}"
export OBJECTSTORE_ENDPOINT OBJECTSTORE_BUCKET OBJECTSTORE_ACCESS_KEY OBJECTSTORE_SECRET_KEY OBJECTSTORE_LOCAL_PATH MANAGEMENT_PASSWORD

render_opencode_api_key_entries() {
local template="$1"
local key_source="${OPENCODE_API_KEYS:-${OPENCODE_API_KEY:-}}"
local candidate existing_key trimmed escaped line
local -a candidates=()
local -a api_keys=()

if [ -n "$key_source" ]; then
IFS=',' read -r -a candidates <<<"$key_source"
fi

for candidate in "${candidates[@]}"; do
trimmed="${candidate#"${candidate%%[![:space:]]*}"}"
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
[ -n "$trimmed" ] || continue

for existing_key in "${api_keys[@]}"; do
if [ "$existing_key" = "$trimmed" ]; then
trimmed=""
break
fi
done
[ -n "$trimmed" ] && api_keys+=("$trimmed")
done

while IFS= read -r line || [ -n "$line" ]; do
if [ "$line" != " api-key-entries: __OPENCODE_API_KEY_ENTRIES__" ]; then
printf '%s\n' "$line"
continue
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.

continue
fi

printf '%s\n' ' api-key-entries:'
for candidate in "${api_keys[@]}"; do
escaped="${candidate//\\/\\\\}"
escaped="${escaped//\"/\\\"}"
printf ' - api-key: "%s"\n' "$escaped"
done
done <"$template"
}

if cliproxy_has_objectstore_credentials; then
mkdir -p "$AUTH_DIR"

Expand All @@ -36,17 +81,15 @@ fi

# Generate config from template
if [ -f "$TEMPLATE" ]; then
@sed@ \
render_opencode_api_key_entries "$TEMPLATE" | @sed@ \
-e "s|__OPENROUTER_API_KEY__|${OPENROUTER_API_KEY:-}|g" \
-e "s|__OPENAI_API_KEY__|${OPENAI_API_KEY:-}|g" \
-e "s|__CLIPROXY_MANAGEMENT_PASSWORD__|${CLIPROXY_MANAGEMENT_PASSWORD:-}|g" \
-e "s|__ZAI_API_KEY__|${ZAI_API_KEY:-}|g" \
-e "s|__QWEN_API_KEY__|${QWEN_API_KEY:-${DASHSCOPE_API_KEY:-}}|g" \
-e "s|__ALIYUN_TOKEN_PLAN_API_KEY__|${ALIYUN_TOKEN_PLAN_API_KEY:-}|g" \
-e "s|__OPENCODE_API_KEY__|${OPENCODE_API_KEY:-}|g" \
-e "s|__AMP_UPSTREAM_API_KEY__|${AMP_UPSTREAM_API_KEY:-}|g" \
-e "s|__OPENCODE_API_KEY__|${OPENCODE_API_KEY:-}|g" \
"$TEMPLATE" >"$CONFIG"
>"$CONFIG"

if [ "$(uname)" = "Linux" ] && [ -n "${CLIPROXY_API_KEY:-}" ]; then
@sed@ -i \
Expand Down
2 changes: 1 addition & 1 deletion home-manager/services/hermes/activate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ for unit in hermes-gateway.service hermes-dashboard.service hermes-dashboard-pro
content="$(cat "$unit_path" 2>/dev/null || true)"
if [ -n "$content" ]; then
rm -f "$unit_path"
printf '%s\n' "$content" > "$unit_path"
printf '%s\n' "$content" >"$unit_path"
chmod 644 "$unit_path"
echo "Replaced symlink $unit with writable copy"
fi
Expand Down
2 changes: 1 addition & 1 deletion spec/cass_indexer_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ HYDRATE="$PWD/home-manager/programs/cass/hydrate.sh"
render() {
rm -rf "$SHELLSPEC_TMPBASE/cass-home"
mkdir -p "$SHELLSPEC_TMPBASE/cass-home"
env -u HOSTNAME HOME="$SHELLSPEC_TMPBASE/cass-home" HOST="$1" bash "$HYDRATE" 2>/dev/null
env -u HOSTNAME -u XDG_CONFIG_HOME HOME="$SHELLSPEC_TMPBASE/cass-home" HOST="$1" bash "$HYDRATE" 2>/dev/null
if [ "$(uname -s)" = "Darwin" ]; then
cat "$SHELLSPEC_TMPBASE/cass-home/Library/Application Support/cass/sources.toml"
else
Expand Down
58 changes: 57 additions & 1 deletion spec/cliproxyapi_spec.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# shellcheck disable=SC2329
# shellcheck disable=SC2329,SC2016

Describe 'cliproxyapi/start.sh'
SCRIPT="$PWD/home-manager/services/cliproxyapi/scripts/start.sh"
Expand Down Expand Up @@ -254,4 +254,60 @@ The status should be success
End
End

Describe 'OpenCode API key pool'
setup_opencode_pool() {
TEMP_POOL=$(mktemp -d)
cat >"$TEMP_POOL/template.yaml" <<'YAML'
openai-compatibility:
- name: "opencode"
api-key-entries: __OPENCODE_API_KEY_ENTRIES__
YAML
sed -n '/^render_opencode_api_key_entries() {/,/^}/p' "$SCRIPT" >"$TEMP_POOL/render.sh"
cat >>"$TEMP_POOL/render.sh" <<'BASH'
render_opencode_api_key_entries "$1"
BASH
}

cleanup_opencode_pool() {
rm -rf "$TEMP_POOL"
}

Before 'setup_opencode_pool'
After 'cleanup_opencode_pool'

It 'declares a generated credential pool for the existing OpenCode endpoint'
When run bash -c "sed -n '/name: \"opencode\"/,/name: \"openai\"/p' '$PWD/config/cliproxyapi/config.template.yaml'"
The output should include 'base-url: "https://opencode.ai/zen/go/v1"'
The output should include '__OPENCODE_API_KEY_ENTRIES__'
The output should not include '__OPENCODE_API_KEY__'
The status should be success
End

It 'renders plural keys with singular fallback without a temporary config'
When run cat "$SCRIPT"
The output should include 'OPENCODE_API_KEYS:-${OPENCODE_API_KEY:-}'
The output should include 'api-key-entries: []'
The output should include 'api_keys+=("$trimmed")'
The output should include 'render_opencode_api_key_entries "$TEMPLATE" | @sed@'
The output should not include 'SED_CONFIG='
The status should be success
End

It 'renders each non-empty plural key once'
When run env OPENCODE_API_KEYS='first-key, second-key,first-key,, third-key ' bash "$TEMP_POOL/render.sh" "$TEMP_POOL/template.yaml"
The output should include ' - api-key: "first-key"'
The output should include ' - api-key: "second-key"'
The output should include ' - api-key: "third-key"'
The output should not include '__OPENCODE_API_KEY_ENTRIES__'
The status should be success
End

It 'falls back to the legacy singular key'
When run env OPENCODE_API_KEYS='' OPENCODE_API_KEY='legacy-key' bash "$TEMP_POOL/render.sh" "$TEMP_POOL/template.yaml"
The output should include ' - api-key: "legacy-key"'
The output should not include 'api-key-entries: []'
The status should be success
End
End

End
1 change: 0 additions & 1 deletion spec/coverage_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@ It 'has spec file for named-hosts/kyber/activate-ip-forwarding.sh'
The path "spec/activate_kyber_spec.sh" should be exist
End


It 'has spec file for home-manager/activation/ensure-tailscale-serve.sh'
The path "spec/tailscale_serve_spec.sh" should be exist
End
Expand Down
Loading