Skip to content

fix: 修复 OpenRouter 令牌余额查询 403 错误#4950

Open
one-ccs wants to merge 1 commit into
QuantumNous:mainfrom
one-ccs:fix/openrouter-balance-403
Open

fix: 修复 OpenRouter 令牌余额查询 403 错误#4950
one-ccs wants to merge 1 commit into
QuantumNous:mainfrom
one-ccs:fix/openrouter-balance-403

Conversation

@one-ccs
Copy link
Copy Markdown

@one-ccs one-ccs commented May 18, 2026

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

默认 /api/v1/credits 接口需要管理秘钥才能获取余额,403 错误时降级使用 /api/v1/key 让普通秘钥也能正确获取余额。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

失败截图:
image

成功截图:
image

Summary by CodeRabbit

  • Improvements
    • Enhanced OpenRouter balance retrieval with improved reliability. The system now includes a fallback mechanism: if the primary endpoint for account credits is unavailable, it automatically retrieves balance and credit limit information from an alternative source, ensuring continuous and uninterrupted access to billing data.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Walkthrough

This PR enhances OpenRouter balance fetching by introducing a fallback mechanism. A new OpenRouterKeyResponse struct is added to parse the /api/v1/key endpoint response. The updateChannelOpenRouterBalance function now attempts to fetch credits from /api/v1/credits first; if that fails, it falls back to /api/v1/key and uses the remaining limit to compute the channel balance.

Changes

OpenRouter Balance Fallback

Layer / File(s) Summary
OpenRouter response type definition
controller/channel-billing.go
OpenRouterKeyResponse struct is added to unmarshal the JSON payload from the OpenRouter /api/v1/key endpoint, exposing Data.LimitRemaining and Data.TotalUsage fields.
Balance update with fallback logic
controller/channel-billing.go
updateChannelOpenRouterBalance is reworked to try /api/v1/credits first and compute balance as TotalCredits - TotalUsage; if that fails, it falls back to /api/v1/key, unmarshals to OpenRouterKeyResponse, and uses LimitRemaining as the balance. Both paths update the channel balance and return early on success.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant updateChannelOpenRouterBalance
  participant CreditsEndpoint as /api/v1/credits
  participant KeyEndpoint as /api/v1/key
  participant ChannelStorage as Channel Balance Storage

  Caller->>updateChannelOpenRouterBalance: fetch OpenRouter balance
  updateChannelOpenRouterBalance->>CreditsEndpoint: GET with auth header
  alt credits request succeeds
    CreditsEndpoint-->>updateChannelOpenRouterBalance: credits JSON (TotalCredits, TotalUsage)
    updateChannelOpenRouterBalance->>updateChannelOpenRouterBalance: balance = TotalCredits - TotalUsage
    updateChannelOpenRouterBalance->>ChannelStorage: update channel balance
    ChannelStorage-->>updateChannelOpenRouterBalance: success
    updateChannelOpenRouterBalance-->>Caller: return balance, nil
  else credits request fails
    CreditsEndpoint-->>updateChannelOpenRouterBalance: error
    updateChannelOpenRouterBalance->>KeyEndpoint: GET /api/v1/key with auth header
    KeyEndpoint-->>updateChannelOpenRouterBalance: key JSON (LimitRemaining)
    updateChannelOpenRouterBalance->>updateChannelOpenRouterBalance: balance = LimitRemaining
    updateChannelOpenRouterBalance->>ChannelStorage: update channel balance
    ChannelStorage-->>updateChannelOpenRouterBalance: success
    updateChannelOpenRouterBalance-->>Caller: return balance, nil
  else both requests fail
    updateChannelOpenRouterBalance-->>Caller: return 0, error
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested reviewers

  • seefs001

Poem

🐰 A fallback path so wise and true,
When credits fade, the key breaks through,
Two routes to balance, tried then tried,
With LimitRemaining as your guide,
OpenRouter's limits now supplied!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 clearly describes the main fix: resolving a 403 error in OpenRouter token balance queries by implementing a fallback mechanism.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 `@controller/channel-billing.go`:
- Around line 323-336: The new OpenRouter code is calling json.Unmarshal
directly; replace those calls with the repo wrapper common.Unmarshal to comply
with the JSON-wrapper rule. In channel-billing.go, locate the json.Unmarshal
usages around the OpenRouterKeyResponse and other OpenRouter response parsing
(the block that follows GetResponseBody and the earlier response parsing that
sets balance and calls channel.UpdateBalance) and change them to
common.Unmarshal(body, &response); ensure the common package is imported if
missing and remove any direct encoding/json usage in these business paths.
- Around line 320-345: The current code falls back to GET /api/v1/key for any
error from GetResponseBody("GET", url, channel, authHeader) for /credits; change
the logic so the fallback to the /key path only happens when the /credits
request failed with HTTP 403 (forbidden). To do this, modify GetResponseBody to
return the HTTP status (or an error type that exposes status) alongside body and
err (e.g., GetResponseBody -> (body []byte, status int, err error)) and update
this function call in this routine to inspect the returned status; only when
status == 403 call the /api/v1/key flow (unmarshal into OpenRouterKeyResponse
and call channel.UpdateBalance), otherwise return the original error
immediately. Ensure references: GetResponseBody, OpenRouterCreditResponse,
OpenRouterKeyResponse, and channel.UpdateBalance are updated accordingly.
🪄 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

Run ID: 69521a98-b625-47ce-9832-ca2b95f3180e

📥 Commits

Reviewing files that changed from the base of the PR and between 5dd0d3b and 2603db4.

📒 Files selected for processing (1)
  • controller/channel-billing.go

Comment on lines +320 to 345
body, err := GetResponseBody("GET", url, channel, authHeader)
if err == nil {
response := OpenRouterCreditResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
balance := response.Data.TotalCredits - response.Data.TotalUsage
channel.UpdateBalance(balance)
return balance, nil
}
response := OpenRouterCreditResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err

url = "https://openrouter.ai/api/v1/key"
body, err = GetResponseBody("GET", url, channel, authHeader)
if err == nil {
response := OpenRouterKeyResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
balance := response.Data.LimitRemaining
channel.UpdateBalance(balance)
return balance, nil
}
balance := response.Data.TotalCredits - response.Data.TotalUsage
channel.UpdateBalance(balance)
return balance, nil
return 0, err
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restrict fallback to 403 instead of all /credits errors.

Current logic falls back to /api/v1/key for any /credits failure. That can hide non-auth failures (network/5xx/contract), making balance updates silently succeed under the wrong path. Gate fallback to 403 and return other errors directly.

OpenRouter API docs: for GET /api/v1/credits and GET /api/v1/key, what status codes are returned for normal API keys vs admin keys, and is 403 the expected code for non-admin access to /credits?
🤖 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 `@controller/channel-billing.go` around lines 320 - 345, The current code falls
back to GET /api/v1/key for any error from GetResponseBody("GET", url, channel,
authHeader) for /credits; change the logic so the fallback to the /key path only
happens when the /credits request failed with HTTP 403 (forbidden). To do this,
modify GetResponseBody to return the HTTP status (or an error type that exposes
status) alongside body and err (e.g., GetResponseBody -> (body []byte, status
int, err error)) and update this function call in this routine to inspect the
returned status; only when status == 403 call the /api/v1/key flow (unmarshal
into OpenRouterKeyResponse and call channel.UpdateBalance), otherwise return the
original error immediately. Ensure references: GetResponseBody,
OpenRouterCreditResponse, OpenRouterKeyResponse, and channel.UpdateBalance are
updated accordingly.

Comment on lines +323 to +336
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
balance := response.Data.TotalCredits - response.Data.TotalUsage
channel.UpdateBalance(balance)
return balance, nil
}
response := OpenRouterCreditResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err

url = "https://openrouter.ai/api/v1/key"
body, err = GetResponseBody("GET", url, channel, authHeader)
if err == nil {
response := OpenRouterKeyResponse{}
err = json.Unmarshal(body, &response)
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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use common.Unmarshal instead of json.Unmarshal in the new OpenRouter paths.

The new changed lines directly call json.Unmarshal, which violates the repo JSON-wrapper rule for Go business code.

Suggested patch
-		err = json.Unmarshal(body, &response)
+		err = common.Unmarshal(body, &response)
 		if err != nil {
 			return 0, err
 		}
@@
-		err = json.Unmarshal(body, &response)
+		err = common.Unmarshal(body, &response)
 		if err != nil {
 			return 0, err
 		}

As per coding guidelines: "**/*.go: All JSON marshal/unmarshal operations MUST use wrapper functions from common/json.go... Do NOT directly import or call encoding/json in business code."

🤖 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 `@controller/channel-billing.go` around lines 323 - 336, The new OpenRouter
code is calling json.Unmarshal directly; replace those calls with the repo
wrapper common.Unmarshal to comply with the JSON-wrapper rule. In
channel-billing.go, locate the json.Unmarshal usages around the
OpenRouterKeyResponse and other OpenRouter response parsing (the block that
follows GetResponseBody and the earlier response parsing that sets balance and
calls channel.UpdateBalance) and change them to common.Unmarshal(body,
&response); ensure the common package is imported if missing and remove any
direct encoding/json usage in these business paths.

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