1. 29 Apr, 2026 1 commit
    • shaw's avatar
      fix(vertex): audit fixes for Vertex Service Account feature (#1977) · 93d91e20
      shaw authored
      - Security: force token_uri to Google default, preventing SSRF via crafted service account JSON
      - Dedup: extract shared getVertexServiceAccountAccessToken() to eliminate ~35 lines of duplication between ClaudeTokenProvider and GeminiTokenProvider
      - Fix: apply model mapping + Vertex model ID normalization in forward_as_responses and forward_as_chat_completions paths
      - Fix: exclude service_account from AI Studio endpoint selection (Vertex cannot serve generativelanguage.googleapis.com)
      - Feature: add model restriction/mapping UI for service_account in EditAccountModal
      - Dedup: extract VERTEX_LOCATION_OPTIONS to shared constants
      - i18n: replace all hardcoded Chinese strings in Vertex UI with translation keys
      93d91e20
  2. 24 Apr, 2026 2 commits
    • keh4l's avatar
      feat(gateway): port Parrot tool-name obfuscation + message cache breakpoints · 6e12578b
      keh4l authored
      Implements the remaining three parity items with Parrot cc_mimicry:
      
        D) Tool-name obfuscation
           - Dynamic mapping when tools.length > 5 (matches Parrot threshold).
             Fake names follow {prefix}{name[:3]}{i:02d} (e.g. 'manage_bas00').
             Go port of random.Random(hash(tuple(names))) uses fnv64a seed +
             math/rand; byte-exact reproduction is impossible (Python hash vs
             Go hash), but the two invariants that matter are preserved:
               * same input tool_names yield identical mapping (cache hit)
               * prefix pool is shuffled (names look distributed)
           - Static prefix map (sessions_ -> cc_sess_, session_ -> cc_ses_)
             applied as fallback, matching Parrot TOOL_NAME_REWRITES verbatim.
           - Server tools (web_search_20250305, computer_*, etc.) are NOT
             renamed; only type=='function' and type=='custom' tools are.
           - tool_choice.name is rewritten in sync (only when type=='tool').
           - Response side: bytes-level replace on every SSE chunk / JSON
             body at 6 injection points (standard stream/non-stream,
             passthrough stream/non-stream, chat_completions stream +
             non-stream, responses stream + non-stream). Reverse mapping
             applied longest-fake-name-first to prevent substring conflicts
             (parity with Parrot _restore_tool_names_in_chunk).
           - tool_choice is no longer unconditionally deleted in
             normalizeClaudeOAuthRequestBody — Parrot passes it through.
      
        E) tools[-1] cache_control breakpoint
           - Injected as {type:ephemeral, ttl:<DefaultCacheControlTTL>} when
             the last tool has no cache_control. Client-provided ttl is
             passed through unchanged (repo-wide policy).
      
        F) messages cache_control strategy
           - stripMessageCacheControl removes every client-provided
             messages[*].content[*].cache_control (multi-turn stability).
           - addMessageCacheBreakpoints then injects two stable breakpoints:
             (1) last message, and (2) second-to-last user turn when
             messages.length >= 4.
           - Combined with the system block breakpoint and tools[-1]
             breakpoint, this gives exactly the 4 breakpoints Anthropic
             allows per request.
      
      Non-trivial implementation details to be aware of when rebasing:
      
        * Two new files, no upstream collision:
            gateway_tool_rewrite.go       (D + E algorithms)
            gateway_messages_cache.go     (F strip + breakpoints)
        * Two new feature calls bolted onto the tail of
          applyClaudeCodeOAuthMimicryToBody in gateway_service.go — rebase
          conflicts will be ~10 lines maximum.
        * Response-side injection points all wrap their existing write with
          reverseToolNamesIfPresent(c, ...), preserving original behavior
          when no mapping is stored (static prefix rollback still runs).
        * Non-stream chat/responses switched from c.JSON to
          json.Marshal + c.Data so bytes-level replace is possible.
        * Retry bodies (FilterThinkingBlocksForRetry,
          FilterSignatureSensitiveBlocksForRetry, RectifyThinkingBudget)
          only prune blocks — they preserve the already-obfuscated tool
          names, so no extra mapping re-application is needed.
      
      Manual QA: end-to-end scenario verified with 6 tools (above threshold)
      and tool_choice.type=='tool'. Obfuscation + restore roundtrip shown
      in test logs; then removed the temp test file.
      
      Tests (16 new):
        - buildDynamicToolMap stability + below-threshold guard
        - sanitizeToolName precedence (dynamic > static)
        - restoreToolNamesInBytes longest-first + static rollback
        - applyToolNameRewriteToBody skips server tools + syncs tool_choice
        - applyToolsLastCacheBreakpoint defaults to 5m + passes client ttl
        - stripMessageCacheControl + addMessageCacheBreakpoints in the
          1/4/string-content cases + second-to-last user turn selection
        - buildToolNameRewriteFromBody ReverseOrdered is desc-by-fake-length
        - fake name shape follows Parrot {prefix}{head3}{i:02d}
      6e12578b
    • keh4l's avatar
      fix(gateway): apply full Claude Code mimicry on /chat/completions and /responses · b5467d61
      keh4l authored
      Before: the OpenAI-compat forwarders only called injectClaudeCodePrompt,
      which prepends the Claude Code banner but leaves the rest of the body
      in its original non-Claude-Code shape. The codebase already admits this
      is insufficient (see the comment on rewriteSystemForNonClaudeCode in
      gateway_service.go: "仅前置追加 Claude Code 提示词无法通过检测").
      
      Effect: OAuth accounts served through /v1/chat/completions or /v1/responses
      were detected as third-party apps and bled plan quota with:
      
          Third-party apps now draw from your extra usage, not your plan limits.
      
      Fix:
        - apicompat.AnthropicRequest: add Metadata json.RawMessage so metadata
          survives the OpenAI->Anthropic->Marshal round trip; without it the
          downstream rewrite has no user_id to work with.
        - service: extract applyClaudeCodeOAuthMimicryToBody, a ParsedRequest-free
          variant of the /v1/messages mimicry pipeline
          (rewriteSystemForNonClaudeCode + normalizeClaudeOAuthRequestBody +
          metadata.user_id injection) so the OpenAI-compat forwarders can reuse it.
        - service: add buildOAuthMetadataUserIDFromBody + hashBodyForSessionSeed
          for the same reason (no ParsedRequest at the call site).
        - ForwardAsChatCompletions / ForwardAsResponses: replace the 3-line
          prompt-prepend with the full mimicry pipeline.
        - applyClaudeCodeMimicHeaders: set x-client-request-id per-request
          (real Claude CLI always does); missing/duplicated values are one more
          third-party fingerprint signal.
      
      No change to the native /v1/messages path: it already called the full
      pipeline, we only lift those helpers into a reusable function.
      
      Tests:
        - go build ./... passes
        - go test ./internal/service/... ./internal/pkg/apicompat/... passes
        - lsp_diagnostics clean on all touched files
        - pre-existing failures in internal/config are unrelated (env-sensitive
          tests that also fail on upstream main)
      b5467d61
  3. 27 Mar, 2026 1 commit
    • shaw's avatar
      feat(tls-fingerprint): 新增 TLS 指纹 Profile 数据库管理及代码质量优化 · 1854050d
      shaw authored
      新增功能:
      - 新增 TLS 指纹 Profile CRUD 管理(Ent schema + 迁移 + Admin API + 前端管理界面)
      - 支持账号绑定数据库中的自定义 TLS Profile,或随机选择(profile_id=-1)
      - HTTPUpstream.DoWithTLS 接口从 bool 改为 *tlsfingerprint.Profile,支持按账号指定 Profile
      - AccountUsageService 注入 TLSFingerprintProfileService,统一 usage 场景与网关的 Profile 解析逻辑
      
      代码优化:
      - 删除已被 TLSFingerprintProfileService 完全取代的 registry.go 死代码(418 行)
      - 提取 3 个 dialer 的重复 TLS 握手逻辑为 performTLSHandshake() 共用函数
      - 修复 GetTLSFingerprintProfileID 缺少 json.Number 处理的 bug
      - gateway_service.Forward 中 ResolveTLSProfile 从重试循环内重复调用改为预解析局部变量
      - 删除冗余的 buildClientHelloSpec() 单行 wrapper 和 int64(e.ID) 无效转换
      - tls_fingerprint_profile_cache.go 日志从 log.Printf 改为 slog 结构化日志
      - dialer_capture_test.go 添加 //go:build integration 标签,防止 CI 失败
      - 去重 TestProfileExpectation 类型至共享 test_types_test.go
      - 修复 9 个测试文件缺少 tlsfingerprint import 的编译错误
      - 修复 error_policy_integration_test.go 中 handleError 回调签名被错误替换的问题
      1854050d
  4. 24 Mar, 2026 1 commit
  5. 23 Mar, 2026 1 commit
    • Ethan0x0000's avatar
      feat(service): add ForwardAsResponses/ForwardAsChatCompletions on GatewayService · 4321adab
      Ethan0x0000 authored
      New forwarding methods on GatewayService for Anthropic platform groups:
      
      - ForwardAsResponses: accept Responses body → convert to Anthropic →
        forward to upstream → convert response back to Responses format.
        Supports both streaming (SSE event-by-event conversion) and buffered
        (accumulate then convert) response modes.
      - ForwardAsChatCompletions: chain CC→Responses→Anthropic for request,
        Anthropic→Responses→CC for response. Streaming uses dual state machine
        chain with [DONE] marker.
      
      Both methods reuse existing GatewayService infrastructure:
      buildUpstreamRequest, Claude Code mimicry, cache control enforcement,
      model mapping, and return UpstreamFailoverError for handler-level retry.
      4321adab