- 24 Apr, 2026 9 commits
-
-
keh4l authored
The previous commit only wired stripMessageCacheControl, addMessageCacheBreakpoints, and tool-name obfuscation into applyClaudeCodeOAuthMimicryToBody (used by /chat/completions and /responses). The native /v1/messages path and count_tokens path have their own independent mimicry code blocks and were missed. Now all three entry points share the same D/E/F pipeline: - /v1/messages (gateway_service.go forwardAnthropic) - /v1/messages/count_tokens (gateway_service.go countTokens) - OpenAI compat (applyClaudeCodeOAuthMimicryToBody)
-
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} -
keh4l authored
Three field-level alignments in normalizeClaudeOAuthRequestBody to match real Claude Code CLI traffic byte-for-byte: 1. temperature: previously deleted unconditionally; now passes through client value, defaults to 1 when absent (real CLI always sends temperature, default 1). 2. max_tokens: defaults to 128000 when absent (real CLI default). 3. context_management: when thinking.type is enabled/adaptive and the client did not provide context_management, inject {"edits":[{"type":"clear_thinking_20251015","keep":"all"}]} to mirror real CLI behavior. tool_choice removal is unchanged (Claude Code OAuth credentials do not allow client-supplied tool_choice). Tests updated: - gateway_body_order_test.go: temperature/max_tokens are now expected in output; tool_choice still removed. - gateway_prompt_test.go: system array is now 2 blocks (billing + cc prompt), assertions adjusted. - gateway_anthropic_apikey_passthrough_test.go: same 2-block assertion. -
keh4l authored
Real Claude Code CLI always sends a 2-block system array: [0] {"type":"text", "text":"x-anthropic-billing-header: cc_version=X.Y.Z.{fp}; cc_entrypoint=cli; cch=00000;"} [1] {"type":"text", "text":"You are Claude Code...", "cache_control":{...}} Before this commit, sub2api's mimicry path only produced block [1]. The missing billing block is one of the primary third-party detection signals Anthropic uses for Claude-Code-scoped OAuth tokens. New file gateway_billing_block.go ports the fingerprint algorithm (byte-for-byte from Parrot cc_mimicry.py:compute_fingerprint): pick chars at positions [4,7,20] of the first user text, then `sha256(SALT + chars + cc_version)[:3]`. - claude/constants.go: CLICurrentVersion = "2.1.92" (must match UA) - gateway_billing_block.go: computeClaudeCodeFingerprint + buildBillingAttributionBlockJSON + extractFirstUserText - gateway_service.go: rewriteSystemForNonClaudeCode now emits both blocks in order; cch=00000 is filled in later by signBillingHeaderCCH in buildUpstreamRequest. Downstream compat note: syncBillingHeaderVersion's regex `cc_version=\d+\.\d+\.\d+` only matches the semver triple, leaving the `.{fp}` suffix intact when rewriting in buildUpstreamRequest. -
keh4l authored
Real Claude CLI traffic sends cache_control as `{"type":"ephemeral","ttl":"1h"}`. Our previous payload only sent `{"type":"ephemeral"}`, which is a bytewise mismatch with the official CLI and one more third-party detection signal. Policy: client-provided ttl is always passed through unchanged. Proxy-generated cache_control blocks default to 5m (vs Parrot's 1h) to avoid burning the 1h cache budget on automatic breakpoints while still aligning with the `ttl` field being present. - claude/constants.go: DefaultCacheControlTTL = "5m" - apicompat/types.go: new AnthropicCacheControl type with TTL field; AnthropicTool gains optional CacheControl pointer so the mimicry path can attach a cache breakpoint to tools[-1] later. - service/gateway_service.go: anthropicCacheControlPayload gains TTL; marshalAnthropicSystemTextBlock and rewriteSystemForNonClaudeCode emit ttl=5m by default. -
keh4l authored
The previous commit added FullClaudeCodeMimicryBetas() but the two call sites in buildUpstreamRequest still hardcoded the old 3-token subset. Anthropic now checks the complete set of beta tokens to decide if a request qualifies as Claude Code. Wire them up: - /v1/messages mimic path: requiredBetas = FullClaudeCodeMimicryBetas() - /v1/messages/count_tokens mimic path: same + BetaTokenCounting Haiku models keep the 2-token exemption (BetaOAuth + InterleaveThinking).
-
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) -
keh4l authored
Align Claude Code mimicry constants with the latest real CLI traffic (see Parrot's src/transform/cc_mimicry.py). Anthropic now uses the full set of anthropic-beta tokens to decide whether a request counts as "official Claude Code"; requests missing tokens that real CLI ships today are demoted to third-party usage: Third-party apps now draw from your extra usage, not your plan limits. Changes: - claude/constants.go: add new beta tokens (prompt-caching-scope, effort, redact-thinking, context-management, extended-cache-ttl) and expose FullClaudeCodeMimicryBetas() for the OAuth mimicry path. - claude/constants.go: bump default User-Agent to claude-cli/2.1.92. - identity_service.go: bump defaultFingerprint User-Agent accordingly. No behavioral change for clients that already send a newer UA (fingerprint merge still prefers the incoming value). -
shaw authored
In reconstructResponseOutputFromSSE, text content Marshal/Unmarshal failure previously caused an early return that silently discarded already-extracted image_generation_call outputs. Now serialization errors are tolerated so image results still reach the client.
-
- 23 Apr, 2026 13 commits
-
-
gaoren002 authored
-
erio authored
Revert payment/wechat, sora/claude-max cleanup, fork-only migrations, and cosmetic changes that were brought in by the release sync commit. Keep only channel-monitor related improvements: - PublicSettingsInjectionPayload named struct with drift test - ChannelMonitorRunner graceful shutdown in wire - image_output_price in SupportedModelChip - Simplified buildSelfNavItems in AppSidebar - Gateway WARN logs for 503 branches
-
erio authored
- Extract PublicSettingsInjectionPayload named struct with drift test - Add channel_monitor_default_interval_seconds to SSR injection - Add image_output_price to SupportedModelChip - Simplify AppSidebar buildSelfNavItems (admins see available channels) - Add gateway WARN logs for 503 no-available-accounts branches - Wire ChannelMonitorRunner into provideCleanup for graceful shutdown - Add migrations 130/131 (CC template userid fix + mimicry field cleanup) - Clean up fork-only features (sora, claude max simulation, client affinity) - Remove ~320 obsolete i18n keys - Add codexUsage utility, WechatServiceButton, BulkEditAccountModal - Tidy go.sum
-
erio authored
Service layer (payment_fulfillment_order_not_found_test.go): - TestHandlePaymentNotification_UnknownOrder_ReturnsSentinel: in-memory sqlite ent client, query for a non-existent out_trade_no → errors.Is must recognise ErrOrderNotFound (handler relies on this to ack 200). - TestHandlePaymentNotification_NonSuccessStatus_Skips: non-success notification short-circuits before DB lookup → nil error. - TestErrOrderNotFound_DistinctFromOtherErrors: generic errors must not match the sentinel (prevents silently swallowing DB failures). Handler layer (payment_webhook_handler_test.go): - TestUnknownOrderWebhookAcksWithSuccess: locks the two ingredients the handleNotify ack path depends on — fmt.Errorf %w wrapping preserves errors.Is recognition, and writeSuccessResponse(stripe) returns an empty 200 body that Stripe treats as acknowledged.
-
erio authored
Introduce a sentinel ErrOrderNotFound in the payment service layer so the webhook handler can distinguish "the out_trade_no does not exist in our DB" from other fulfillment failures, and downgrade the former to a WARN log + success response. Background - Providers (Stripe, Alipay, Wxpay, EasyPay, ...) retry webhooks whenever we answer non-2xx. When a webhook endpoint is misconfigured (e.g. a foreign environment points at us) or our orders table has been wiped, we return 500 forever and the provider retries for days, spamming logs. - The old code also collapsed "order not found" and "DB query failed" into the same branch — a DB blip would be reported as "order not found" and swallowed. Service layer (payment_fulfillment.go) - Add `var ErrOrderNotFound = errors.New("payment order not found")`. - In HandlePaymentNotification, distinguish the two error paths: * dbent.IsNotFound(err) → wrap with ErrOrderNotFound so callers can errors.Is(...) it. * anything else → wrap the original err with %w so it still bubbles up as 500 and the provider retries (DB hiccup should be retried). Handler layer (payment_webhook_handler.go) - Before returning 500, check errors.Is(err, service.ErrOrderNotFound): emit a WARN (with provider / outTradeNo / tradeNo for discoverability), then call writeSuccessResponse so the provider sees its expected 2xx body (Stripe empty body / Wxpay JSON / others "success"). - Other errors retain the existing 500 behavior. Monitoring note: because this path now swallows unknown-order webhooks silently from the provider's perspective, the WARN log line is the only signal. Alert on "unknown order, acking to stop retries" if you want visibility into misrouted webhooks or accidental data loss. -
shaw authored
-
james-6-23 authored
P0: - rpm_override 嵌入 Auth Cache Snapshot,消除每请求 DB 查询 (snapshot v6→v7) - 429 RPM 响应返回 Retry-After 头(当前分钟剩余秒数) P1: - ClearAll 按钮直连 DELETE API,带 loading 防重复 - 新增 GET /admin/users/:id/rpm-status 管理员 RPM 用量查询端点 优化: - checkRPM 从级联互斥改为并行取最严,user.rpm_limit 作为全局硬上限始终生效 - Override/Group 变更后自动失效 auth cache - fail-open 语义不变,Redis 故障不阻塞业务
-
shaw authored
-
wx-11 authored
-
wx-11 authored
-
wx-11 authored
-
shaw authored
Limit image download and multipart upload reads to 20MB to prevent unbounded memory allocation from abnormal upstream responses.
-
meteor041 authored
-
- 22 Apr, 2026 18 commits
-
-
IanShaw027 authored
-
shaw authored
-
IanShaw027 authored
-
shaw authored
-
IanShaw027 authored
-
IanShaw027 authored
-
IanShaw027 authored
-
IanShaw027 authored
-
IanShaw027 authored
-
IanShaw027 authored
-
IanShaw027 authored
-
IanShaw027 authored
-
IanShaw027 authored
-
IanShaw027 authored
-
IanShaw027 authored
-
IanShaw027 authored
-
lucas morgan authored
- 移除不安全的类型断言用法 - 补齐响应体关闭与字符串拼接的 lint 问题 - 按 staticcheck 建议简化选择器与条件表达式
-
IanShaw027 authored
-