Skip to content

feat(proxy): add bounded streaming discovery - #1348

Merged
ding113 merged 31 commits into
ding113:integration/discovery-stack-20260723from
Brisbanehuang:codex/discovery-pr2
Jul 23, 2026
Merged

feat(proxy): add bounded streaming discovery#1348
ding113 merged 31 commits into
ding113:integration/discovery-stack-20260723from
Brisbanehuang:codex/discovery-pr2

Conversation

@Brisbanehuang

@Brisbanehuang Brisbanehuang commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

This is PR 2/3 for #1340 and is stacked on #1347. It adds a default-off bounded streaming Discovery path beside the existing Hedge path.

  • Cold starts launch the configured normal candidate batch immediately; healthy Sticky requests remain single-provider.
  • Priority gates allow lower-priority attempts to prepare but prevent them from bypassing a higher tier still inside its SLA.
  • Later rounds keep at most one fallback and fill the remaining concurrency slots with new candidates. The total racing deadline ends at winner commit and never truncates the committed long stream.
  • Sticky timeouts use generation-aware clear/cooldown; late fallback success can rescue only the current request and cannot recreate Sticky.
  • Protocol-aware incremental parsers reject metadata-only, fake-200, error, empty, and malformed prefixes before winner commit.
  • Discovery losers are force-cancelled and never enter legacy loser drain/billing.
  • Lease conflicts use an explicit single-active-upstream mode with binding writes disabled.
  • Stream finalization creates or renews Sticky only after a normal completion marker, successful fake-200 validation, lease ownership verification, and generation CAS.
  • Structured observability records attempts, active concurrency, rounds, provider time, fallback promotion, cancellation failures, winner origin, TTFT, lease conflicts, and CAS conflicts.

The existing Hedge path remains unchanged when Discovery is disabled, the protocol/endpoint is ineligible, or the Redis capability gate is unavailable.

Validation

  • Discovery/binding/finalizer focused regression: 250 tests passed.
  • Real loopback lifecycle: winner selection, loser socket cancellation, lease/CAS handoff, and agent cleanup passed.
  • bun run typecheck
  • bun run format:check
  • bun run build

Stack

Refs #1340.

Greptile Summary

This PR adds a bounded streaming Discovery path that races multiple providers in parallel behind a default-off feature flag, sitting beside the existing Hedge path without altering its behavior. The core is a pure state machine (DiscoveryCoordinator) and an incremental validity parser (DiscoveryValidityParser) that validates SSE/JSON prefixes before committing a winner.

  • New coordinator (discovery-coordinator.ts): pure state machine with priority gating, round management, epoch-validated events, and sticky-probe to fallback demotion.
  • New parser (discovery-validity.ts): protocol-aware incremental SSE/JSON parser that rejects error, fake-200, empty, and malformed prefixes, with a 1 MiB byte cap and 1 024-event cap.
  • Forwarder changes (forwarder.ts): adds prepareStreamingDiscovery (lease acquisition, rollout gate) and sendStreamingWithDiscovery (the main race loop, sticky SLA timer, round timer, total deadline, lease transfer, provider-session-ref tracking).
  • Response-handler changes (response-handler.ts): adds startDiscoveryLeaseLifecycle (renewal + heartbeat during stream finalization), CAS-based binding on success, finalizeFailedDiscoveryBinding in all failure paths, and a stricter format-aware hasStreamCompletionMarker replacing the old regex approach.

Confidence Score: 5/5

Safe to merge — the Discovery path is default-off, the existing Hedge path is untouched, and the new code is well-guarded with epoch validation, idempotent settlement, and a rollout-percent gate.

All findings are non-blocking style and observability nits. The coordinator state machine is epoch-validated and covers all edge cases (sticky demotion, priority gating, round boundaries, total deadline, client abort). The validity parser correctly handles byte/event limits. The response-handler failure paths consistently call finalizeFailedDiscoveryBinding, closing the gap flagged in the previous review cycle.

No files require special attention. The three changed core files (forwarder, response-handler, coordinator) each have unit/integration test coverage added in this PR.

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/discovery-coordinator.ts New pure state machine for bounded provider discovery; well-isolated with no network/timer dependencies. Minor: redundant this.state = "DISCOVERY_RACING" writes after beginRound() calls in onRoundBoundary and finishOrLaunch.
src/app/v1/_lib/proxy/discovery-validity.ts New incremental SSE/JSON validity parser with byte and event count caps. Minor: new TextEncoder() is allocated on every string-chunk push; using new Blob([chunk]).size is a drop-in improvement.
src/app/v1/_lib/proxy/forwarder.ts Large addition of prepareStreamingDiscovery and sendStreamingWithDiscovery. Logic is well-guarded: sticky probe, round timer, total deadline, epoch validation, noMoreCandidates handling, and lease transfer on winner commit all appear correct.
src/app/v1/_lib/proxy/response-handler.ts Adds lease lifecycle management, CAS-based binding on success, and finalizeFailedDiscoveryBinding covering all failure paths (completion-marker missing, fake-200, non-2xx, stream-abort). hasStreamCompletionMarker refactored from regex to format-aware SSE parsing.
src/lib/observability/discovery-metrics.ts New structured metrics class. ttfbMs field is set to elapsedMs at winner-commit time, which is closer to "time to winner selection" than true TTFB from the client's perspective.
src/app/v1/_lib/proxy/session.ts Extends ProxySession with sessionBindingSnapshot, sessionBindingAllowed, streamingHedgeDisabled flags, and converts providerSessionRefs from a Set to a Map supporting retainOnSuccess metadata. Changes are additive and backward-compatible.
src/app/v1/_lib/proxy/provider-selector.ts Adds pickDiscoveryProviders static method and resolveEffectivePriorityForSession helper; replaces clearSessionProvider calls with the generation-aware clearRejectedProviderBinding closure to avoid ABA on stale binding clears.
src/app/v1/_lib/proxy/stream-finalization.ts Adds DeferredStreamingDiscoveryLease type and Discovery-specific fields (bindingIntent, discoveryLease, providerSessionRefOwned, etc.) to DeferredStreamingFinalization. Clean additive change.

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant F as Forwarder
    participant DC as DiscoveryCoordinator
    participant P1 as Provider (Sticky)
    participant P2 as Provider (Normal)
    participant RH as ResponseHandler
    participant SM as SessionManager

    C->>F: streaming request
    F->>SM: acquireSessionDiscoveryLease
    SM-->>F: lease (ownerToken)
    F->>DC: new DiscoveryCoordinator
    F->>DC: startStickyProbe()
    F->>P1: launch(stickyProvider, normal)
    note over F: stickyTimer = stickySlaMs

    alt Sticky responds before SLA
        P1-->>F: "valid chunk ready=true"
        F->>DC: markReady(sticky.id)
        DC-->>F: commit_normal
        F->>RH: commit(winner, lease)
        RH->>SM: renewSessionDiscoveryLease
        RH->>SM: compareAndSetSessionProvider CAS
        RH->>SM: releaseSessionDiscoveryLease
    else Sticky SLA timeout
        note over F: stickyTimer fires
        F->>DC: demoteToFallback(sticky.id)
        F->>DC: startDiscoveryAfterSticky()
        F->>SM: clearVersionedSessionProvider cooldown
        F->>P2: launch(discoveryProvider, normal)
        note over F: roundTimer = discoverySlaMs

        alt Normal wins
            P2-->>F: "valid chunk ready=true"
            F->>DC: markReady(normal.id)
            DC-->>F: commit_normal
            F->>P1: cancel discovery_sla_timeout
            F->>RH: commit(winner, lease)
            RH->>SM: "compareAndSetSessionProvider bindingIntent=create"
        else Round boundary
            note over F: roundTimer fires
            F->>DC: onRoundBoundary()
            DC-->>F: launch next round
            F->>P2: cancel losers
        else Total deadline
            F->>DC: onDeadline()
            DC-->>F: terminal_failure or promote_fallback
        end
    end
    F-->>C: Response stream
Loading

Reviews (12): Last reviewed commit: "Merge remote-tracking branch 'origin/int..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

新增有界流式 Discovery 状态机、跨协议有效性解析器及租约生命周期,并将流式代理接入候选竞速与 fallback。会话 provider 绑定迁移至 Redis 版本化 canonical/legacy 双镜像、CAS、清理和终止流程,同时补充配置、观测及测试覆盖。

Changes

发现与代理编排

Layer / File(s) Summary
发现状态机与有效性解析
src/app/v1/_lib/proxy/discovery-*.ts, tests/unit/proxy/discovery-*.test.ts
新增按优先级、轮次和 epoch 管理尝试的状态机,并解析多种流式协议的 ready、terminal 和 error 事件。
Proxy Discovery 流程
src/app/v1/_lib/proxy/forwarder.ts, src/app/v1/_lib/proxy/provider-selector.ts, src/app/v1/_lib/proxy/session.ts
流式请求可进入有界 Discovery,按候选、SLA 和 fallback 规则完成赢家提交、取消、租约及 provider 引用处理。
流式最终化
src/app/v1/_lib/proxy/response-handler.ts, src/app/v1/_lib/proxy/stream-finalization.ts
增加 completion marker 检测、缺失标记失败结算、绑定 CAS/清理和 Discovery lease 释放。

版本化 Session Binding

Layer / File(s) Summary
Redis 绑定原语
src/lib/redis/lua-scripts.ts, src/lib/redis/session-binding.ts, src/lib/redis/client.ts
新增双镜像读重整、CAS、touch、clear、terminate、租约、能力探测和安全 legacy mutation。
SessionManager 适配
src/lib/session-manager.ts, src/lib/session-tracker.ts, src/app/v1/_lib/proxy/provider-selector.ts
provider 绑定、复用、刷新、Codex cache 和终止流程改用 keyId、快照与 generation CAS,并按能力状态执行 legacy fallback。
配置、观测与测试
src/types/system-config.ts, src/lib/config/*, src/lib/observability/*, tests/configs/*, tests/unit/*, tests/integration/*
新增 Discovery 配置、灰度变量、请求指标、覆盖率入口、Redis 集成测试及绑定清理和生命周期断言。

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: ding113

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.19% 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
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 标题准确概括了本次新增受限流式 Discovery 路径的主要变更。
Description check ✅ Passed 描述与变更内容一致,清楚说明了 Discovery、租约、绑定和验证范围。
✨ 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.

@coderabbitai
coderabbitai Bot requested a review from ding113 July 20, 2026 18:07

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a bounded streaming discovery path alongside the legacy hedging mechanism, adding a pure state machine coordinator, protocol-specific validity chunk parsers, and versioned session bindings in Redis. The review highlights several critical issues, including a potential deadlock in the discovery coordinator when fallback attempts become ready after normal attempts fail, an O(N^2) performance bottleneck in stream chunk parsing, and a potential runtime TypeError due to missing optional chaining on the request message. Additionally, the feedback points out unhandled promise rejections in background provider launches and a regression in session clearing when the API key owner is missing, recommending a fire-and-forget approach for non-critical Redis operations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/app/v1/_lib/proxy/discovery-coordinator.ts
Comment thread src/app/v1/_lib/proxy/discovery-validity.ts
Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated
Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated
Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated
Comment thread src/lib/session-manager.ts

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

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 `@src/app/v1/_lib/proxy/discovery-coordinator.ts`:
- Around line 148-213: 统一 forwarder.ts 与 DiscoveryCoordinator 的 discovery
边界状态机,避免两处分别处理轮次推进、fallback 晋升和终止失败;优先让 forwarder 的边界与截止路径调用
DiscoveryCoordinator.onRoundBoundary、onDeadline 及相关取消 API,并移除重复决策逻辑,或删除生产路径未使用的
onRoundBoundary、onDeadline、cancelRequest 等协调器接口。确保所有状态转换和 action 生成只由单一实现负责。

In `@src/app/v1/_lib/proxy/forwarder.ts`:
- Around line 5099-5130: 在处理 attempt.reader.read() 返回值的循环中,确保 commit(attempt)
完成接管后立即停止当前读取流程,避免继续将数据写入已快照的 attempt.chunks;在 response 读取逻辑和 commit
函数之间增加接管状态检查,或在接管时取消/转移 reader,并保留正常提交前的读取行为。
🪄 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: 990b50fe-7f18-4da1-9a86-db4b79068e76

📥 Commits

Reviewing files that changed from the base of the PR and between 30bdda8 and a5aeccf.

📒 Files selected for processing (31)
  • package.json
  • src/app/v1/_lib/proxy/discovery-coordinator.ts
  • src/app/v1/_lib/proxy/discovery-validity.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/provider-selector.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/session.ts
  • src/app/v1/_lib/proxy/stream-finalization.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/redis/client.ts
  • src/lib/redis/lua-scripts.ts
  • src/lib/redis/session-binding.ts
  • src/lib/session-manager.ts
  • src/lib/session-tracker.ts
  • src/types/system-config.ts
  • tests/configs/integration.config.ts
  • tests/configs/session-binding.config.ts
  • tests/integration/session-binding-versioning-redis.test.ts
  • tests/unit/lib/redis/client.test.ts
  • tests/unit/lib/redis/session-binding.test.ts
  • tests/unit/lib/session-manager-binding-smart.test.ts
  • tests/unit/lib/session-manager-terminate-provider-sessions.test.ts
  • tests/unit/lib/session-manager-terminate-session.test.ts
  • tests/unit/lib/session-manager-versioned-binding.test.ts
  • tests/unit/lib/session-tracker-cleanup.test.ts
  • tests/unit/proxy/discovery-coordinator.test.ts
  • tests/unit/proxy/discovery-validity.test.ts
  • tests/unit/proxy/provider-selector-cross-type-model.test.ts
  • tests/unit/proxy/provider-selector-model-mismatch-binding.test.ts
  • tests/unit/proxy/proxy-forwarder-hedge-first-byte.test.ts
  • tests/unit/proxy/response-handler-endpoint-circuit-isolation.test.ts

Comment thread src/app/v1/_lib/proxy/discovery-coordinator.ts
Comment thread src/app/v1/_lib/proxy/forwarder.ts

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/app/v1/_lib/proxy/discovery-coordinator.ts (3)

244-253: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

保持 fallback 晋升后的内部状态一致。

commitWinner 提交的 attempt 类型为 fallback 时,应将内部状态置为 FALLBACK_ACTIVE,以与 markReadyonRoundBoundary 中的状态变迁语义保持一致。这也确保了它能与修正后的 isTerminal 属性配合正常工作。

修复建议
  commitWinner(id: string): DiscoveryAction {
    const attempt = this.attempts.get(id);
    if (!attempt || this.isTerminal) return { type: "none" };
    attempt.pending = false;
-   this.state = "WINNER_COMMITTED";
+   this.state = attempt.kind === "fallback" ? "FALLBACK_ACTIVE" : "WINNER_COMMITTED";
    return {
      type: attempt.kind === "fallback" ? "promote_fallback" : "commit_normal",
      attemptId: id,
    };
  }
🤖 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 `@src/app/v1/_lib/proxy/discovery-coordinator.ts` around lines 244 - 253,
Update commitWinner so fallback attempts set the coordinator state to
FALLBACK_ACTIVE, while non-fallback attempts retain WINNER_COMMITTED. Keep the
existing promote_fallback/commit_normal action selection and attempt handling
unchanged.

274-283: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

修正新轮次启动时的槽位计算。

finishOrLaunch 仅在当前没有任何活动的尝试(既无 pending normal,也无 fallback)时被调用。既然没有保留 fallback 占用并发槽位,新轮次应当可以使用完整的 this.concurrency 槽位,而不是 this.concurrency - 1

修复建议
  private finishOrLaunch(): DiscoveryAction {
    if (this.round >= this.maxRounds) {
      this.state = "TERMINAL_FAILED";
      return { type: "terminal_failure" };
    }
    this.round += 1;
    this.roundEpoch += 1;
    this.state = "DISCOVERY_RACING";
-   return { type: "launch", slots: Math.max(1, this.concurrency - 1) };
+   return { type: "launch", slots: this.concurrency };
  }
🤖 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 `@src/app/v1/_lib/proxy/discovery-coordinator.ts` around lines 274 - 283, 更新
DiscoveryCoordinator.finishOrLaunch 中新轮次的 launch 动作,使 slots 使用完整的
this.concurrency,而不是 this.concurrency - 1;保留其余轮次递增、状态切换和终止失败逻辑不变。

85-87: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

FALLBACK_ACTIVE 纳入终止状态。

FALLBACK_ACTIVE 表示 fallback 已经胜出并晋升,此时该请求的协调器应当处于终止状态。由于当前 isTerminal 未包含此状态,一旦脱离外部调用方的严格防重入保护,状态机可能会在 fallback 晋升后错误地继续处理其他延迟到达的事件,甚至导致状态被意外重置。

修复建议
-  get isTerminal(): boolean {
-    return this.state === "WINNER_COMMITTED" || this.state === "TERMINAL_FAILED";
-  }
+  get isTerminal(): boolean {
+    return (
+      this.state === "WINNER_COMMITTED" ||
+      this.state === "FALLBACK_ACTIVE" ||
+      this.state === "TERMINAL_FAILED"
+    );
+  }
🤖 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 `@src/app/v1/_lib/proxy/discovery-coordinator.ts` around lines 85 - 87, Update
the isTerminal getter in the coordinator state machine to include
FALLBACK_ACTIVE alongside WINNER_COMMITTED and TERMINAL_FAILED, so fallback
promotion is treated as terminal and later events cannot continue processing or
reset state.
🧹 Nitpick comments (1)
src/app/v1/_lib/proxy/discovery-coordinator.ts (1)

155-156: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

简化最佳优先级的筛选逻辑。

readyNormal 数组在此前已经按照 compareAttempts 进行了排序,因此 readyNormal[0] 必然是当前具有最高优先级的最佳候选尝试,无需再次通过 filter 遍历数组。

重构建议
-   const sameTier = readyNormal.filter((attempt) => attempt.priority === bestPriority);
-   const winner = sameTier[0];
+   const winner = readyNormal[0];
🤖 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 `@src/app/v1/_lib/proxy/discovery-coordinator.ts` around lines 155 - 156,
简化最佳候选选择逻辑:在发现协调流程中移除围绕 sameTier 的 filter 操作,直接使用已按 compareAttempts 排序的
readyNormal[0] 作为 winner,并保持后续对 winner 的处理不变。
🤖 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.

Outside diff comments:
In `@src/app/v1/_lib/proxy/discovery-coordinator.ts`:
- Around line 244-253: Update commitWinner so fallback attempts set the
coordinator state to FALLBACK_ACTIVE, while non-fallback attempts retain
WINNER_COMMITTED. Keep the existing promote_fallback/commit_normal action
selection and attempt handling unchanged.
- Around line 274-283: 更新 DiscoveryCoordinator.finishOrLaunch 中新轮次的 launch 动作,使
slots 使用完整的 this.concurrency,而不是 this.concurrency - 1;保留其余轮次递增、状态切换和终止失败逻辑不变。
- Around line 85-87: Update the isTerminal getter in the coordinator state
machine to include FALLBACK_ACTIVE alongside WINNER_COMMITTED and
TERMINAL_FAILED, so fallback promotion is treated as terminal and later events
cannot continue processing or reset state.

---

Nitpick comments:
In `@src/app/v1/_lib/proxy/discovery-coordinator.ts`:
- Around line 155-156: 简化最佳候选选择逻辑:在发现协调流程中移除围绕 sameTier 的 filter 操作,直接使用已按
compareAttempts 排序的 readyNormal[0] 作为 winner,并保持后续对 winner 的处理不变。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 55279730-6ccb-4b40-af3e-ff6dc0512dfb

📥 Commits

Reviewing files that changed from the base of the PR and between 31c4324 and ee5557e.

📒 Files selected for processing (5)
  • src/app/v1/_lib/proxy/discovery-coordinator.ts
  • src/app/v1/_lib/proxy/discovery-validity.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • tests/unit/proxy/discovery-coordinator.test.ts
  • tests/unit/proxy/discovery-validity.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/unit/proxy/discovery-validity.test.ts
  • tests/unit/proxy/discovery-coordinator.test.ts
  • src/app/v1/_lib/proxy/discovery-validity.ts
  • src/app/v1/_lib/proxy/forwarder.ts

@Brisbanehuang
Brisbanehuang marked this pull request as ready for review July 20, 2026 19:44
@github-actions github-actions Bot added the size/XL Extra Large PR (> 1000 lines) label Jul 20, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee5557ecaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/v1/_lib/proxy/forwarder.ts

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43c6733e43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated
Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated
Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated
Comment thread src/app/v1/_lib/proxy/discovery-validity.ts Outdated
Comment thread src/app/v1/_lib/proxy/response-handler.ts Outdated
Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated

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

Code Review Summary

Issues Found

# Severity File Line Description
1 Medium response-handler.ts 1155-1159 Versioned binding clear uses wrong expectedProviderId — silently fails for Discovery
2 Medium forwarder.ts 4841 No unit tests for sendStreamingWithDiscovery (~400 lines of complex async logic)

No Critical or High severity issues found.


Finding Details

1. [Medium] clearSessionBinding passes wrong expectedProviderId for versioned clear

In the completionMarkerMissing failure path (and all Discovery failure paths that call clearSessionBinding), clearVersionedSessionProvider is called with providerIdForPersistence (the winner's provider ID). But the binding CAS-set only runs in the success path after the completion marker check passes — so at the point of clearing, the binding was never updated to the winner. The CLEAR_SESSION_BINDING Lua script detects the provider mismatch and returns {conflict, provider_mismatch}, causing the clear to silently no-op. The return value is discarded with no logging.

The forwarder's settleFailure in sendStreamingWithDiscovery (~line 4955) correctly uses bindingSnapshot.providerId — confirming the intended pattern. Fix: use meta.bindingSnapshot.providerId instead of providerIdForPersistence.

2. [Medium] sendStreamingWithDiscovery has zero unit test coverage

The method implements the full Discovery streaming pipeline: multi-round provider racing, three independent timers (total/round/sticky), coordinator state machine integration, reader-loop cancellation, winner commit, and binding management. The coordinator and validity parser have isolated tests, but the integration layer is completely untested. CLAUDE.md requires 80% coverage for new features.


Review Coverage

  • Execution paths and error handling
  • Test coverage gaps
  • Silent failure detection (return values, swallowed errors)
  • Type safety and casts
  • Race conditions and async coordination
  • Redis Lua script correctness
  • Session binding lifecycle (read/CAS/clear/terminate)
  • Dead code analysis

PR Size: XL (5,283+ / 314- across 32 files)

For a PR of this size, consider splitting into focused PRs to reduce review risk:

  1. Discovery primitivesdiscovery-coordinator.ts + discovery-validity.ts (with tests)
  2. Session binding versioningsession-binding.ts, Lua scripts, session-manager.ts changes
  3. Forwarder integrationsendStreamingWithDiscovery + response-handler.ts changes + provider selector

The core architecture (pure coordinator state machine, protocol-aware validity parsing, generation-CAS binding with capability probing) is well-structured. The main risks are in the untested integration layer and the binding-clear correctness issue noted above.


Automated review by Claude AI

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e0cedba26

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/v1/_lib/proxy/forwarder.ts
Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated
Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated
Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated
Comment thread src/app/v1/_lib/proxy/forwarder.ts Outdated
# Conflicts:
#	src/app/v1/_lib/proxy/forwarder.ts
#	src/app/v1/_lib/proxy/response-handler.ts
#	src/app/v1/_lib/proxy/stream-finalization.ts
#	tests/unit/proxy/proxy-forwarder-hedge-first-byte.test.ts
#	tests/unit/proxy/response-handler-endpoint-circuit-isolation.test.ts
@Brisbanehuang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e24438b3b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/v1/_lib/proxy/discovery-validity.ts Outdated
Comment thread src/app/v1/_lib/proxy/forwarder.ts
@Brisbanehuang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f9cca1f0f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/v1/_lib/proxy/forwarder.ts

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d23d6cbc1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/v1/_lib/proxy/forwarder.ts

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c1f166281

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/v1/_lib/proxy/response-handler.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c82d521a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/v1/_lib/proxy/forwarder.ts
@Brisbanehuang

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

…0723' into agent/discovery-1348-on-integration

# Conflicts:
#	tests/unit/proxy/response-handler-endpoint-circuit-isolation.test.ts
@ding113
ding113 merged commit 46315cf into ding113:integration/discovery-stack-20260723 Jul 23, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Jul 23, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a885e0c251

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

endpointId: activeEndpoint.endpointId,
endpointUrl: endpointAudit.endpointUrl,
upstreamStatusCode: response.status,
bindingIntent: session.isSessionBindingAllowed() ? undefined : "none",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Release provider refs when disabling binding

When prepareStreamingDiscovery() disables session binding after a Discovery lease/binding conflict, ProviderSelector.ensure() may already have recorded a provider concurrent-session ref for this request. This deferred metadata only suppresses the binding write; because it does not mark the ref as owned, releaseOwnedProviderSessionRef() never consumes it on a successful single-upstream SSE, so the provider's active-session count remains inflated until TTL and later requests can incorrectly hit limitConcurrentSessions.

Useful? React with 👍 / 👎.

Comment on lines +1312 to +1314
event.event === "response.completed" &&
isRecord(event.data) &&
event.data.type === "response.completed" &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept data-only Responses completion events

For Responses-format SSE streams whose terminal arrives as a data-only event (data: {"type":"response.completed", ...} without an event: line), parseSSEData() assigns the default event name message and DiscoveryValidityParser accepts the same payload as terminal. This strict event-name gate makes finalization classify an otherwise complete Discovery winner as STREAM_COMPLETION_MARKER_MISSING, avoiding/clearing Sticky and recording a provider failure after a valid response.

Useful? React with 👍 / 👎.

Comment on lines +116 to +118
(object.type === "response.output_text.delta" && hasContent(object.delta)) ||
(object.type === "response.function_call_arguments.delta" && hasContent(object.delta)) ||
(object.type === "response.output_item.added" && hasOpenAIResponsesOutputItem(object.item)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept deliverable Responses stream events

When a Responses stream's first or only user-visible payload is delivered through response.output_item.done, response.function_call_arguments.done, response.refusal.delta/done, or reasoning-summary events, this readiness predicate never marks the candidate ready even though those event types carry finalized text/tool arguments per the Responses streaming events reference. The subsequent response.completed is then treated as terminal-without-ready by the Discovery forwarder, causing a valid provider to be failed over or returned as unavailable.

Useful? React with 👍 / 👎.

if (!choice || typeof choice !== "object") return false;
const choiceObject = choice as Record<string, unknown>;
const delta = choiceObject.delta;
return hasContent(delta) || hasContent(choiceObject.message);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat Chat refusals as deliverable content

When an OpenAI-compatible Chat stream returns a refusal-only answer, the streamed delta can carry refusal instead of content per the Chat Completions streaming schema. Because this path delegates to hasContent(delta), which does not inspect refusal, Discovery never marks that valid refusal as ready and then fails the provider when the stream terminates without a recognized content delta.

Useful? React with 👍 / 👎.

const orchestrate = async () => {
let initialLaunchFailed = false;
try {
await launch(initialProvider, "normal");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor Sticky cooldown for initial Discovery provider

After a Sticky timeout writes a provider cooldown, the next request can still have the normal selector choose that same provider as initialProvider; the only cooldown check is inside pickDiscoveryProviders(), but this line launches the initial provider before that helper is used. In sessions with a recently timed-out Sticky provider, this bypasses stickyTimeoutCooldownMs and can immediately retry the provider that was supposed to be cooled down.

Useful? React with 👍 / 👎.

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

Labels

area:provider area:session enhancement New feature or request size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants