mirror of
https://gh-proxy.org/https://github.com/klinsmaya/CLIProxyAPI.git
synced 2026-11-11 02:46:47 +08:00
Add Grok-native reasoning replay for Claude-sourced xAI Responses requests. Replay state is scoped by model and session, normalized to reasoning/function_call/custom_tool_call items, and injected with the same tool-call alignment semantics used by Codex. Include the minimal Grok encrypted_content validator needed on upstream/dev so replay cache entries cannot accept Codex/GPT, Gemini, or Claude signature shapes. Cache completed output items from HTTP, streaming, and WebSocket xAI responses. Cover two-turn Claude replay, tool_result continuity, valid Grok cache storage, and rejection of Codex-shaped encrypted_content.
172 lines
5.2 KiB
Go
172 lines
5.2 KiB
Go
package executor
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
internalcache "github.com/router-for-me/CLIProxyAPI/v7/internal/cache"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/signature"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
type xaiReasoningReplayScope struct {
|
|
modelName string
|
|
sessionKey string
|
|
}
|
|
|
|
var getXAIReasoningReplayItemsRequired = internalcache.GetXAIReasoningReplayItemsRequired
|
|
|
|
func (s xaiReasoningReplayScope) valid() bool {
|
|
return strings.TrimSpace(s.modelName) != "" && strings.TrimSpace(s.sessionKey) != ""
|
|
}
|
|
|
|
func applyXAIReasoningReplayCacheRequired(ctx context.Context, from sdktranslator.Format, req cliproxyexecutor.Request, opts cliproxyexecutor.Options, body []byte) ([]byte, xaiReasoningReplayScope, error) {
|
|
scope := xaiReasoningReplayScopeFromRequest(ctx, from, req, opts, body)
|
|
if !scope.valid() {
|
|
return body, scope, nil
|
|
}
|
|
items, ok, errReplay := getXAIReasoningReplayItemsRequired(ctx, scope.modelName, scope.sessionKey)
|
|
if errReplay != nil {
|
|
log.Warnf("xai reasoning replay cache read failed: %v", errReplay)
|
|
return body, scope, nil
|
|
}
|
|
if !ok {
|
|
return body, scope, nil
|
|
}
|
|
items = filterXAIReasoningReplayItemsForInput(body, items)
|
|
if len(items) == 0 {
|
|
return body, scope, nil
|
|
}
|
|
updated, ok := insertCodexReasoningReplayItems(body, items)
|
|
if !ok {
|
|
return body, scope, nil
|
|
}
|
|
return updated, scope, nil
|
|
}
|
|
|
|
func xaiReasoningReplayScopeFromRequest(ctx context.Context, from sdktranslator.Format, req cliproxyexecutor.Request, opts cliproxyexecutor.Options, body []byte) xaiReasoningReplayScope {
|
|
if !xaiReasoningReplayEnabledForSource(from) {
|
|
return xaiReasoningReplayScope{}
|
|
}
|
|
return xaiReasoningReplayScope{
|
|
modelName: thinking.ParseSuffix(req.Model).ModelName,
|
|
sessionKey: codexReasoningReplaySessionKey(ctx, from, req, opts, body),
|
|
}
|
|
}
|
|
|
|
func xaiReasoningReplayEnabledForSource(from sdktranslator.Format) bool {
|
|
return sourceFormatEqual(from, sdktranslator.FormatClaude)
|
|
}
|
|
|
|
func xaiInputHasValidReasoningEncryptedContent(body []byte) bool {
|
|
input := gjson.GetBytes(body, "input")
|
|
if !input.IsArray() {
|
|
return false
|
|
}
|
|
for _, item := range input.Array() {
|
|
if strings.TrimSpace(item.Get("type").String()) != "reasoning" {
|
|
continue
|
|
}
|
|
encryptedContent := item.Get("encrypted_content")
|
|
if encryptedContent.Type != gjson.String {
|
|
continue
|
|
}
|
|
if _, err := signature.InspectGrokEncryptedContent(encryptedContent.String()); err == nil {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func filterXAIReasoningReplayItemsForInput(body []byte, items [][]byte) [][]byte {
|
|
input := gjson.GetBytes(body, "input")
|
|
if !input.IsArray() {
|
|
return nil
|
|
}
|
|
|
|
hasInputReasoning := xaiInputHasValidReasoningEncryptedContent(body)
|
|
existingCalls := make(map[string]bool)
|
|
existingOutputs := make(map[string]bool)
|
|
for _, inputItem := range input.Array() {
|
|
itemType := strings.TrimSpace(inputItem.Get("type").String())
|
|
if itemType == "function_call_output" || itemType == "custom_tool_call_output" {
|
|
callID := strings.TrimSpace(inputItem.Get("call_id").String())
|
|
if callID != "" {
|
|
for _, candidate := range codexReplayComparableCallIDs(callID) {
|
|
existingOutputs[candidate] = true
|
|
}
|
|
}
|
|
}
|
|
for _, key := range codexReplayToolCallKeys(inputItem) {
|
|
existingCalls[key] = true
|
|
}
|
|
}
|
|
|
|
filtered := make([][]byte, 0, len(items))
|
|
for _, item := range items {
|
|
itemResult := gjson.ParseBytes(item)
|
|
switch strings.TrimSpace(itemResult.Get("type").String()) {
|
|
case "reasoning":
|
|
if hasInputReasoning {
|
|
continue
|
|
}
|
|
case "function_call", "custom_tool_call":
|
|
keys := codexReplayToolCallKeys(itemResult)
|
|
if len(keys) == 0 || codexReplayAnyToolCallKeyExists(existingCalls, keys) {
|
|
continue
|
|
}
|
|
hasMatchingOutput := false
|
|
callID := strings.TrimSpace(itemResult.Get("call_id").String())
|
|
if callID != "" {
|
|
for _, candidate := range codexReplayComparableCallIDs(callID) {
|
|
if existingOutputs[candidate] {
|
|
hasMatchingOutput = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !hasMatchingOutput {
|
|
continue
|
|
}
|
|
for _, key := range keys {
|
|
existingCalls[key] = true
|
|
}
|
|
default:
|
|
continue
|
|
}
|
|
filtered = append(filtered, item)
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func cacheXAIReasoningReplayFromCompleted(ctx context.Context, scope xaiReasoningReplayScope, completedData []byte) {
|
|
if !scope.valid() {
|
|
return
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
output := gjson.GetBytes(completedData, "response.output")
|
|
if !output.IsArray() {
|
|
return
|
|
}
|
|
items := make([][]byte, 0, len(output.Array()))
|
|
for _, item := range output.Array() {
|
|
switch strings.TrimSpace(item.Get("type").String()) {
|
|
case "reasoning", "function_call", "custom_tool_call":
|
|
items = append(items, []byte(item.Raw))
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
if !internalcache.CacheXAIReasoningReplayItemsBestEffort(ctx, scope.modelName, scope.sessionKey, items) {
|
|
if errDelete := internalcache.DeleteXAIReasoningReplayItemRequired(ctx, scope.modelName, scope.sessionKey); errDelete != nil {
|
|
log.Warnf("xai reasoning replay cache delete failed after completed cache store failed: %v", errDelete)
|
|
}
|
|
}
|
|
}
|