mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 12:20:52 +08:00
187 lines
6.5 KiB
Go
187 lines
6.5 KiB
Go
// Package cli provides request translation functionality for Gemini CLI API.
|
|
// It handles the conversion and formatting of CLI tool responses, specifically
|
|
// transforming between different JSON formats to ensure proper conversation flow
|
|
// and API compatibility. The package focuses on intelligently grouping function
|
|
// calls with their corresponding responses, converting from linear format to
|
|
// grouped format where function calls and responses are properly associated.
|
|
package cli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/tidwall/gjson"
|
|
"github.com/tidwall/sjson"
|
|
)
|
|
|
|
// FunctionCallGroup represents a group of function calls and their responses
|
|
type FunctionCallGroup struct {
|
|
ModelContent map[string]interface{}
|
|
FunctionCalls []gjson.Result
|
|
ResponsesNeeded int
|
|
}
|
|
|
|
// FixCLIToolResponse performs sophisticated tool response format conversion and grouping.
|
|
// This function transforms the CLI tool response format by intelligently grouping function calls
|
|
// with their corresponding responses, ensuring proper conversation flow and API compatibility.
|
|
// It converts from a linear format (1.json) to a grouped format (2.json) where function calls
|
|
// and their responses are properly associated and structured.
|
|
func FixCLIToolResponse(input string) (string, error) {
|
|
// Parse the input JSON to extract the conversation structure
|
|
parsed := gjson.Parse(input)
|
|
|
|
// Extract the contents array which contains the conversation messages
|
|
contents := parsed.Get("request.contents")
|
|
if !contents.Exists() {
|
|
// log.Debugf(input)
|
|
return input, fmt.Errorf("contents not found in input")
|
|
}
|
|
|
|
// Initialize data structures for processing and grouping
|
|
var newContents []interface{} // Final processed contents array
|
|
var pendingGroups []*FunctionCallGroup // Groups awaiting completion with responses
|
|
var collectedResponses []gjson.Result // Standalone responses to be matched
|
|
|
|
// Process each content object in the conversation
|
|
// This iterates through messages and groups function calls with their responses
|
|
contents.ForEach(func(key, value gjson.Result) bool {
|
|
role := value.Get("role").String()
|
|
parts := value.Get("parts")
|
|
|
|
// Check if this content has function responses
|
|
var responsePartsInThisContent []gjson.Result
|
|
parts.ForEach(func(_, part gjson.Result) bool {
|
|
if part.Get("functionResponse").Exists() {
|
|
responsePartsInThisContent = append(responsePartsInThisContent, part)
|
|
}
|
|
return true
|
|
})
|
|
|
|
// If this content has function responses, collect them
|
|
if len(responsePartsInThisContent) > 0 {
|
|
collectedResponses = append(collectedResponses, responsePartsInThisContent...)
|
|
|
|
// Check if any pending groups can be satisfied
|
|
for i := len(pendingGroups) - 1; i >= 0; i-- {
|
|
group := pendingGroups[i]
|
|
if len(collectedResponses) >= group.ResponsesNeeded {
|
|
// Take the needed responses for this group
|
|
groupResponses := collectedResponses[:group.ResponsesNeeded]
|
|
collectedResponses = collectedResponses[group.ResponsesNeeded:]
|
|
|
|
// Create merged function response content
|
|
var responseParts []interface{}
|
|
for _, response := range groupResponses {
|
|
var responseMap map[string]interface{}
|
|
errUnmarshal := json.Unmarshal([]byte(response.Raw), &responseMap)
|
|
if errUnmarshal != nil {
|
|
log.Warnf("failed to unmarshal function response: %v\n", errUnmarshal)
|
|
continue
|
|
}
|
|
responseParts = append(responseParts, responseMap)
|
|
}
|
|
|
|
if len(responseParts) > 0 {
|
|
functionResponseContent := map[string]interface{}{
|
|
"parts": responseParts,
|
|
"role": "function",
|
|
}
|
|
newContents = append(newContents, functionResponseContent)
|
|
}
|
|
|
|
// Remove this group as it's been satisfied
|
|
pendingGroups = append(pendingGroups[:i], pendingGroups[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
return true // Skip adding this content, responses are merged
|
|
}
|
|
|
|
// If this is a model with function calls, create a new group
|
|
if role == "model" {
|
|
var functionCallsInThisModel []gjson.Result
|
|
parts.ForEach(func(_, part gjson.Result) bool {
|
|
if part.Get("functionCall").Exists() {
|
|
functionCallsInThisModel = append(functionCallsInThisModel, part)
|
|
}
|
|
return true
|
|
})
|
|
|
|
if len(functionCallsInThisModel) > 0 {
|
|
// Add the model content
|
|
var contentMap map[string]interface{}
|
|
errUnmarshal := json.Unmarshal([]byte(value.Raw), &contentMap)
|
|
if errUnmarshal != nil {
|
|
log.Warnf("failed to unmarshal model content: %v\n", errUnmarshal)
|
|
return true
|
|
}
|
|
newContents = append(newContents, contentMap)
|
|
|
|
// Create a new group for tracking responses
|
|
group := &FunctionCallGroup{
|
|
ModelContent: contentMap,
|
|
FunctionCalls: functionCallsInThisModel,
|
|
ResponsesNeeded: len(functionCallsInThisModel),
|
|
}
|
|
pendingGroups = append(pendingGroups, group)
|
|
} else {
|
|
// Regular model content without function calls
|
|
var contentMap map[string]interface{}
|
|
errUnmarshal := json.Unmarshal([]byte(value.Raw), &contentMap)
|
|
if errUnmarshal != nil {
|
|
log.Warnf("failed to unmarshal content: %v\n", errUnmarshal)
|
|
return true
|
|
}
|
|
newContents = append(newContents, contentMap)
|
|
}
|
|
} else {
|
|
// Non-model content (user, etc.)
|
|
var contentMap map[string]interface{}
|
|
errUnmarshal := json.Unmarshal([]byte(value.Raw), &contentMap)
|
|
if errUnmarshal != nil {
|
|
log.Warnf("failed to unmarshal content: %v\n", errUnmarshal)
|
|
return true
|
|
}
|
|
newContents = append(newContents, contentMap)
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
// Handle any remaining pending groups with remaining responses
|
|
for _, group := range pendingGroups {
|
|
if len(collectedResponses) >= group.ResponsesNeeded {
|
|
groupResponses := collectedResponses[:group.ResponsesNeeded]
|
|
collectedResponses = collectedResponses[group.ResponsesNeeded:]
|
|
|
|
var responseParts []interface{}
|
|
for _, response := range groupResponses {
|
|
var responseMap map[string]interface{}
|
|
errUnmarshal := json.Unmarshal([]byte(response.Raw), &responseMap)
|
|
if errUnmarshal != nil {
|
|
log.Warnf("failed to unmarshal function response: %v\n", errUnmarshal)
|
|
continue
|
|
}
|
|
responseParts = append(responseParts, responseMap)
|
|
}
|
|
|
|
if len(responseParts) > 0 {
|
|
functionResponseContent := map[string]interface{}{
|
|
"parts": responseParts,
|
|
"role": "function",
|
|
}
|
|
newContents = append(newContents, functionResponseContent)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the original JSON with the new contents
|
|
result := input
|
|
newContentsJSON, _ := json.Marshal(newContents)
|
|
result, _ = sjson.Set(result, "request.contents", json.RawMessage(newContentsJSON))
|
|
|
|
return result, nil
|
|
}
|