Skip to content

Comments

refactor(proxy): introduce EndpointPolicy to replace hardcoded count_tokens checks#801

Merged
ding113 merged 2 commits intodevfrom
refactor/proxy-endpoint-policy
Feb 18, 2026
Merged

refactor(proxy): introduce EndpointPolicy to replace hardcoded count_tokens checks#801
ding113 merged 2 commits intodevfrom
refactor/proxy-endpoint-policy

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Feb 17, 2026

Summary

Introduce EndpointPolicy system to replace scattered isCountTokensRequest() conditionals across the proxy pipeline. This generalizes the "raw passthrough" behavior to cover both count_tokens and responses/compact endpoints via a unified policy object resolved once at ProxySession construction.

Related Issues

Problem

  1. Scattered conditional logic: The codebase had hardcoded isCountTokensRequest() checks spread across proxy-handler, forwarder, response-handler, and filters - making it difficult to maintain consistent special-case handling.

  2. Codex CLI compact endpoint issues (Issue 中转codex cli的remote compact异常的BUG #797):

    • Invalid prompt_cache_key parameter was being injected into /v1/responses/compact requests
    • Circuit breaker and retry logic were incorrectly applied to this internal diagnostic endpoint
  3. Extensibility: Adding new special-case endpoints required modifying multiple files with repetitive conditional checks.

Solution

Create a declarative EndpointPolicy system:

  1. Centralized path constants (endpoint-paths.ts): Case-insensitive, trailing-slash tolerant, query-string safe path normalization

  2. Immutable policy objects (endpoint-policy.ts): Two frozen policies:

    • DEFAULT_ENDPOINT_POLICY: Full pipeline for normal chat requests
    • RAW_PASSTHROUGH_ENDPOINT_POLICY: Minimal processing for count_tokens and responses/compact
  3. Policy-driven behavior: All components now read from session.getEndpointPolicy() instead of hardcoded checks:

    • trackConcurrentRequests: Skip for passthrough endpoints
    • bypassForwarderPreprocessing: Skip model redirects, cache TTL overrides, provider-specific injections (fixes 中转codex cli的remote compact异常的BUG #797 bug 1)
    • bypassRequestFilters: Skip global/provider request filters
    • bypassResponseRectifier: Skip response fixer processing
    • allowRetry/allowCircuitBreakerAccounting: Skip for passthrough endpoints

Changes

Core Changes

  • src/app/v1/_lib/proxy/endpoint-paths.ts - Centralized endpoint path constants + normalization utilities
  • src/app/v1/_lib/proxy/endpoint-policy.ts - EndpointPolicy interface with resolveEndpointPolicy() resolver
  • src/app/v1/_lib/proxy/session.ts - Hold immutable EndpointPolicy, expose via getEndpointPolicy()
  • src/app/v1/_lib/proxy/guard-pipeline.ts - New fromSession() / fromEndpointPolicy() builders; rename COUNT_TOKENS_PIPELINE -> RAW_PASSTHROUGH_PIPELINE
  • src/app/v1/_lib/proxy/forwarder.ts - Gate retry/circuit-breaker/preprocessing on policy flags (allowRetry, bypassForwarderPreprocessing)
  • src/app/v1/_lib/proxy/response-handler.ts - Gate circuit-breaker accounting and response rectifier on policy flags
  • src/app/v1/_lib/proxy/request-filter.ts / provider-request-filter.ts - Early return when bypassRequestFilters is set
  • src/app/v1/_lib/proxy-handler.ts - Use trackConcurrentRequests from policy instead of isCountTokensRequest()

Tests (12 files)

  • New tests (3):
    • tests/unit/proxy/endpoint-path-normalization.test.ts - Path normalization edge cases
    • tests/unit/proxy/endpoint-policy.test.ts - Policy resolution correctness
    • tests/unit/proxy/endpoint-policy-parity.test.ts - T11-T14: parity, bypass completeness, non-target regression, edge cases
  • Updated tests (9):
    • All existing proxy tests updated to supply endpointPolicy in session stubs

Breaking Changes

None. This is an internal refactoring that maintains backward compatibility.

Test Plan

  • bun run test - 497 tests pass (45 files)
  • bun run typecheck - clean
  • bun run lint - clean
  • Verify count_tokens requests still bypass session/rate-limit/message-context guards
  • Verify responses/compact requests get the same raw passthrough treatment (fixes 中转codex cli的remote compact异常的BUG #797 part 1)
  • Verify normal chat endpoints (/v1/messages, /v1/chat/completions) retain full pipeline behavior

Implementation Notes

  • The policy is resolved once at ProxySession construction and frozen - no runtime overhead
  • RAW_PASSTHROUGH_PIPELINE guard steps: [auth, client, model, version, probe, provider] - minimal validation only
  • fromRequestType() is deprecated but retained for backward compatibility; new code should use fromSession() or fromEndpointPolicy()

Description enhanced by Claude AI

Greptile Summary

Replaces scattered isCountTokensRequest() conditionals with a centralized EndpointPolicy system resolved once at ProxySession construction. The count_tokens and responses/compact endpoints now share a unified raw_passthrough policy that bypasses forwarder preprocessing (fixing the prompt_cache_key injection bug for /v1/responses/compact), request filters, response rectifier, circuit breaker accounting, retry logic, and concurrent request tracking. Normal chat endpoints retain the full pipeline via the frozen default policy.

  • New files: endpoint-paths.ts (path constants + normalization), endpoint-policy.ts (EndpointPolicy interface + resolver)
  • Core changes: session.ts holds immutable policy; proxy-handler.ts, forwarder.ts, response-handler.ts, request-filter.ts, provider-request-filter.ts, and guard-pipeline.ts all switch from hardcoded checks to policy-driven behavior
  • Guard pipeline: COUNT_TOKENS_PIPELINE renamed to RAW_PASSTHROUGH_PIPELINE with fewer steps (removes requestFilter/providerRequestFilter); backward-compat alias retained
  • Tests: 3 new test files with thorough parity, bypass, regression, and edge-case coverage; 9 existing test files updated with endpointPolicy in session stubs
  • Minor observations: Several EndpointPolicy fields (bypassSpecialSettings, allowProviderSwitch, endpointPoolStrictness) and endpoint-paths.ts exports are defined but not consumed yet — these appear to be forward-looking placeholders

Confidence Score: 4/5

  • This PR is a well-structured internal refactoring with comprehensive test coverage and no behavioral regressions for standard endpoints.
  • The refactoring cleanly centralizes previously scattered conditionals into a frozen, immutable policy resolved once at construction time. All six circuit-breaker sites, the response rectifier, forwarder preprocessing, and request filters are consistently gated. The follow-up commit fixes a duplicate cache TTL call introduced during the restructuring. Tests cover parity, bypass completeness, regression, and edge cases. Minor deduction for unused policy fields and dead exports that add surface area without consumption.
  • src/app/v1/_lib/proxy/endpoint-policy.ts and src/app/v1/_lib/proxy/endpoint-paths.ts have unused exported symbols that could be cleaned up.

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/endpoint-paths.ts New file: Centralized endpoint path constants and normalization utilities. Several exported functions and constants (toV1RoutePath, STANDARD_ENDPOINT_PATHS, STRICT_STANDARD_ENDPOINT_PATHS, isStandardEndpointPath, isStrictStandardEndpointPath) are unused anywhere in the codebase.
src/app/v1/_lib/proxy/endpoint-policy.ts New file: EndpointPolicy interface with two frozen singleton policies and resolver. Fields bypassSpecialSettings, allowProviderSwitch, and endpointPoolStrictness are defined but not consumed anywhere in production code.
src/app/v1/_lib/proxy/session.ts Adds private readonly endpointPolicy field resolved at construction time. Defensive resolveSessionEndpointPolicy wrapper handles malformed URLs gracefully. isCountTokensRequest() updated to use normalized path matching.
src/app/v1/_lib/proxy/guard-pipeline.ts Adds fromSession/fromEndpointPolicy builders; renames COUNT_TOKENS_PIPELINE to RAW_PASSTHROUGH_PIPELINE (with backward-compat alias). Removes requestFilter and providerRequestFilter steps from passthrough pipeline.
src/app/v1/_lib/proxy/forwarder.ts Gates retry/circuit-breaker/preprocessing on policy flags. Cache TTL duplication fixed in follow-up commit. Reorder within bypass gate is correct (cache TTL now applied after provider overrides).
src/app/v1/_lib/proxy/response-handler.ts All circuit breaker recordFailure calls and ResponseFixer invocation gated behind policy flags. Six circuit-breaker sites updated consistently.
src/app/v1/_lib/proxy-handler.ts Switches from isCountTokensRequest() + RequestType enum to policy-driven pipeline selection and concurrent request tracking. Clean simplification.
src/app/v1/_lib/proxy/request-filter.ts Adds early-return guard for bypassRequestFilters policy. Defense-in-depth alongside pipeline step removal.
src/app/v1/_lib/proxy/provider-request-filter.ts Adds early-return guard for bypassRequestFilters policy. Defense-in-depth alongside pipeline step removal.
tests/unit/proxy/endpoint-policy-parity.test.ts New test: Comprehensive parity tests (T11-T14) covering policy equality, bypass completeness via spies, non-target regression, and path edge cases.

Flowchart

flowchart TD
    A[Incoming Request] --> B[ProxySession.fromContext]
    B --> C[resolveSessionEndpointPolicy<br/>pathname → EndpointPolicy]
    C --> D{policy.guardPreset}
    D -->|chat| E[CHAT_PIPELINE<br/>auth → sensitive → client → model<br/>→ version → probe → session → warmup<br/>→ requestFilter → rateLimit → provider<br/>→ providerRequestFilter → messageContext]
    D -->|raw_passthrough| F[RAW_PASSTHROUGH_PIPELINE<br/>auth → client → model<br/>→ version → probe → provider]
    E --> G[trackConcurrentRequests ✓]
    F --> H[trackConcurrentRequests ✗]
    G --> I[ProxyForwarder.send]
    H --> I
    I --> J{bypassForwarderPreprocessing?}
    J -->|No| K[Model redirect + Provider overrides<br/>+ Cache TTL + Billing rectifier]
    J -->|Yes| L[Skip all preprocessing]
    K --> M[Upstream Request]
    L --> M
    M --> N[ProxyResponseHandler.dispatch]
    N --> O{bypassResponseRectifier?}
    O -->|No| P[ResponseFixer.process]
    O -->|Yes| Q[Skip ResponseFixer]
    P --> R{allowCircuitBreakerAccounting?}
    Q --> R
    R -->|Yes| S[Record success/failure in circuit breaker]
    R -->|No| T[Skip circuit breaker accounting]
    S --> U[Return Response]
    T --> U
Loading

Last reviewed commit: 40b030d

…tokens checks

Replace scattered isCountTokensRequest() conditionals with a unified
EndpointPolicy system resolved once at session construction time. This
generalizes the "raw passthrough" behavior to cover both count_tokens
and responses/compact endpoints via a single policy object.

Key changes:
- Add endpoint-paths.ts (path constants + normalization with case/slash/query handling)
- Add endpoint-policy.ts (EndpointPolicy interface + resolution logic)
- ProxySession holds immutable EndpointPolicy resolved at construction
- GuardPipeline.fromSession() reads policy instead of RequestType enum
- Forwarder, ResponseHandler, RequestFilter all gate on policy flags
- proxy-handler uses trackConcurrentRequests from policy
@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

将基于 RequestType 的守卫与并发计数逻辑替换为基于会话的端点策略(EndpointPolicy)。新增端点路径与策略模块,并在转发器、响应处理、请求过滤和守卫管道中以策略标志为门控条件实现有条件绕过与隔离行为。

Changes

Cohort / File(s) Summary
端点路径 & 策略模块
src/app/v1/_lib/proxy/endpoint-paths.ts, src/app/v1/_lib/proxy/endpoint-policy.ts
新增集中化的 V1 路径常量、路径规范化与判定函数;新增 EndpointPolicy 类型、默认与 raw_passthrough 策略以及策略解析与判定工具。
会话与接口接入
src/app/v1/_lib/proxy/session.ts
在 ProxySession 中引入 endpointPolicy 字段,新增 getEndpointPolicy(),通过请求 URL 在会话初始化时解析并缓存策略;改用路径判定替代硬编码字符串检测。
守卫管道重构
src/app/v1/_lib/proxy/guard-pipeline.ts, src/app/v1/_lib/proxy/proxy-handler.ts
移除基于 RequestType 的 fromRequestType;添加 GuardPipelineBuilder.fromSession/fromEndpointPolicy;新增 RAW_PASSTHROUGH_PIPELINE 并将并发计数控制点改为依据 session.getEndpointPolicy().trackConcurrentRequests。
转发器行为调整
src/app/v1/_lib/proxy/forwarder.ts
为 raw_passthrough/绕过模式增加早期放行:在 endpointPolicy.allowRetry=false 时直接抛出并跳过电路断路器与提供商切换;将模型重定向、Codex/Anthropic 覆盖、缓存 TTL、审计持久化等预处理操作置于 bypassForwarderPreprocessing 门控之下。
请求/提供者过滤器
src/app/v1/_lib/proxy/request-filter.ts, src/app/v1/_lib/proxy/provider-request-filter.ts
当 session.getEndpointPolicy().bypassRequestFilters 为真时,提前返回跳过全局与提供者级请求过滤逻辑。
响应处理器隔离
src/app/v1/_lib/proxy/response-handler.ts
在多个错误/超时/非200 分支用 allowCircuitBreakerAccounting 门控电路断路器记账;用 bypassResponseRectifier 门控对响应修正(ResponseFixer)的调用;保留原日志与回退路径。
单元测试更新/新增
tests/unit/proxy/... (多个文件, 包括 endpoint-path-normalization.test.ts, endpoint-policy*.test.ts, guard-pipeline-warmup.test.ts, proxy-forwarder-*.test.ts, response-handler-*.test.ts, session.test.ts)
新增大量测试覆盖端点路径规范化、策略解析与等价性、绕过行为、并发计数隔离以及守卫管道预设选择;测试中为会话实例注入或绑定 endpointPolicy/getEndpointPolicy。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 分钟

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% 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 PR标题清晰概括了主要变更内容:引入EndpointPolicy系统以替代硬编码的count_tokens检查,准确反映了本次重构的核心目标。
Linked Issues check ✅ Passed PR成功解决了问题#797的核心需求:通过EndpointPolicy系统绕过responses/compact的forwarder预处理,防止无效prompt_cache_key参数注入,确保响应行为正确。
Out of Scope Changes check ✅ Passed 所有变更均围绕EndpointPolicy系统的引入而展开,包括新增的路径规范化、策略解析、守卫管道调整和相关测试,无超出范围的修改。
Description check ✅ Passed PR description accurately describes the EndpointPolicy system refactoring, changes made, and objectives to fix the prompt_cache_key injection bug for /v1/responses/compact endpoint.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/proxy-endpoint-policy

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.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the proxy's request handling logic by introducing a robust EndpointPolicy system. This change centralizes the configuration and behavior of different API endpoints, moving away from hardcoded conditional checks. The new system allows for more flexible and consistent application of rules for aspects like concurrent request tracking, retry mechanisms, and request/response processing, particularly generalizing 'raw passthrough' behavior for specific endpoints.

Highlights

  • EndpointPolicy System Introduction: A new EndpointPolicy system has been introduced to centralize and replace scattered isCountTokensRequest() conditional checks throughout the proxy pipeline, improving maintainability and clarity.
  • Centralized Endpoint Path Management: New files endpoint-paths.ts and endpoint-policy.ts were added to manage endpoint path constants, normalization (case-insensitive, trailing-slash tolerant, query-string safe), and to define the EndpointPolicy interface and its resolution logic.
  • Generalized Raw Passthrough Behavior: The 'raw passthrough' behavior, previously specific to count_tokens requests, has been generalized to cover both count_tokens and responses/compact endpoints through a unified EndpointPolicy.
  • Pipeline Integration: The EndpointPolicy is now resolved once at ProxySession construction and used across proxy-handler.ts, forwarder.ts, guard-pipeline.ts, response-handler.ts, and request filters to dynamically adjust behavior based on the endpoint's policy flags.
Changelog
  • src/app/v1/_lib/proxy-handler.ts
    • Removed direct import of RequestType enum.
    • Updated guard pipeline construction to use GuardPipelineBuilder.fromSession instead of fromRequestType.
    • Replaced session.isCountTokensRequest() checks with session.getEndpointPolicy().trackConcurrentRequests for incrementing and decrementing concurrent request counts.
  • src/app/v1/_lib/proxy/endpoint-paths.ts
    • Added new file to define V1_ENDPOINT_PATHS constants for various API endpoints.
    • Introduced normalizeEndpointPath for consistent path handling, supporting case-insensitivity, trailing slashes, and query string removal.
    • Provided helper functions like isStandardEndpointPath, isStrictStandardEndpointPath, isCountTokensEndpointPath, and isResponseCompactEndpointPath.
  • src/app/v1/_lib/proxy/endpoint-policy.ts
    • Added new file to define the EndpointPolicy interface, specifying various boolean flags and presets for endpoint behavior.
    • Defined DEFAULT_ENDPOINT_POLICY and RAW_PASSTHROUGH_ENDPOINT_POLICY as frozen singleton objects.
    • Implemented isRawPassthroughEndpointPath and isRawPassthroughEndpointPolicy helper functions.
    • Created resolveEndpointPolicy to determine the appropriate policy based on the request pathname.
  • src/app/v1/_lib/proxy/forwarder.ts
    • Modified error handling to use !session.getEndpointPolicy().allowRetry to skip circuit breaker and provider switch for raw passthrough endpoints.
    • Wrapped model redirection and provider-level overrides (Codex, Anthropic) within a conditional check using !session.getEndpointPolicy().bypassForwarderPreprocessing.
  • src/app/v1/_lib/proxy/guard-pipeline.ts
    • Removed RequestType enum from imports.
    • Introduced fromSession and fromEndpointPolicy static methods to build guard pipelines based on the resolved EndpointPolicy.
    • Renamed COUNT_TOKENS_PIPELINE to RAW_PASSTHROUGH_PIPELINE and simplified its steps to ['auth', 'client', 'model', 'version', 'probe', 'provider'].
    • Aliased COUNT_TOKENS_PIPELINE to RAW_PASSTHROUGH_PIPELINE for backward compatibility.
  • src/app/v1/_lib/proxy/provider-request-filter.ts
    • Added an early return at the beginning of ensure method if session.getEndpointPolicy().bypassRequestFilters is true.
  • src/app/v1/_lib/proxy/request-filter.ts
    • Added an early return at the beginning of ensure method if session.getEndpointPolicy().bypassRequestFilters is true.
  • src/app/v1/_lib/proxy/response-handler.ts
    • Modified conditional checks for circuit breaker accounting to use session.getEndpointPolicy().allowCircuitBreakerAccounting.
    • Wrapped the ResponseFixer.process call within a conditional check using !session.getEndpointPolicy().bypassResponseRectifier.
  • src/app/v1/_lib/proxy/session.ts
    • Imported EndpointPolicy, resolveEndpointPolicy, and isCountTokensEndpointPath.
    • Added a private readonly endpointPolicy: EndpointPolicy property.
    • Initialized endpointPolicy in the constructor by calling resolveSessionEndpointPolicy with the request URL.
    • Added a public getEndpointPolicy() method to expose the resolved policy.
    • Updated isCountTokensRequest() to use the new isCountTokensEndpointPath helper for more robust path matching.
  • tests/unit/proxy/endpoint-path-normalization.test.ts
    • Added new test file to verify the correctness of endpoint path normalization and classification for various path variants.
  • tests/unit/proxy/endpoint-policy-parity.test.ts
    • Added new test file to ensure parity between count_tokens and responses/compact endpoints regarding their EndpointPolicy.
    • Verified bypass completeness for request filters using spy-based assertions.
    • Confirmed non-target regression, ensuring default endpoints retain their full default policy.
    • Tested path edge cases for normalization, including trailing slashes, case variants, and query strings.
  • tests/unit/proxy/endpoint-policy.test.ts
    • Added new test file to validate the resolution and properties of EndpointPolicy for raw passthrough and default endpoints.
  • tests/unit/proxy/guard-pipeline-warmup.test.ts
    • Updated tests to use resolveEndpointPolicy and GuardPipelineBuilder.fromSession for pipeline construction.
    • Added tests to confirm count_tokens and responses/compact use the same raw preset and that /v1/messages retains the chat preset.
  • tests/unit/proxy/proxy-forwarder-fake-200-html.test.ts
    • Updated session creation to include endpointPolicy.
  • tests/unit/proxy/proxy-forwarder-large-chunked-response.test.ts
    • Updated session creation to include endpointPolicy.
  • tests/unit/proxy/proxy-forwarder-nonok-body-hang.test.ts
    • Updated session creation to include endpointPolicy.
  • tests/unit/proxy/proxy-handler-session-id-error.test.ts
    • Updated session mock to include getEndpointPolicy.
    • Modified GuardPipelineBuilder mock to include fromSession.
    • Added parameterized tests to verify concurrent request tracking is skipped for raw passthrough endpoints.
  • tests/unit/proxy/response-handler-endpoint-circuit-isolation.test.ts
    • Updated session creation to include endpointPolicy.
  • tests/unit/proxy/response-handler-gemini-stream-passthrough-timeouts.test.ts
    • Updated session creation to include endpointPolicy.
  • tests/unit/proxy/response-handler-lease-decrement.test.ts
    • Updated session creation to include endpointPolicy.
  • tests/unit/proxy/session.test.ts
    • Added tests for ProxySession endpoint policy resolution, ensuring correct policy assignment at creation and immutability.
    • Verified fallback to default policy for malformed URLs.
Activity
  • All 497 unit tests passed (bun run test).
  • Type checking completed without errors (bun run typecheck).
  • Linting checks passed without issues (bun run lint).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added the size/XL Extra Large PR (> 1000 lines) label Feb 17, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is an excellent refactoring that introduces the EndpointPolicy system, replacing scattered isCountTokensRequest() checks with a more robust and maintainable policy-driven approach. The centralization of endpoint path logic in endpoint-paths.ts and the clear definition of policies in endpoint-policy.ts significantly improve code clarity and organization. The changes are well-supported by a comprehensive suite of new and updated tests. I have one suggestion to clean up some duplicated code that was touched during this refactoring.

Comment on lines 1905 to 1917
if (
resolvedCacheTtl &&
(provider.providerType === "claude" || provider.providerType === "claude-auth")
) {
const applied = applyCacheTtlOverrideToMessage(session.request.message, resolvedCacheTtl);
if (applied) {
logger.info("ProxyForwarder: Applied cache TTL override to request", {
providerId: provider.id,
providerName: provider.name,
cacheTtl: resolvedCacheTtl,
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This block of code, which applies a cache TTL override for Claude providers, appears to be redundant. A similar block exists later in this file (lines 2025-2036) within the more specific claude provider logic. This duplication means applyCacheTtlOverrideToMessage is called twice for the same request, which could lead to unexpected behavior.

To consolidate the logic and prevent the redundant call, I recommend removing this block. The logic will be correctly handled by the block within the provider-specific if statement.

@github-actions
Copy link
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@github-actions github-actions bot added enhancement New feature or request area:core labels Feb 17, 2026
Copy link

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
src/app/v1/_lib/proxy/response-handler.ts (1)

256-365: ⚠️ Potential issue | 🟠 Major

allowCircuitBreakerAccounting 只覆盖失败路径,成功路径仍会被统计。

当前仅在失败分支跳过 recordFailure,但成功分支仍会执行 recordEndpointSuccess/recordSuccess,raw_passthrough 仍会影响熔断统计,语义不一致。建议成功路径也加同样的 policy 保护。

建议修复
-  if (meta.endpointId != null) {
-    try {
-      const { recordEndpointSuccess } = await import("@/lib/endpoint-circuit-breaker");
-      await recordEndpointSuccess(meta.endpointId);
-    } catch (endpointError) {
-      logger.warn("[ResponseHandler] Failed to record endpoint success (stream)", {
-        endpointId: meta.endpointId,
-        providerId: meta.providerId,
-        error: endpointError,
-      });
-    }
-  }
-
-  try {
-    const { recordSuccess } = await import("@/lib/circuit-breaker");
-    await recordSuccess(meta.providerId);
-  } catch (cbError) {
-    logger.warn("[ResponseHandler] Failed to record streaming success in circuit breaker", {
-      providerId: meta.providerId,
-      error: cbError,
-    });
-  }
+  if (session.getEndpointPolicy().allowCircuitBreakerAccounting) {
+    if (meta.endpointId != null) {
+      try {
+        const { recordEndpointSuccess } = await import("@/lib/endpoint-circuit-breaker");
+        await recordEndpointSuccess(meta.endpointId);
+      } catch (endpointError) {
+        logger.warn("[ResponseHandler] Failed to record endpoint success (stream)", {
+          endpointId: meta.endpointId,
+          providerId: meta.providerId,
+          error: endpointError,
+        });
+      }
+    }
+
+    try {
+      const { recordSuccess } = await import("@/lib/circuit-breaker");
+      await recordSuccess(meta.providerId);
+    } catch (cbError) {
+      logger.warn("[ResponseHandler] Failed to record streaming success in circuit breaker", {
+        providerId: meta.providerId,
+        error: cbError,
+      });
+    }
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/response-handler.ts` around lines 256 - 365, Success
responses are still updating circuit-breaker/accounting (e.g., calls to
recordEndpointSuccess, recordSuccess or any raw_passthrough-related accounting)
while failures are gated by
session.getEndpointPolicy().allowCircuitBreakerAccounting; modify the
success-path code to check
session.getEndpointPolicy().allowCircuitBreakerAccounting before performing any
circuit-breaker or endpoint accounting. Specifically, in the response success
handling (the code path that currently invokes recordEndpointSuccess,
recordSuccess or performs raw_passthrough accounting), wrap those calls with if
(session.getEndpointPolicy().allowCircuitBreakerAccounting) { ... } or skip the
accounting when the policy is false so success and failure paths follow the same
policy semantics.
🧹 Nitpick comments (4)
tests/unit/proxy/proxy-handler-session-id-error.test.ts (1)

179-212: 新增的 RED 测试用例结构清晰,但 getEndpointPolicy 重复赋值是冗余的。

Line 202 重新赋值 h.session.getEndpointPolicy,但 Line 16 已经定义了动态版本 () => resolveEndpointPolicy(h.session.requestUrl.pathname),会自动跟随 requestUrl 的变更。Line 201 设置了新的 requestUrl 后,Line 16 的定义已经能正确返回对应策略,无需重复赋值。

建议移除冗余赋值
     h.session.requestUrl = new URL(`http://localhost${pathname}`);
-    h.session.getEndpointPolicy = () => resolveEndpointPolicy(h.session.requestUrl.pathname);
     h.session.sessionId = "s_123";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/proxy/proxy-handler-session-id-error.test.ts` around lines 179 -
212, The test redundantly reassigns h.session.getEndpointPolicy after setting
h.session.requestUrl; remove the second assignment so the existing dynamic
getter definition h.session.getEndpointPolicy = () =>
resolveEndpointPolicy(h.session.requestUrl.pathname) is used (it will pick up
the new h.session.requestUrl), keeping the rest of the test setup and assertions
(including handleProxyRequest, h.session.requestUrl, and resolveEndpointPolicy)
unchanged.
src/app/v1/_lib/proxy/endpoint-paths.ts (1)

21-26: STRICT_STANDARD_ENDPOINT_PATHS 的语义与成员不太直观。

该数组包含 RESPONSES_COMPACT(一个 raw passthrough 端点),同时排除了 MESSAGES_COUNT_TOKENS(也是 raw passthrough)和 MODELS。"Strict Standard" 的命名没有清晰传达其筛选标准。建议补充一行注释说明该集合的用途和筛选逻辑,方便后续维护者理解为什么某些端点被包含/排除。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/endpoint-paths.ts` around lines 21 - 26,
STRICT_STANDARD_ENDPOINT_PATHS 的命名与实际成员(包含 RESPONSES_COMPACT,排除
MESSAGES_COUNT_TOKENS 与 MODELS 等 raw
passthrough/非标准端点)容易让人疑惑;请在该常量定义上方添加一行简短注释,说明该集合的用途(比如“用于对外暴露的标准写入/读取端点集合,用于 X
场景/路由判断”)以及清晰列出筛选逻辑(为何包含 V1_ENDPOINT_PATHS.RESPONSES_COMPACT、为何排除
V1_ENDPOINT_PATHS.MESSAGES_COUNT_TOKENS 与
V1_ENDPOINT_PATHS.MODELS),以便后续维护者理解包含/排除的原则;保持注释与常量靠近并使用同一文件中的
STRICT_STANDARD_ENDPOINT_PATHS / V1_ENDPOINT_PATHS 名称以便定位。
tests/unit/proxy/session.test.ts (1)

400-431: createSessionForHeaders 辅助函数缺少 endpointPolicy 字段。

虽然当前使用此函数的测试(isHeaderModified 系列)不涉及端点策略,但如果后续扩展使用场景,缺少该字段会导致 getEndpointPolicy() 返回 undefined。建议与其他测试辅助函数保持一致。

建议补充
 import { resolveEndpointPolicy } from "@/app/v1/_lib/proxy/endpoint-policy";
 // ...
 Object.assign(session, {
     // ... existing fields ...
     cachedBillingModelSource: undefined,
+    endpointPolicy: resolveEndpointPolicy("/v1/messages"),
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/proxy/session.test.ts` around lines 400 - 431, The helper
createSessionForHeaders is missing the endpointPolicy property which can make
ProxySession.getEndpointPolicy() return undefined later; update the object
assigned to the created ProxySession instance in createSessionForHeaders to
include an endpointPolicy field (matching the shape used by other
helpers/tests), e.g. add endpointPolicy: <appropriate default/policy object or
null> so tests that call ProxySession.getEndpointPolicy() behave consistently
with other session builders.
src/app/v1/_lib/proxy/session.ts (1)

805-814: catch 块会静默吞掉异常,建议至少记录一条 debug 日志。

当前实现在 pathname 读取失败时静默回退到默认策略。虽然测试已覆盖此场景,但在生产环境中完全吞掉异常会增加排查难度。

建议添加日志
 function resolveSessionEndpointPolicy(requestUrl: URL): EndpointPolicy {
   try {
     const pathname = requestUrl.pathname;
     if (typeof pathname === "string" && pathname.length > 0) {
       return resolveEndpointPolicy(pathname);
     }
-  } catch {}
+  } catch (err) {
+    logger.debug("[resolveSessionEndpointPolicy] Failed to read pathname, using default policy", {
+      error: err,
+    });
+  }
 
   return resolveEndpointPolicy("/");
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/session.ts` around lines 805 - 814, In
resolveSessionEndpointPolicy, the empty catch silently swallows exceptions;
update the catch to log the error (and relevant context like requestUrl or
pathname) at debug level before falling back to resolveEndpointPolicy("/");
specifically, inside the catch block of function
resolveSessionEndpointPolicy(requestUrl: URL) call a debug logger (e.g.,
console.debug(...) or the project logger if available) with the caught error and
the requestUrl.pathname, then continue returning resolveEndpointPolicy("/").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/v1/_lib/proxy/endpoint-policy.ts`:
- Line 1: Replace the relative import of normalizeEndpointPath and
V1_ENDPOINT_PATHS in endpoint-policy.ts with the project path-alias import using
the `@/` prefix; locate the import statement that references "./endpoint-paths"
and change it to import normalizeEndpointPath and V1_ENDPOINT_PATHS from
"@/app/v1/_lib/proxy/endpoint-paths" so the module uses the src path alias.

In `@src/app/v1/_lib/proxy/forwarder.ts`:
- Around line 1624-1637: The raw_passthrough error handling only honored
allowRetry/allowProviderSwitch/allowCircuitBreakerAccounting inside the
PROVIDER_ERROR branch; instead, after error classification you should early-exit
based on session.getEndpointPolicy() for all error kinds so system
errors/timeouts do not trigger retries, provider switches or circuit-breaker
accounting. Update the logic in ProxyForwarder around the raw_passthrough branch
to check session.getEndpointPolicy().allowRetry, .allowProviderSwitch and
.allowCircuitBreakerAccounting immediately after classifying proxyError (using
variables proxyError/lastError/currentProvider) and throw/return when the policy
forbids retry/switch/accounting; also gate calls to recordFailure,
recordEndpointFailure, recordSuccess and recordEndpointSuccess with those same
policy flags so accounting only happens when allowed.

In `@src/app/v1/_lib/proxy/guard-pipeline.ts`:
- Line 3: Import of EndpointPolicy should use the project path alias; update the
import in guard-pipeline.ts to reference EndpointPolicy via the '@/...' alias
instead of a relative path (replace the existing import of EndpointPolicy from
"./endpoint-policy" with an import that uses the '@/...' path to the same
module), ensuring the module specifier matches the repo's src root alias and
keeps the same exported symbol name EndpointPolicy.

---

Outside diff comments:
In `@src/app/v1/_lib/proxy/response-handler.ts`:
- Around line 256-365: Success responses are still updating
circuit-breaker/accounting (e.g., calls to recordEndpointSuccess, recordSuccess
or any raw_passthrough-related accounting) while failures are gated by
session.getEndpointPolicy().allowCircuitBreakerAccounting; modify the
success-path code to check
session.getEndpointPolicy().allowCircuitBreakerAccounting before performing any
circuit-breaker or endpoint accounting. Specifically, in the response success
handling (the code path that currently invokes recordEndpointSuccess,
recordSuccess or performs raw_passthrough accounting), wrap those calls with if
(session.getEndpointPolicy().allowCircuitBreakerAccounting) { ... } or skip the
accounting when the policy is false so success and failure paths follow the same
policy semantics.

---

Nitpick comments:
In `@src/app/v1/_lib/proxy/endpoint-paths.ts`:
- Around line 21-26: STRICT_STANDARD_ENDPOINT_PATHS 的命名与实际成员(包含
RESPONSES_COMPACT,排除 MESSAGES_COUNT_TOKENS 与 MODELS 等 raw
passthrough/非标准端点)容易让人疑惑;请在该常量定义上方添加一行简短注释,说明该集合的用途(比如“用于对外暴露的标准写入/读取端点集合,用于 X
场景/路由判断”)以及清晰列出筛选逻辑(为何包含 V1_ENDPOINT_PATHS.RESPONSES_COMPACT、为何排除
V1_ENDPOINT_PATHS.MESSAGES_COUNT_TOKENS 与
V1_ENDPOINT_PATHS.MODELS),以便后续维护者理解包含/排除的原则;保持注释与常量靠近并使用同一文件中的
STRICT_STANDARD_ENDPOINT_PATHS / V1_ENDPOINT_PATHS 名称以便定位。

In `@src/app/v1/_lib/proxy/session.ts`:
- Around line 805-814: In resolveSessionEndpointPolicy, the empty catch silently
swallows exceptions; update the catch to log the error (and relevant context
like requestUrl or pathname) at debug level before falling back to
resolveEndpointPolicy("/"); specifically, inside the catch block of function
resolveSessionEndpointPolicy(requestUrl: URL) call a debug logger (e.g.,
console.debug(...) or the project logger if available) with the caught error and
the requestUrl.pathname, then continue returning resolveEndpointPolicy("/").

In `@tests/unit/proxy/proxy-handler-session-id-error.test.ts`:
- Around line 179-212: The test redundantly reassigns
h.session.getEndpointPolicy after setting h.session.requestUrl; remove the
second assignment so the existing dynamic getter definition
h.session.getEndpointPolicy = () =>
resolveEndpointPolicy(h.session.requestUrl.pathname) is used (it will pick up
the new h.session.requestUrl), keeping the rest of the test setup and assertions
(including handleProxyRequest, h.session.requestUrl, and resolveEndpointPolicy)
unchanged.

In `@tests/unit/proxy/session.test.ts`:
- Around line 400-431: The helper createSessionForHeaders is missing the
endpointPolicy property which can make ProxySession.getEndpointPolicy() return
undefined later; update the object assigned to the created ProxySession instance
in createSessionForHeaders to include an endpointPolicy field (matching the
shape used by other helpers/tests), e.g. add endpointPolicy: <appropriate
default/policy object or null> so tests that call
ProxySession.getEndpointPolicy() behave consistently with other session
builders.

@@ -0,0 +1,68 @@
import { normalizeEndpointPath, V1_ENDPOINT_PATHS } from "./endpoint-paths";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

endpoint-paths 导入请改用 @/ 路径别名。

该文件位于 src 内,新增导入按规范应使用 @/ 前缀。As per coding guidelines: Use path alias @/ to reference files in ./src/ directory.

建议修复
-import { normalizeEndpointPath, V1_ENDPOINT_PATHS } from "./endpoint-paths";
+import { normalizeEndpointPath, V1_ENDPOINT_PATHS } from "@/app/v1/_lib/proxy/endpoint-paths";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { normalizeEndpointPath, V1_ENDPOINT_PATHS } from "./endpoint-paths";
import { normalizeEndpointPath, V1_ENDPOINT_PATHS } from "@/app/v1/_lib/proxy/endpoint-paths";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/endpoint-policy.ts` at line 1, Replace the relative
import of normalizeEndpointPath and V1_ENDPOINT_PATHS in endpoint-policy.ts with
the project path-alias import using the `@/` prefix; locate the import statement
that references "./endpoint-paths" and change it to import normalizeEndpointPath
and V1_ENDPOINT_PATHS from "@/app/v1/_lib/proxy/endpoint-paths" so the module
uses the src path alias.

Comment on lines 1624 to 1637
// Raw passthrough endpoints: no circuit breaker, no provider switch, no retry
if (!session.getEndpointPolicy().allowRetry) {
logger.debug(
"ProxyForwarder: count_tokens request error, skipping circuit breaker and provider switch",
"ProxyForwarder: raw passthrough endpoint error, skipping circuit breaker and provider switch",
{
providerId: currentProvider.id,
providerName: currentProvider.name,
statusCode,
error: proxyError.message,
policyKind: session.getEndpointPolicy().kind,
}
);
// 直接抛出错误,不重试,不切换供应商
// Throw immediately: no retry, no provider switch
throw lastError;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

raw_passthrough 仅在 PROVIDER_ERROR 分支短路,系统错误仍会重试/计入熔断。

allowRetry/allowProviderSwitch/allowCircuitBreakerAccounting 目前只在 PROVIDER_ERROR 分支生效,SYSTEM_ERROR/超时仍会重试、切换供应商并记录熔断,和 policy 语义不一致。建议在错误分类后统一早退,并在 recordFailure/recordEndpointFailure/recordSuccess/recordEndpointSuccess 处统一 gate。

建议修复
-          // Raw passthrough endpoints: no circuit breaker, no provider switch, no retry
-          if (!session.getEndpointPolicy().allowRetry) {
+          const policy = session.getEndpointPolicy();
+          // Raw passthrough endpoints: no circuit breaker, no provider switch, no retry
+          if (!policy.allowRetry) {
             logger.debug(
               "ProxyForwarder: raw passthrough endpoint error, skipping circuit breaker and provider switch",
               {
                 providerId: currentProvider.id,
                 providerName: currentProvider.name,
                 statusCode,
                 error: proxyError.message,
-                policyKind: session.getEndpointPolicy().kind,
+                policyKind: policy.kind,
               }
             );
             // Throw immediately: no retry, no provider switch
             throw lastError;
           }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/forwarder.ts` around lines 1624 - 1637, The
raw_passthrough error handling only honored
allowRetry/allowProviderSwitch/allowCircuitBreakerAccounting inside the
PROVIDER_ERROR branch; instead, after error classification you should early-exit
based on session.getEndpointPolicy() for all error kinds so system
errors/timeouts do not trigger retries, provider switches or circuit-breaker
accounting. Update the logic in ProxyForwarder around the raw_passthrough branch
to check session.getEndpointPolicy().allowRetry, .allowProviderSwitch and
.allowCircuitBreakerAccounting immediately after classifying proxyError (using
variables proxyError/lastError/currentProvider) and throw/return when the policy
forbids retry/switch/accounting; also gate calls to recordFailure,
recordEndpointFailure, recordSuccess and recordEndpointSuccess with those same
policy flags so accounting only happens when allowed.

@@ -1,5 +1,6 @@
import { ProxyAuthenticator } from "./auth-guard";
import { ProxyClientGuard } from "./client-guard";
import type { EndpointPolicy } from "./endpoint-policy";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

EndpointPolicy 导入请改用 @/ 路径别名。

该文件位于 src 内,新增导入按规范应使用 @/ 前缀。As per coding guidelines: Use path alias @/ to reference files in ./src/ directory.

建议修复
-import type { EndpointPolicy } from "./endpoint-policy";
+import type { EndpointPolicy } from "@/app/v1/_lib/proxy/endpoint-policy";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { EndpointPolicy } from "./endpoint-policy";
import type { EndpointPolicy } from "@/app/v1/_lib/proxy/endpoint-policy";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/guard-pipeline.ts` at line 3, Import of EndpointPolicy
should use the project path alias; update the import in guard-pipeline.ts to
reference EndpointPolicy via the '@/...' alias instead of a relative path
(replace the existing import of EndpointPolicy from "./endpoint-policy" with an
import that uses the '@/...' path to the same module), ensuring the module
specifier matches the repo's src root alias and keeps the same exported symbol
name EndpointPolicy.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR introduces a well-designed EndpointPolicy system that successfully replaces scattered isCountTokensRequest() conditionals with a unified, declarative approach. The implementation is clean, the test coverage is comprehensive (341 new tests across 3 files), and the refactoring maintains backward compatibility while fixing the Codex CLI compact endpoint issues.

PR Size: XL

  • Lines changed: 1259 (1062 additions, 197 deletions)
  • Files changed: 22

Split Suggestion for Future: While this PR is well-scoped for a refactoring, it could theoretically be split into:

  1. Core policy infrastructure (endpoint-paths.ts, endpoint-policy.ts, session.ts changes)
  2. Guard pipeline integration (guard-pipeline.ts)
  3. Forwarder and response handler integration (forwarder.ts, response-handler.ts, filters)
  4. Test additions

However, given the interdependent nature of these changes, keeping them together is reasonable.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 1 0
Types 0 0 0 0
Comments/Docs 0 0 0 2
Tests 0 0 0 0
Simplification 0 0 0 0

Medium Priority Issues (Should Fix)

1. [ERROR-SILENT] Empty catch block in session.ts:811

  • Location: src/app/v1/_lib/proxy/session.ts:811
  • Issue: The resolveSessionEndpointPolicy function silently swallows errors
  • Fix: Add debug logging:
} catch (error) {
  logger.debug("[ProxySession] Failed to resolve endpoint policy from URL, using default", {
    error: error instanceof Error ? error.message : String(error),
  });
}

Low Priority Issues (Author's Discretion)

2. [COMMENT-INCOMPLETE] Outdated comments in proxy-handler.ts

  • Location: Lines 62 and 98
  • Issue: Comments say "跳过 count_tokens" but should say "跳过 raw passthrough 端点" to match the new generic policy

Review Coverage

  • Logic and correctness - Clean implementation, good architectural decisions
  • Security (OWASP Top 10) - No issues found
  • Error handling - One silent catch block to address
  • Type safety - Proper use of readonly types and Object.freeze
  • Documentation accuracy - Minor comment updates suggested
  • Test coverage - Comprehensive (341 new tests across 3 files)
  • Code clarity - Well-structured, follows existing patterns

Highlights

  • Excellent test coverage: Path normalization edge cases, policy parity tests, bypass completeness assertions
  • Clean architecture: Immutable frozen policy objects, single-responsibility path normalization
  • Backward compatibility: COUNT_TOKENS_PIPELINE alias and fromRequestType() deprecated but retained
  • Bug fix: Properly addresses the prompt_cache_key injection bug for /v1/responses/compact

Automated review by Claude AI

… in local var

- Remove redundant first applyCacheTtlOverrideToMessage call (lines 1905-1917)
  that duplicated the post-Anthropic-overrides call (lines 2025-2036)
- Cache session.getEndpointPolicy() in local variable in error handling path
  to avoid repeated accessor calls

Addresses: gemini-code-assist and coderabbitai review comments
@github-actions
Copy link
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

// Raw passthrough endpoints: no circuit breaker, no provider switch, no retry
const endpointPolicy = session.getEndpointPolicy();
if (!endpointPolicy.allowRetry) {
logger.debug(
Copy link
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] [LOGIC-BUG] Raw passthrough EndpointPolicy can still mutate circuit-breaker state / trigger provider switching

Why this is a problem: the raw passthrough branch is currently keyed off:

const endpointPolicy = session.getEndpointPolicy();
if (!endpointPolicy.allowRetry) {
  // ...
  throw lastError;
}

But RAW_PASSTHROUGH_ENDPOINT_POLICY also sets allowCircuitBreakerAccounting: false and allowProviderSwitch: false. Today this check only prevents this retry/switch path — it does not prevent earlier/lateral circuit-breaker writes (e.g. vendor-type circuit open) or other error categories (system/network errors) from calling recordFailure(...) / entering provider-switch logic.

That means /v1/messages/count_tokens and /v1/responses/compact can still influence global availability decisions and failover behaviour, contradicting the policy contract.

Suggested fix: enforce policy at the top of the error-handling path, before any circuit-breaker/provider-switch side effects, and gate all circuit-breaker writes behind endpointPolicy.allowCircuitBreakerAccounting (and provider switching behind endpointPolicy.allowProviderSwitch).

const endpointPolicy = session.getEndpointPolicy();

if (!endpointPolicy.allowRetry) {
  logger.debug("ProxyForwarder: raw passthrough endpoint error, skipping retry/provider switch/circuit breaker", {
    providerId: currentProvider.id,
    statusCode,
    policyKind: endpointPolicy.kind,
  });
  throw lastError;
}

// Example: only open vendor-type circuit when accounting is allowed
if (endpointPolicy.allowCircuitBreakerAccounting && currentProvider.providerVendorId && statusCode === 524 /* ... */) {
  await recordVendorTypeAllEndpointsTimeout(currentProvider.providerVendorId, currentProvider.providerType);
}

return resolveEndpointPolicy(pathname);
}
} catch {}

Copy link
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] [ERROR-SWALLOWED] Empty catch {} silently suppresses endpoint-policy resolution failures

Why this is a problem: in resolveSessionEndpointPolicy, this new code swallows all exceptions and silently falls back to the default policy:

try {
  const pathname = requestUrl.pathname;
  if (typeof pathname === "string" && pathname.length > 0) {
    return resolveEndpointPolicy(pathname);
  }
} catch {}

return resolveEndpointPolicy("/");

If anything ever goes wrong here (unexpected requestUrl, runtime differences, future refactors), raw passthrough endpoints will quietly revert to the default pipeline (re-enabling preprocessing/circuit-breaker logic) with no log trail.

Suggested fix: either remove the try/catch entirely (preferred — URL.pathname should be safe), or at minimum log the error before falling back.

function resolveSessionEndpointPolicy(requestUrl: URL): EndpointPolicy {
  const pathname = requestUrl.pathname;
  return resolveEndpointPolicy(pathname.length > 0 ? pathname : "/");
}

// OR (if you want to keep a fallback)
// } catch (error) {
//   logger.warn("[ProxySession] Failed to resolve endpoint policy; defaulting to /", { error });
// }

Copy link

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/app/v1/_lib/proxy/forwarder.ts`:
- Around line 1624-1639: 在 ProxyForwarder 中把 raw passthrough
的策略短路判断上移到错误分类之后、重试/切换供应商逻辑之前:在错误分类完成后(即在现有分类分支开始执行重试/provider switch 前的位置)读取
session.getEndpointPolicy() 并用 endpointPolicy.allowRetry (或命名为
allowCircuitBreakerAccounting) 决定是否早退;若不允许则立即跳过重试与 provider
switch,并且在所有计账处(recordFailure、recordSuccess、recordEndpointFailure、recordEndpointSuccess)前加上同一门控判断以避免对
raw passthrough 端点做熔断/成功/失败记账;确保原先仅在 PROVIDER_ERROR 分支的短路逻辑被移除并统一由该集中判断控制。

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR is an XL refactor that introduces EndpointPolicy and rewires core proxy pipeline behavior. There are two critical policy-consistency issues that can cause raw passthrough endpoints to produce unintended side effects (circuit breaker/provider switching) and make endpoint classification failures hard to diagnose.

PR Size: XL

  • Lines changed: 1250
  • Files changed: 22

Split suggestions (recommended for reviewability/risk reduction):

  • PR1: Introduce endpoint-paths.ts + endpoint-policy.ts + session plumbing (getEndpointPolicy())
  • PR2: Wire policy through forwarder/response-handler/guard pipeline (behavioral changes)
  • PR3: Add/adjust tests for policy parity + bypass guarantees

Issues Found

Category Critical High Medium Low
Logic/Bugs 1 0 0 0
Security 0 0 0 0
Error Handling 1 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

  • src/app/v1/_lib/proxy/forwarder.ts:1626 (and vendor-type circuit open at src/app/v1/_lib/proxy/forwarder.ts:1616): raw passthrough policy isn’t enforced before circuit-breaker/provider-switch side effects; allowCircuitBreakerAccounting/allowProviderSwitch are not consistently respected. Confidence: 98
  • src/app/v1/_lib/proxy/session.ts:811: empty catch {} silently swallows endpoint policy resolution errors, causing silent fallback to default policy. Confidence: 95

High Priority Issues (Should Fix)

Review Coverage

  • Logic and correctness
  • Security (OWASP Top 10)
  • Error handling
  • Type safety
  • Documentation accuracy
  • Test coverage
  • Code clarity

Automated review by Codex AI

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

22 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

readonly trackConcurrentRequests: boolean;
readonly bypassRequestFilters: boolean;
readonly bypassForwarderPreprocessing: boolean;
readonly bypassSpecialSettings: boolean;
Copy link

Choose a reason for hiding this comment

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

Unused policy field bypassSpecialSettings

bypassSpecialSettings is defined on the EndpointPolicy interface and set on both policy objects, but it is never read anywhere in the production source code. The same applies to allowProviderSwitch (line 11) and endpointPoolStrictness (line 18) — none of these fields are consumed by any component in this PR. If these are placeholders for future work, consider adding a brief comment indicating that; otherwise they add surface area to the interface without being tested in an integration context.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/endpoint-policy.ts
Line: 16:16

Comment:
**Unused policy field `bypassSpecialSettings`**

`bypassSpecialSettings` is defined on the `EndpointPolicy` interface and set on both policy objects, but it is never read anywhere in the production source code. The same applies to `allowProviderSwitch` (line 11) and `endpointPoolStrictness` (line 18) — none of these fields are consumed by any component in this PR. If these are placeholders for future work, consider adding a brief comment indicating that; otherwise they add surface area to the interface without being tested in an integration context.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +12 to +26
export const STANDARD_ENDPOINT_PATHS = [
V1_ENDPOINT_PATHS.MESSAGES,
V1_ENDPOINT_PATHS.MESSAGES_COUNT_TOKENS,
V1_ENDPOINT_PATHS.RESPONSES,
V1_ENDPOINT_PATHS.RESPONSES_COMPACT,
V1_ENDPOINT_PATHS.CHAT_COMPLETIONS,
V1_ENDPOINT_PATHS.MODELS,
] as const;

export const STRICT_STANDARD_ENDPOINT_PATHS = [
V1_ENDPOINT_PATHS.MESSAGES,
V1_ENDPOINT_PATHS.RESPONSES,
V1_ENDPOINT_PATHS.RESPONSES_COMPACT,
V1_ENDPOINT_PATHS.CHAT_COMPLETIONS,
] as const;
Copy link

Choose a reason for hiding this comment

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

Several exports are unused across the codebase

STANDARD_ENDPOINT_PATHS, STRICT_STANDARD_ENDPOINT_PATHS, isStandardEndpointPath, isStrictStandardEndpointPath, and toV1RoutePath are all exported but never imported or used anywhere in the source or test files. If these are intended for future use, a brief comment would help; otherwise consider removing them to keep the module focused.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/endpoint-paths.ts
Line: 12:26

Comment:
**Several exports are unused across the codebase**

`STANDARD_ENDPOINT_PATHS`, `STRICT_STANDARD_ENDPOINT_PATHS`, `isStandardEndpointPath`, `isStrictStandardEndpointPath`, and `toV1RoutePath` are all exported but never imported or used anywhere in the source or test files. If these are intended for future use, a brief comment would help; otherwise consider removing them to keep the module focused.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

  • Identified and reviewed PR #801 and applied the size/XL label.
  • Left 2 inline review comments on diff lines:
    • src/app/v1/_lib/proxy/forwarder.ts (raw passthrough policy not consistently enforced before circuit-breaker/provider-switch side effects)
    • src/app/v1/_lib/proxy/session.ts (empty catch {} silently swallowing endpoint-policy resolution errors)
  • Submitted the required “Code Review Summary” via gh pr review --comment with severity counts, confidence scores, and XL split suggestions.

@ding113 ding113 merged commit 8f50adc into dev Feb 18, 2026
14 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core 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.

1 participant