Skip to content

🔒 fix(sidecar): Retry-After 退避恢复 + prompt 假抽象拆除 + link/wiki 校验统一 zod - #468

Merged
CavinHuang merged 5 commits into
mainfrom
sidecar-fixes-422-294-292
Aug 23, 2026
Merged

🔒 fix(sidecar): Retry-After 退避恢复 + prompt 假抽象拆除 + link/wiki 校验统一 zod#468
CavinHuang merged 5 commits into
mainfrom
sidecar-fixes-422-294-292

Conversation

@CavinHuang

Copy link
Copy Markdown
Owner

三个独立 sidecar issue 的批量修复,互不相干、分主题提交。

🐛 #422 Retry-After 挂载点迁移至 sidecar 传输层

对账发现 issue 前提已部分过时:err.retryAfterMs 生产端其实早已在 pi-ai-provider 落地(onResponse 捕获响应头 → PiAiProviderError.retryAfterMs),但存在两处收尾欠账:

  • parseRetryAfterHeader / computeRetryDelay / MAX_RETRY_AFTER_DELAY_MS(120s 硬上限)此前不在 @lume/agent-sdk 导出面上,sidecar 无法按 issue 建议直接复用 → 本 PR 补齐导出
  • pi-ai-provider 内残留一份手写解析实现(delta-seconds 与 HTTP-date 双格式),与 SDK 版本构成双实现漂移风险 → 删除本地重复,改为一行委托共享实现

验收格式覆盖(delta-seconds / HTTP-date / 缺失回退 / clamp 120s)已由 packages/sdk/src/utils/retry.test.ts 既有用例钉死,本 PR 全量跑绿。

🔥 #294 prompt 组装器假抽象残留

全仓 grep 复核确认零消费后删除:

  • section-composer.tscomposePromptSections / renderPromptSection 与其测试文件(仅自身测试引用)
  • prompt/types.tsPromptSection / PromptSectionMode(唯一消费者就是上述文件)
  • context-budget.tsDEFAULT_CONTEXT_BUDGET(0.25/0.15/0.20… 比例只被展开进 trace 元数据)

context-assembler 保留指纹 trace;budget 元数据收敛为真实的 { total: tokenBudget },测试断言面(.budget.total)不变。

♻️ #292 RPC 校验双轨制

issue 点名的 link-handlers.ts / wiki-handlers.ts 已分别随 Link 与 Wiki 功能整体移除删除,双轨制的原始载体不复存在。复核发现残存的手写校验只剩 channel 家族(同文件内 5 个 handler 用 zod、7 个仍手写 asObject/asString,正是 issue 描述的漂移现场):

  • 新增 channelIdParamsSchema / connectionIdParamsSchema / oauthSessionIdParamsSchema / oauthAnswerParamsSchema / oauthCancelParamsSchema(与相邻 channel schema 同款 .strict() 风格)
  • channel 全部 handler 迁入 validateInput,删除手写校验分支
  • 行为等价:必填 id 空/缺拒绝、OAUTH_ANSWER.value 可选、OAUTH_CANCEL 缺 sessionId 保持静默 no-op;新增 schemas.channel-params.test.ts 钉死边界 case

验证

  • bun run typecheck 六包全绿
  • 定向测试 44 pass / 0 fail:sdk retry、pi-ai-provider、context-assembler、channel params schemas、create-rpc-handlers 装配回归

Fixes #422
Fixes #294
Fixes #292

🤖 Generated with Claude Code

@CavinHuang
CavinHuang force-pushed the sidecar-fixes-422-294-292 branch from 289f691 to da41db0 Compare August 22, 2026 17:32
TaTaLiao and others added 3 commits August 23, 2026 01:37
parseRetryAfterHeader/computeRetryDelay/120s 硬上限补进 @lume/agent-sdk 导出面;pi-ai-provider 删除本地重复解析,改用共享实现(delta-seconds 与 HTTP-date 双格式行为不变,SDK retry.test.ts 已覆盖验收格式与 clamp)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
composePromptSections/PromptSection/PromptSectionMode 全仓仅自测试引用,连同 context-budget.ts 的 DEFAULT_CONTEXT_BUDGET 一并删除;context-assembler 保留指纹 trace,budget 元数据收敛为真实 tokenBudget 总额。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
link/wiki 两家族已随功能移除删除(#418/#432),残存的手写 asObject/asString 校验仅剩 channel 家族:DECRYPT_KEY/TEST/SYNC_MODELS/OAUTH_* 全部迁入 schemas.ts 严格 schema,接受/拒绝面与迁移前一致(OAUTH_CANCEL 缺 sessionId 仍为静默 no-op),新增边界 case 单测。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@CavinHuang
CavinHuang force-pushed the sidecar-fixes-422-294-292 branch from da41db0 to 796bb2c Compare August 22, 2026 17:38
@CavinHuang

Copy link
Copy Markdown
Owner Author

Review

结论:✅ 可合。三个主题逐一实证,无阻塞缺陷;1 条 P2 为验收口径缺口(main 既有、非本 PR 引入),建议随合或 follow-up 表态。


发现

[P2] #422 验收口径未闭环:sidecar 实际 clamp 是 30s,非 issue 写明的 120s

  • issue [P2][sdk/providers] Retry-After 挂载点需随 provider 移除迁移至 sidecar 传输层 #422 验收标准原文:「限流响应下重试间隔采用服务端指示值(clamp 120s)」。但 sidecar 消费链路 resolvePiAiRetryDelayMs 用的是自有常量 MAX_RETRY_AFTER_MS = 30_000,且 pi-ai-provider.test.ts 已显式钉死 retryAfterMs: 90_000 → 30_000
  • 本 PR 补导出的 computeRetryDelay / MAX_RETRY_AFTER_DELAY_MS(120s) sidecar 并未复用——只 import 了 parseRetryAfterHeader。现状是「导出了没人用 + 30s/120s 两套 cap 并存」,恰是 issue 反对的实现漂移换了个形式延续。
  • 建议二选一:a) resolvePiAiRetryDelayMs 改用 MAX_RETRY_AFTER_DELAY_MS 统一到 120s(同步更新钉 30s 的那条测试);b) 保留 30s 并在 [P2][sdk/providers] Retry-After 挂载点需随 provider 移除迁移至 sidecar 传输层 #422 记录偏离理由(长 Retry-After 截短以更快重试是有意取舍)。
  • 注:该偏差 main 既有,本 PR 未使其恶化,不阻塞合并;但 PR 描述中「120s 硬上限」的表述容易让 reviewer 误以为 sidecar 也享受此上限。

[P3] OAUTH_CANCEL / OAUTH_ANSWER 对空串与 null 的行为收紧

{sessionId: ""} 原为静默 no-op,现被 idSchema.min(1) 拒绝抛错;value: null 同理(原 asString 吞成 "",现 zod 拒绝)。已核对全仓唯一调用方 apps/web/src/lib/desktop-api/channel.ts——全部传非空字面量,现网零命中;新方向 fail-loud 也更有利排错。仅记录语义变化,新增边界测试已覆盖。

[P3] computeRetryDelay / MAX_RETRY_AFTER_DELAY_MS 当前无生产消费者

与上面 P2 同根:若采纳统一方案即自然获得消费方;若保留 30s,建议撤掉这两个导出、只留 parseRetryAfterHeader,避免导出面虚胖。


已核实

#422 生产链路真实(非假修复)

  • onResponse 回调捕获 response.headersPiAiProviderError 构造两处(stream error / stream ended without terminal event)均挂 retryAfterMsresolvePiAiRetryDelayMsMath.max(jittered, retryAfter) 消费。header 在可见层获取并使用。
  • 一行委托语义逐行等价:数值分支 Math.max(0, s*1000)Math.max(s,0)*1000;date 分支 Number.isFinite(Date.parse(...))!Number.isNaN(...)
  • 测试面属实:packages/sdk/src/utils/retry.test.ts 既有用例端到端断言了 withRetry 采用 err.retryAfterMs(baseDelay 30s 场景下 retryAfterMs=5ms 总耗时 <5s),另有 delta-seconds / HTTP-date / 垃圾值 undefined / computeRetryDelay clamp 120s——并非只测 parse 函数本身。

sdk 导出面改动最小

  • index.ts 纯加 3 行导出,无内部改动;withRetry/computeRetryDelay 内部接线不变;CI 全绿。

#294 删除完整性

  • composePromptSections / renderPromptSection / PromptSection / PromptSectionMode 全仓出现位置均在三个被删文件内;DEFAULT_CONTEXT_BUDGET / ContextBudget 仅 context-assembler 一处外部引用(本 PR 已改)。ContextBudgetSnapshot / ContextBudgetRuntimeSnapshot 是同名前缀的独立类型,不受波及。
  • budget 收敛为 { total } 后:context-assembler.test.ts 的 .budget.total 断言形状不变;trace 目录与 desktop 侧均无 budget 展开字段的解析消费方,trace 指纹保留成立。

#292 基于当前 main 形态 + 校验等价面

  • link-handlers.ts / wiki-handlers.ts 在 origin/main rpc 目录已不存在(随功能移除删除),diff 无旧路径引用,issue 前提过时的对账成立;残存双轨制确在 channel 家族(5 zod + 7 手写)。
  • channel 全部 9 个 handler 迁入 validateInput,手写 asObject/asString import 清除;5 个新 schema 与相邻 schema 的 .strict() 风格一致。
  • 逐个调用方核对(web channel.ts 全文 + desktop main.ts vault 转发):test / sync-models / oauth-start / oauth-status / decrypt-key 均为单字段精确字面量;oauth-answer 三字段且 web 端 value 必填 string(schema optional 更宽松,安全)——.strict() 拒绝额外键对现网零命中。必填 id 空/缺两侧均为报错路径,等价。
  • 边界测试覆盖:缺失 / 空串 / 非 string / 额外键 / value 可选 / cancel 缺 sessionId no-op,钉死了迁移后的接受/拒绝面。

流程与合规

  • 3 commit ↔ 3 issue 一一对应,划分干净;10 文件全部落在三主题内,无 scope creep;文案无竞品表述;CI 4/4 通过。

…除双 cap 并存

依据 issue #422 验收口径:clamp 120s,与 sdk withRetry 的 MAX_RETRY_AFTER_DELAY_MS 一致。
@CavinHuang

Copy link
Copy Markdown
Owner Author

Retry-After clamp 口径已统一为 120s:sidecar 删除自有 MAX_RETRY_AFTER_MS = 30_000resolvePiAiRetryDelayMs 与 pi-ai stream 的 maxRetryDelayMs 均改用本 PR 从 sdk 导出的 MAX_RETRY_AFTER_DELAY_MS(120s),导出从此有真实消费者,双 cap 并存消除。

依据 = issue #422 验收口径(clamp 120s,与 sdk withRetry 一致)。

测试同步更新:retryAfterMs: 90_000 现透传 90_000(≤120s 不 clamp),新增 300_000 → 120_000 clamp 用例。computeRetryDelay 本体未引入 sidecar——其语义是「Retry-After 或指数退避二选一」,而 sidecar 需保留自有 jitter 阶梯并取 max(jitter, retryAfter),故只复用 cap 常量、不动其余退避行为。

六包 typecheck 绿;pi-ai-provider + sdk retry 定向测试绿。

@CavinHuang
CavinHuang merged commit 0e0e7d6 into main Aug 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants