Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8453458
docs(config): document X-Session-ID guidance for full-history compact…
luispater Nov 10, 2026
13fdcc5
fix(thinking): recognize claude thinking.display updates in summary p…
luispater Nov 10, 2026
c9dad84
fix(home): only renew liveness on heartbeat pong
hkfires Nov 10, 2026
18883a5
fix(usage,auth): propagate request-scoped error rule classification t…
sususu98 Nov 10, 2026
40e5a40
Merge pull request #7397 from sususu98/fix/request-scoped-errors-usag…
sususu98 Nov 10, 2026
0e0cab0
fix(openai): inject placeholder reasoning_content for compat Claude t…
luispater Nov 10, 2026
1855209
Merge pull request #7403 from router-for-me/home
luispater Nov 10, 2026
46d48eb
fix(openai): start responses websocket keepalive after upgrade
luispater Nov 10, 2026
f0c9c2d
fix(claude): embed tool schemas with rejected property names in descr…
luispater Nov 10, 2026
61f1ad5
fix(codex): support local compaction for compatible Responses endpoints
sususu98 Nov 10, 2026
d576416
fix(compaction): strip text format constraints and configuration_upda…
sususu98 Nov 10, 2026
6876a2e
fix(claude): treat cli-bg x-app header as native claude code client
luispater Nov 10, 2026
905637d
Merge pull request #7413 from sususu98/feat/codex-compat-compaction-7406
sususu98 Nov 10, 2026
d1ea84f
docs(readme): remove AICodeMirror sponsorship banner
luispater Nov 10, 2026
effbac1
feat(models): add Gemini 4 Carbon model entry to models.json
luispater Nov 10, 2026
cb70eac
fix(claude): emit anthropic-ratelimit-unified headers on local cooldo…
luispater Nov 10, 2026
949b43b
fix(xai): classify non-stream terminal SSE errors and empty or incomp…
luispater Nov 10, 2026
ece3f52
fix(logging): preserve bounded upstream responses in error logs
luispater Nov 10, 2026
69ed202
fix(registry): restore model listings once bounded cooldown deadlines…
luispater Nov 10, 2026
f77a3c1
fix(meta): pass through explicit client prompt_cache_retention
luispater Nov 11, 2026
9a7e1e6
feat(executor): add sensitive-words-replace and harden Antigravity ob…
luispater Nov 11, 2026
65de937
feat(claude): consume Home credential_options cloak settings
luispater Nov 11, 2026
ecebc71
fix(auth): keep request retry rounds after scheduler plugin reject
luispater Nov 11, 2026
fa11a57
fix(claude): make server-side fallback opt-in and surface serving models
luispater Nov 11, 2026
9d7d012
fix(auth): preserve large weighted pool fairness and force cooldowns
luispater Nov 11, 2026
8bba7a7
docs(README): add AICodeMirror sponsor banner to EN, CN, and JA readmes
luispater Nov 11, 2026
b7c3827
chore: merge upstream/main through 8bba7a77
jiroamato Nov 12, 2026
1cebcb3
test: fix load-sensitive flakes and record reviewed copy in upstream …
jiroamato Nov 12, 2026
3ea23f1
test: allow for Windows temp-file open stalls in incomplete-body test
jiroamato Nov 12, 2026
da48fbb
test: fix order assumption in same-revision batch wait test
jiroamato Nov 12, 2026
19e267f
test: drop Devin OAuth test bound change in favor of the root-cause fix
jiroamato Nov 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 37 additions & 11 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,13 @@ routing:
# CPA preserves the retained credential and concurrency slot instead of redispatching each turn.
# For OAuth/file credentials, add a top-level numeric "weight" field to the auth JSON.
# Enable universal session-sticky routing for all clients.
# Explicit Claude Code, Codex, OpenCode, and pi session headers are preferred,
# followed by prompt_cache_key, Responses conversation IDs, legacy body IDs,
# execution or derived session identity, and the existing first-message hash fallback.
# Explicit Claude Code, Codex, OpenCode, pi, and generic (X-Session-ID) session headers
# are preferred, followed by prompt_cache_key, Responses conversation IDs, legacy body IDs,
# execution session identity, Merkle LCP prefix/compaction matching, derived session
# identity, and the existing first-message hash fallback.
# LCP compaction matching requires at least 2 shared trailing turns; clients whose
# compaction replaces all earlier turns with a summary should send X-Session-ID (or
# another explicit session ID), or affinity starts a new binding at every compaction.
# Automatic failover is always enabled when bound auth becomes unavailable.
# An established binding outranks credential priority: once a session is bound, that
# credential is kept even if a higher-priority credential recovers. Credential priority
Expand Down Expand Up @@ -295,7 +299,7 @@ requests:
# default: # Default rules only set parameters when they are missing in the payload.
# - models:
# - name: "gemini-2.5-pro" # Supports wildcards (e.g., "gemini-*")
# protocol: "gemini" # restricts the rule to a specific protocol, options: openai, gemini, claude, codex, antigravity
# protocol: "gemini" # restricts the rule to a specific protocol, options: openai, gemini, claude, codex, antigravity, meta
# from-protocol: "responses" # restricts the rule to the source protocol, options: openai, responses, gemini, claude
# headers: # all configured request headers must match; values support "*" wildcards
# X-Client-Tier: "tenant-*-region-*"
Expand All @@ -309,10 +313,15 @@ requests:
# - "metadata.disable_payload"
# params: # JSON path (gjson/sjson syntax) -> value
# "generationConfig.thinkingConfig.thinkingBudget": 32768
# - models:
# - name: "muse-*"
# protocol: "meta"
# params:
# prompt_cache_retention: "24h" # opt into Meta's 24h prompt cache when the client omits prompt_cache_retention
# default-raw: # Default raw rules set parameters using raw JSON when missing (must be valid JSON).
# - models:
# - name: "gemini-2.5-pro" # Supports wildcards (e.g., "gemini-*")
# protocol: "gemini" # restricts the rule to a specific protocol, options: openai, gemini, claude, codex, antigravity
# protocol: "gemini" # restricts the rule to a specific protocol, options: openai, gemini, claude, codex, antigravity, meta
# params: # JSON path (gjson/sjson syntax) -> raw JSON value (strings are used as-is, must be valid JSON)
# "generationConfig.responseJsonSchema": "{\"type\":\"object\",\"properties\":{\"answer\":{\"type\":\"string\"}}}"
# override: # Override rules always set parameters, overwriting any existing values.
Expand All @@ -325,7 +334,7 @@ requests:
# service_tier: priority
# - models:
# - name: "gpt-*" # Supports wildcards (e.g., "gpt-*")
# protocol: "codex" # restricts the rule to a specific protocol, options: openai, gemini, claude, codex, antigravity
# protocol: "codex" # restricts the rule to a specific protocol, options: openai, gemini, claude, codex, antigravity, meta
# params: # JSON path (gjson/sjson syntax) -> value
# "reasoning.effort": "high"
# - models:
Expand All @@ -336,13 +345,13 @@ requests:
# override-raw: # Override raw rules always set parameters using raw JSON (must be valid JSON).
# - models:
# - name: "gpt-*" # Supports wildcards (e.g., "gpt-*")
# protocol: "codex" # restricts the rule to a specific protocol, options: openai, gemini, claude, codex, antigravity
# protocol: "codex" # restricts the rule to a specific protocol, options: openai, gemini, claude, codex, antigravity, meta
# params: # JSON path (gjson/sjson syntax) -> raw JSON value (strings are used as-is, must be valid JSON)
# "response_format": "{\"type\":\"json_schema\",\"json_schema\":{\"name\":\"answer\",\"schema\":{\"type\":\"object\"}}}"
# filter: # Filter rules remove specified parameters from the payload.
# - models:
# - name: "gemini-2.5-pro" # Supports wildcards (e.g., "gemini-*")
# protocol: "gemini" # restricts the rule to a specific protocol, options: openai, gemini, claude, codex, antigravity
# protocol: "gemini" # restricts the rule to a specific protocol, options: openai, gemini, claude, codex, antigravity, meta
# params: # JSON paths (gjson/sjson syntax) to remove from the payload
# - "generationConfig.thinkingConfig.thinkingBudget"
# - "generationConfig.responseJsonSchema"
Expand Down Expand Up @@ -488,6 +497,16 @@ upstream:
# Default false keeps the translation and catalog behavior that predate tool_search support.
tool-search: false

# When true, inject Anthropic server-side refusal fallbacks on cloaked Claude requests
# when the request body does not already specify fallbacks (claude-opus-5-5 -> claude-opus-4-8,
# claude-fable-5-1 -> claude-opus-5). Default false never opts requests into cross-model
# fallback unless the caller or requests.payload rules explicitly provide fallbacks.
# When fallback occurs, translated OpenAI Chat Completions and Responses payloads report
# the actual serving model and attach structured fallback metadata (choices[0].delta.fallback /
# choices[0].message.fallback and fallbacks for Chat; response.fallback and response.fallbacks
# for Responses).
server-side-fallback: false

# Days to retain the cloaked currentDate pin per session in in-memory mode
# (server.kv-store.disabled: true without Home), so an idle gap or a credential
# switch cannot re-anchor the date and invalidate earlier thinking signatures and the
Expand Down Expand Up @@ -744,6 +763,7 @@ upstream:
# max-context-length: 1048576 # optional: override Codex client context window metadata
# force-mapping: true # optional: rewrite response model fields back to the alias
# # Preserve thinking blocks with empty signatures for compatible upstreams.
# # Also handle compaction_trigger requests with local summary compaction instead of forwarding them upstream.
# # client.codex.optimize-multi-agent-v2 applies to both OAuth and API-key credentials.
# is-compat: false
# # Optional: enable configuration_update for this API-key model; omitted defaults to false.
Expand Down Expand Up @@ -807,7 +827,7 @@ upstream:
# rebuild-mid-system-message: false # optional: default is false; when true, move messages with role "system" into the top-level Claude system field
# native-tool-search: false # optional: default is false; when true, trust this key's custom base-url with Anthropic native tool search (defer_loading + tool_reference) for Codex tool_search; requires upstream.claude.tool-search: true
# cloak: # optional: explicitly enable request cloaking for non-Claude-Code clients
# mode: "auto" # "auto" (default inside this block): cloak only when client is not Claude Code
# mode: "auto" # omitted inherits shared default; explicit "auto": cloak only when client is not Claude Code
# # "always": cloak every unconfirmed client; confirmed native Claude Code still passes through
# # "never" / "uncloak": never apply cloaking; on non-CLI API keys, also strip incoming Claude Code identity/billing attribution
# # This "cloak" block applies to this api-keys.claude key entry only. For Claude OAuth
Expand All @@ -820,6 +840,8 @@ upstream:
# sensitive-words: # optional: words to obfuscate with zero-width characters
# - "API"
# - "proxy"
# sensitive-words-replace: # optional: literal sensitive-word replacements ("target": "replacement")
# "Claude Code": "Claude"
# cache-user-id: true # optional: default is false; set true to reuse cached user_id per API key instead of generating a random one each request
# # Every custom tool on a cloaked OAuth request automatically uses a caller-stable opaque mcp__<server>__<tool> alias.
#
Expand Down Expand Up @@ -1192,9 +1214,11 @@ oauth:

# Antigravity provider behavior.
antigravity:
# sensitive-words: # optional: words to obfuscate with zero-width characters in system instructions
# sensitive-words: # optional: words to obfuscate with combining grapheme joiners (U+034F) in system instructions
# - "API"
# - "proxy"
# sensitive-words-replace: # optional: literal sensitive-word replacements ("target": "replacement") in system instructions
# "You are Codex, an agent based on GPT-5.": "You are a helpful coding assistant."
# connection-pool: # optional: upstream HTTP connection pool settings
# enabled: false # whether to enable connection pooling (default: false)
# idle-conn-timeout: "30s" # idle keep-alive timeout (default: 30s, capped at 210s)
Expand All @@ -1214,9 +1238,11 @@ oauth:

# Devin provider behavior.
# devin:
# sensitive-words: # optional: words to obfuscate with zero-width characters in system instructions and prompts
# sensitive-words: # optional: words to remove matching lines in system prompts
# - "API"
# - "proxy"
# sensitive-words-replace: # optional: literal sensitive-word replacements ("target": "replacement") in system prompts
# "Claude Code": "Claude"

# Image and video behavior.
multimedia:
Expand Down
10 changes: 8 additions & 2 deletions internal/api/handlers/management/auth_files_cooldown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1237,8 +1237,14 @@ func (s *targetedRecoveryTestCodexOAuthService) CreateTokenStorage(bundle *codex

func pollOAuthStatusUntilDone(t *testing.T, router http.Handler, state string) map[string]any {
t.Helper()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
// The OAuth flow polls for its callback file every 500ms, so a slow CI host
// needs a generous deadline. Pause between polls so this loop does not
// starve the flow goroutine it is waiting on.
deadline := time.Now().Add(15 * time.Second)
for first := true; time.Now().Before(deadline); first = false {
if !first {
time.Sleep(10 * time.Millisecond)
}
w := httptest.NewRecorder()
router.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/v8/management/oauth/status?state="+state, nil))
if w.Code != http.StatusOK {
Expand Down
49 changes: 49 additions & 0 deletions internal/api/handlers/management/config_v8_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -975,3 +975,52 @@ api-keys:
}
}
}

func TestConfigV8_SensitiveWordsReplace(t *testing.T) {
gin.SetMode(gin.TestMode)
path := filepath.Join(t.TempDir(), "config.yaml")
if err := os.WriteFile(path, []byte("server: {port: 8317}\n"), 0600); err != nil {
t.Fatal(err)
}
cfg, err := config.LoadConfig(path)
if err != nil {
t.Fatal(err)
}
h := &Handler{cfg: cfg, configFilePath: path}
router := gin.New()
router.GET("/v8/management/config/*path", h.ConfigV8)
router.PUT("/v8/management/config/*path", h.ConfigV8)
router.PATCH("/v8/management/config/*path", h.ConfigV8)
router.DELETE("/v8/management/config/*path", h.ConfigV8)

for _, endpoint := range []string{
"/v8/management/config/oauth/providers/antigravity/sensitive-words-replace",
"/v8/management/config/oauth/providers/devin/sensitive-words-replace",
} {
w := httptest.NewRecorder()
router.ServeHTTP(w, httptest.NewRequest(http.MethodPut, endpoint, strings.NewReader(`{"You are Codex, an agent based on GPT-5.":"You are a helpful coding assistant."}`)))
if w.Code != http.StatusOK {
t.Fatalf("PUT %s: status=%d body=%s", endpoint, w.Code, w.Body.String())
}
getRec := httptest.NewRecorder()
router.ServeHTTP(getRec, httptest.NewRequest(http.MethodGet, endpoint, nil))
if getRec.Code != http.StatusOK || !strings.Contains(getRec.Body.String(), "You are a helpful coding assistant.") {
t.Fatalf("GET %s: status=%d body=%s", endpoint, getRec.Code, getRec.Body.String())
}
delRec := httptest.NewRecorder()
router.ServeHTTP(delRec, httptest.NewRequest(http.MethodDelete, endpoint, nil))
if delRec.Code != http.StatusOK {
t.Fatalf("DELETE %s: status=%d body=%s", endpoint, delRec.Code, delRec.Body.String())
}
}

claudePut := httptest.NewRecorder()
router.ServeHTTP(claudePut, httptest.NewRequest(
http.MethodPut,
"/v8/management/config/api-keys/claude",
strings.NewReader(`[{"name":"claude-1","keys":[{"api-key":"sk-ant-1","cloak":{"sensitive-words-replace":{"Claude Code":"Claude"}}}] }]`),
))
if claudePut.Code != http.StatusOK {
t.Fatalf("PUT api-keys/claude: status=%d body=%s", claudePut.Code, claudePut.Body.String())
}
}
4 changes: 4 additions & 0 deletions internal/api/middleware/request_logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ func RequestLoggingMiddleware(logger logging.RequestLogger) gin.HandlerFunc {
wrapper.ginContext = c
if !loggerEnabled {
wrapper.logOnErrorOnly = true
deferredAPICapture := logging.NewDeferredAPICapture()
wrapper.deferredAPICapture = deferredAPICapture
c.Request = c.Request.WithContext(logging.WithDeferredAPICapture(c.Request.Context(), deferredAPICapture))
c.Set(logging.DeferredAPIResponseContextKey, deferredAPICapture)
}
c.Writer = wrapper
attachRequestLogSources(c, logger, loggerEnabled && hasMatchedRoute)
Expand Down
Loading
Loading