Skip to content

fix(search): Cindy AI 搜索把余额耗尽(429 + ExceededBudget)误报为限流,改用共享额度判定并给充值引导 - #4026

Merged
MagicLizi merged 1 commit into
makecindy:mainfrom
fico-hub:fix/4024-search-quota-exhausted
Sep 7, 2026
Merged

fix(search): Cindy AI 搜索把余额耗尽(429 + ExceededBudget)误报为限流,改用共享额度判定并给充值引导#4026
MagicLizi merged 1 commit into
makecindy:mainfrom
fico-hub:fix/4024-search-quota-exhausted

Conversation

@fico-hub

@fico-hub fico-hub commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

这次改了什么

摘要

Cindy AI 余额耗尽时,Web Search 插件的搜索调用被归类为 RATE_LIMITED,用户看到「请求过于频繁,请稍后再试」——但实际是余额为 0,按提示等待永远不会恢复,也不指向充值;同一客户端的对话链路对同一网关错误却能正确显示「余额不足,请充值」。

根因:apps/desktop/src/main/mcp-integrations/cindyProxySearch.tsclassifyHttpFailure 自维护了一份宽松额度正则(quota|credit|balance|insufficient|exhausted|spend limit),而网关预算闸的实际响应形态是 HTTP 429 + ExceededBudget(仓内 errorRedaction.test.ts / terminal-rate-limit-retry.test.ts 均以此为样本),正则无一命中,于是落入 status === 429 分支。

本 PR 让搜索链路复用仓库已有的 SSoT matchesDeterministicUsageExhaustionText(@cindy/maker-shared/error-redaction,对话 Error Banner、终端限流重试、providerErrors 已在用):HTTP 429 只认该严格信号(ExceededBudget / budget_exceeded 等)→ QUOTA_EXHAUSTED;非 429 状态保留原宽松措辞与 402;普通 429(rate limit / Too Many Requests,即便正文带 credits refill 之类措辞)仍为 RATE_LIMITED。额度耗尽文案改为「Cindy AI 余额不足,请充值后再试,或在插件设置中改用自己的搜索渠道」。非 2xx 的拒绝日志新增 bodyDigest:经 redactSensitiveText 脱敏(凭证、aigw: principal)后压成单行并截断到 160 字,只进本机诊断日志,不回传插件/用户。

变更类型

  • feat 新功能
  • fix 缺陷修复
  • refactor / perf 重构或性能优化
  • docs / test / chore 文档、测试或工程维护
  • 其他:

范围

  • 关联 Issue / 需求:Fixes bug: Cindy AI 搜索把「余额耗尽」误报为「请求过于频繁」——cindyProxySearch 未复用共享额度判定 #4024
  • 本 PR 包含:cindyProxySearch.ts 的分类顺序与文案、脱敏响应体摘要日志;cindyProxySearch.test.ts 新增/扩展用例。
  • 明确不包含:网关共享层与服务端;Web Search 插件协议(不新增可点击「查看余额」入口,插件仍只收 errorCode + message,是否新增入口按 dash-s-cindy 的意见留作单独产品/API 决策);cindySlot 的 binding 消费与重试语义(未触碰)。
  • 用户可见变化:余额耗尽时搜索返回 QUOTA_EXHAUSTED 与「余额不足,请充值后再试」提示,不再误导为限流。
  • 是否存在 breaking change:无。CindyProxySearchErrorCode 枚举不变,仅 429 + 明确额度信号的归类从 RATE_LIMITED 改为既有的 QUOTA_EXHAUSTED

UI 变化

  • 引用的设计规范:不涉及:仅主进程 mcp-integrations 的错误分类、文案字符串与日志,无 renderer、布局、样式改动;提示文案经插件原有 message 通道展示,不新增界面元素。

怎么验证的

自动验证

pnpm --dir apps/desktop exec vitest run src/main/mcp-integrations/__tests__/cindyProxySearch.test.ts
结果:9/9 通过(新增:429+ExceededBudget → QUOTA_EXHAUSTED;429+budget_exceeded → QUOTA_EXHAUSTED;
429+rate limit / Too Many Requests / "rate limit exceeded, credits refill in 60s" → RATE_LIMITED;
402+insufficient balance → QUOTA_EXHAUSTED;401/403/404/503 既有归类不变;
余额耗尽用例断言文案含「余额不足」「充值」,日志 bodyDigest 含 ExceededBudget、
aigw:[REDACTED],不含原始 principal / sk- 密钥 / query / Authorization,长度 ≤ 161)。

反证:把 cindyProxySearch.ts 回退到 main、只保留新测试 → 2 个用例失败
(429+ExceededBudget 被归为 RATE_LIMITED;日志无 bodyDigest),恢复后全部通过。

pnpm --dir apps/desktop run typecheck:0 错误
pnpm test:unit:related(仓库根):PASS(apps/desktop unit)
eslint / prettier / git diff --check:通过

手工验证

未在真实余额为 0 的账号上端到端复测(本机账号有余额)。分类逻辑以 issue 中的真实网关日志形态(429、无 requestId、百毫秒级延迟)与仓内 ExceededBudget 测试样本为准。

未执行的验证

  • 真实网关余额为 0 的端到端复测(需要一个余额耗尽的账号;提报人 issue 中已给出 45/45 次对照数据)。
  • Web Search 插件侧展示新文案的目检(插件只透传 message,无 UI 改动)。

风险

风险分类

  • 无已知风险
  • SQLite / migration
  • system prompt
  • 协议兼容
  • 权限 / 安全 / 用户数据
  • 存量插件兼容(批准状态 / 指纹 / manifest 校验 / 安装布局 / 包格式)
  • 原生层 / fingerprint / OTA
  • 跨平台差异
  • 其他:错误分类边界

影响与回滚

  • 影响范围:仅 Cindy AI 托管搜索(cindy/web-search)的 HTTP 非 2xx 分类与拒绝日志;Brave / Tavily BYO 路径不经此函数。
  • 误报/漏报面:429 分支现在只认共享严格信号——正文不带 ExceededBudget/budget_exceeded/insufficient quota 等明确措辞的额度拒绝仍会归为限流(与修复前一致,未变差);带明确措辞的瞬时限流理论上可能被判为额度耗尽,但共享判定的注释与既有消费方(Error Banner / 终端重试)已按此口径运行,风险与它们同级。
  • 日志:bodyDigest 经共享脱敏后截断,与 mediaRequestLog 等既有用法同一函数;不含 query(query 不在响应体中)与 Authorization。
  • 回滚 / 降级方式:revert 本 PR 单个 commit 即可,无数据、协议或配置变更。

@greptile-apps

greptile-apps Bot commented Sep 7, 2026

Copy link
Copy Markdown

Greptile Summary

本 PR 修正 Cindy AI 托管搜索对“HTTP 429 + 明确预算耗尽信号”的分类,并为余额不足提供充值或切换搜索渠道的提示。

  • 复用共享的确定性额度耗尽文本判定器,区分预算耗尽和普通瞬时限流。
  • 为相关分类边界及用户提示增加测试。
  • 在非成功响应日志中加入脱敏、定长的响应体摘要,但任意正文日志仍存在未识别敏感内容落盘的风险。

Confidence Score: 4/5

该修复的错误分类和透传行为看起来可以合并,但建议先将诊断日志改为只记录允许名单中的结构化字段,避免任意上游正文落盘。

429 的分类边界、文案透传和重试语义均有代码与测试支持;剩余问题是新增日志会持久化通用脱敏器无法识别的响应内容,属于可独立修正的隐私加固问题。

Files Needing Attention: apps/desktop/src/main/mcp-integrations/cindyProxySearch.ts

Important Files Changed

Filename Overview
apps/desktop/src/main/mcp-integrations/cindyProxySearch.ts 调整 429 额度耗尽分类和提示文案,并新增响应体诊断摘要;摘要记录任意上游正文带来非标准敏感内容落盘风险。
apps/desktop/src/main/mcp-integrations/tests/cindyProxySearch.test.ts 覆盖预算耗尽、普通限流、充值文案以及已知 principal/密钥格式的日志脱敏与长度限制。

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[搜索上游返回非 2xx] --> B{HTTP 状态}
  B -->|401 / 403| C[AUTH_REJECTED]
  B -->|429| D{正文命中确定性额度耗尽信号?}
  D -->|是| E[QUOTA_EXHAUSTED<br/>提示充值或切换渠道]
  D -->|否| F[RATE_LIMITED<br/>提示稍后重试]
  B -->|其他状态| G{402 或宽松额度措辞?}
  G -->|是| E
  G -->|否| H[按状态映射其他错误]
  A --> I[生成脱敏且最长 160 字符的 bodyDigest]
  I --> J[写入本机诊断日志]
Loading
Prompt To Fix All With AI
### Issue 1
apps/desktop/src/main/mcp-integrations/cindyProxySearch.ts:431
**响应正文可能写入日志**

如果上游错误响应回显用户查询、使用非标准字段名携带凭证,或包含其他敏感内容,通用脱敏器可能无法识别这些数据。此处会将它们原样保留在 `bodyDigest` 的前 160 个字符中,并写入本机日志。当前测试只覆盖 `aigw:` principal 和 `sk-` 密钥;建议仅记录允许名单中的结构化诊断字段,例如错误码、`spend``budget`,而不是记录任意响应正文。

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(search): Cindy AI 搜索把余额耗尽(429 + Exce..." | Re-trigger Greptile

Comment thread apps/desktop/src/main/mcp-integrations/cindyProxySearch.ts
@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) labels Sep 7, 2026

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

安全与隐私门未通过:发现疑似真实 API key(类型 sk-api-key),不能合入。

请处理:

  1. apps/desktop/src/main/mcp-integrations/__tests__/cindyProxySearch.test.ts:364sk-api-key
  2. apps/desktop/src/main/mcp-integrations/__tests__/cindyProxySearch.test.ts:398sk-api-key

要求:

  • 从分支历史中彻底移除这些内容(只追加一个删除 commit 不够,泄露内容仍留在可达历史里)
  • 立即轮换已出现在远端的凭证——推到 GitHub 即视为已泄露
  • 测试请改用明显占位符 / mock,不要再放真实或仿真可调用的 key

本条为 P0,修完并清理历史后再请求审查。

…引导 (makecindy#4024)

cindyProxySearch.classifyHttpFailure 自维护了一份宽松额度正则,而网关预算闸
实际以 HTTP 429 + ExceededBudget 拒绝,正则无一命中,落入 429 分支归为
RATE_LIMITED,用户被引导「稍后再试」但永远不会恢复。

改为复用 @cindy/maker-shared 的 matchesDeterministicUsageExhaustionText
(与对话 Error Banner / 终端限流重试同一 SSoT):429 只认严格额度信号,
非 429 保留原宽松措辞;额度耗尽文案明确「余额不足,请充值后再试」并保留
BYO 渠道提示。

拒绝日志新增允许名单式的结构化正文摘要(review):只从 JSON 正文抽取短标识
形态的 error/code/type 与数值 spend/budget,其余只记 json/length;非 JSON
正文只记长度,不落盘任意上游正文。测试用的凭证占位符不使用密钥形态。

Fixes makecindy#4024

Signed-off-by: ficowang <fico@xd.com>
@fico-hub
fico-hub force-pushed the fix/4024-search-quota-exhausted branch from 98f7d9d to 5cd1ddb Compare September 7, 2026 04:56
@fico-hub

fico-hub commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

@MagicLizi 安全门已处理(head 5cd1ddb):

  • 该字符串不是真实凭证。 sk-should-never-be-logged-000000 是测试里人为构造的占位符(用来断言凭证不进日志),从未对应任何可调用的 key,因此没有可轮换的凭证;仍按门的要求当作密钥形态处理。
  • 测试改用非密钥形态的占位符:placeholder-credential-must-not-be-logged,断言逻辑不变(整条日志不得含它)。分支内已无任何 sk-/pk-/rk- 形态字符串(git log -p origin/main..HEAD 扫描为 0)。
  • 历史已清理:分支重写为单个 commit 5cd1ddb(squash 原 85b36ea + 98f7d9d),--force-with-lease 推送;原两个 commit 已不在分支可达历史中(GitHub 会在 PR 时间线保留 force-push 事件记录,这一层不由我方控制)。
  • 内容与此前一致:429 只认共享严格额度信号 → QUOTA_EXHAUSTED + 充值文案;拒绝日志为允许名单式结构化摘要(Greptile P2 已按建议改)。测试 10/10,eslint/prettier 通过。

请重新跑安全门与审查。

@MagicLizi
MagicLizi dismissed their stale review September 7, 2026 05:52

这些问题已在当前 head 的后续 commit 中修复(格式门/安全门已通过),自动 dismiss 旧的 CHANGES_REQUESTED 以解除合并阻塞。

@MagicLizi MagicLizi removed the awaiting-discussion 等待维护者讨论(review-pr) label Sep 7, 2026

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

代码重审通过:当前 head 未发现 P0/P1。429+ExceededBudget 按共享额度判定为余额耗尽并给充值引导,普通限流不升级;日志只记允许名单摘要。

@MagicLizi
MagicLizi merged commit 25d08fc into makecindy:main Sep 7, 2026
10 checks passed
@MagicLizi

Copy link
Copy Markdown
Contributor

网关把余额耗尽和瞬时限流挤在同一个 429 里,这次终于拆开了:该充值的会看到充值,该等一会儿的不会被赶去付钱。日志也只留允许名单,不把查询和身份写进去。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Cindy AI 搜索把「余额耗尽」误报为「请求过于频繁」——cindyProxySearch 未复用共享额度判定

2 participants