feat(executor): implement agent-scoped cache key isolation and enhance replay handling

- Introduced agent-specific cache key isolation to ensure distinct prompt caching across agents.
- Improved reasoning replay handling for Claude models, including cumulative tool turn restoration and validation.
- Enhanced HTTP and WebSocket session consistency during execution scope and cache replay synchronization.
- Added new unit tests and extended coverage for prompt caching, agent isolation, and replay cache functionality.

Closes: #4352
This commit is contained in:
Luis Pater
2026-07-18 02:18:09 +08:00
parent da087d7fad
commit baffbe2cc7
12 changed files with 1285 additions and 98 deletions

View File

@@ -307,3 +307,62 @@ func TestCodexExecutorCacheHelper_ClaudeUsesSessionHeader(t *testing.T) {
t.Fatalf("same Claude Code session header produced different prompt_cache_key: first=%q second=%q", firstKey, secondKey)
}
}
func TestCodexExecutorCacheHelper_ClaudeAgentScopeUsesResolvedModelAcrossHTTPAndWebsocket(t *testing.T) {
executor := &CodexExecutor{}
url := "https://example.com/responses"
req := cliproxyexecutor.Request{
Model: "requested-alias-high",
Payload: []byte(`{"model":"requested-alias","messages":[{"role":"user","content":"hello"}]}`),
}
rootHeaders := http.Header{}
rootHeaders.Set(helps.ClaudeCodeSessionHeader, "resolved-model-session")
childHeaders := rootHeaders.Clone()
childHeaders.Set(helps.ClaudeCodeAgentHeader, "agent-a")
rawJSON := []byte(`{"model":"gpt-5.4","stream":true}`)
rootRequest, _, _, errRoot := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, nil, req, req.Payload, rawJSON, rootHeaders)
if errRoot != nil {
t.Fatalf("root cacheHelper error: %v", errRoot)
}
rootBody, errReadRoot := io.ReadAll(rootRequest.Body)
if errReadRoot != nil {
t.Fatalf("read root body: %v", errReadRoot)
}
rootKey := gjson.GetBytes(rootBody, "prompt_cache_key").String()
childRequest, _, _, errChild := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, nil, req, req.Payload, rawJSON, childHeaders)
if errChild != nil {
t.Fatalf("child cacheHelper error: %v", errChild)
}
childBody, errReadChild := io.ReadAll(childRequest.Body)
if errReadChild != nil {
t.Fatalf("read child body: %v", errReadChild)
}
childKey := gjson.GetBytes(childBody, "prompt_cache_key").String()
if rootKey == "" || childKey == "" || rootKey == childKey {
t.Fatalf("agent prompt keys are not isolated: root=%q child=%q", rootKey, childKey)
}
aliasReq := req
aliasReq.Model = "another-local-alias-low"
aliasRequest, _, _, errAlias := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, nil, aliasReq, aliasReq.Payload, rawJSON, childHeaders)
if errAlias != nil {
t.Fatalf("alias cacheHelper error: %v", errAlias)
}
aliasBody, errReadAlias := io.ReadAll(aliasRequest.Body)
if errReadAlias != nil {
t.Fatalf("read alias body: %v", errReadAlias)
}
if aliasKey := gjson.GetBytes(aliasBody, "prompt_cache_key").String(); aliasKey != childKey {
t.Fatalf("resolved model key fragmented by request alias: first=%q alias=%q", childKey, aliasKey)
}
websocketBody, _, errWebsocket := applyCodexPromptCacheHeadersWithContext(context.Background(), sdktranslator.FromString("claude"), aliasReq, rawJSON, childHeaders)
if errWebsocket != nil {
t.Fatalf("websocket prompt cache error: %v", errWebsocket)
}
if websocketKey := gjson.GetBytes(websocketBody, "prompt_cache_key").String(); websocketKey != childKey {
t.Fatalf("HTTP/WebSocket prompt keys differ: http=%q websocket=%q", childKey, websocketKey)
}
}