mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 13:00:52 +08:00
feat(responses): add unique identifiers for responses, function calls, and tool uses
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/chat-completions"
|
. "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/chat-completions"
|
||||||
@@ -24,6 +25,9 @@ type convertCliResponseToOpenAIChatParams struct {
|
|||||||
FunctionIndex int
|
FunctionIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// functionCallIDCounter provides a process-wide unique counter for function call identifiers.
|
||||||
|
var functionCallIDCounter uint64
|
||||||
|
|
||||||
// ConvertAntigravityResponseToOpenAI translates a single chunk of a streaming response from the
|
// ConvertAntigravityResponseToOpenAI translates a single chunk of a streaming response from the
|
||||||
// Gemini CLI API format to the OpenAI Chat Completions streaming format.
|
// Gemini CLI API format to the OpenAI Chat Completions streaming format.
|
||||||
// It processes various Gemini CLI event types and transforms them into OpenAI-compatible JSON responses.
|
// It processes various Gemini CLI event types and transforms them into OpenAI-compatible JSON responses.
|
||||||
@@ -146,7 +150,7 @@ func ConvertAntigravityResponseToOpenAI(_ context.Context, _ string, originalReq
|
|||||||
|
|
||||||
functionCallTemplate := `{"id": "","index": 0,"type": "function","function": {"name": "","arguments": ""}}`
|
functionCallTemplate := `{"id": "","index": 0,"type": "function","function": {"name": "","arguments": ""}}`
|
||||||
fcName := functionCallResult.Get("name").String()
|
fcName := functionCallResult.Get("name").String()
|
||||||
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "id", fmt.Sprintf("%s-%d", fcName, time.Now().UnixNano()))
|
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "id", fmt.Sprintf("%s-%d-%d", fcName, time.Now().UnixNano(), atomic.AddUint64(&functionCallIDCounter, 1)))
|
||||||
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "index", functionCallIndex)
|
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "index", functionCallIndex)
|
||||||
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "function.name", fcName)
|
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "function.name", fcName)
|
||||||
if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() {
|
if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
@@ -27,6 +28,9 @@ type Params struct {
|
|||||||
ResponseIndex int // Index counter for content blocks in the streaming response
|
ResponseIndex int // Index counter for content blocks in the streaming response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toolUseIDCounter provides a process-wide unique counter for tool use identifiers.
|
||||||
|
var toolUseIDCounter uint64
|
||||||
|
|
||||||
// ConvertGeminiCLIResponseToClaude performs sophisticated streaming response format conversion.
|
// ConvertGeminiCLIResponseToClaude performs sophisticated streaming response format conversion.
|
||||||
// This function implements a complex state machine that translates backend client responses
|
// This function implements a complex state machine that translates backend client responses
|
||||||
// into Claude Code-compatible Server-Sent Events (SSE) format. It manages different response types
|
// into Claude Code-compatible Server-Sent Events (SSE) format. It manages different response types
|
||||||
@@ -197,7 +201,7 @@ func ConvertGeminiCLIResponseToClaude(_ context.Context, _ string, originalReque
|
|||||||
|
|
||||||
// Create the tool use block with unique ID and function details
|
// Create the tool use block with unique ID and function details
|
||||||
data := fmt.Sprintf(`{"type":"content_block_start","index":%d,"content_block":{"type":"tool_use","id":"","name":"","input":{}}}`, (*param).(*Params).ResponseIndex)
|
data := fmt.Sprintf(`{"type":"content_block_start","index":%d,"content_block":{"type":"tool_use","id":"","name":"","input":{}}}`, (*param).(*Params).ResponseIndex)
|
||||||
data, _ = sjson.Set(data, "content_block.id", fmt.Sprintf("%s-%d", fcName, time.Now().UnixNano()))
|
data, _ = sjson.Set(data, "content_block.id", fmt.Sprintf("%s-%d-%d", fcName, time.Now().UnixNano(), atomic.AddUint64(&toolUseIDCounter, 1)))
|
||||||
data, _ = sjson.Set(data, "content_block.name", fcName)
|
data, _ = sjson.Set(data, "content_block.name", fcName)
|
||||||
output = output + fmt.Sprintf("data: %s\n\n\n", data)
|
output = output + fmt.Sprintf("data: %s\n\n\n", data)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/chat-completions"
|
. "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/chat-completions"
|
||||||
@@ -24,6 +25,9 @@ type convertCliResponseToOpenAIChatParams struct {
|
|||||||
FunctionIndex int
|
FunctionIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// functionCallIDCounter provides a process-wide unique counter for function call identifiers.
|
||||||
|
var functionCallIDCounter uint64
|
||||||
|
|
||||||
// ConvertCliResponseToOpenAI translates a single chunk of a streaming response from the
|
// ConvertCliResponseToOpenAI translates a single chunk of a streaming response from the
|
||||||
// Gemini CLI API format to the OpenAI Chat Completions streaming format.
|
// Gemini CLI API format to the OpenAI Chat Completions streaming format.
|
||||||
// It processes various Gemini CLI event types and transforms them into OpenAI-compatible JSON responses.
|
// It processes various Gemini CLI event types and transforms them into OpenAI-compatible JSON responses.
|
||||||
@@ -146,7 +150,7 @@ func ConvertCliResponseToOpenAI(_ context.Context, _ string, originalRequestRawJ
|
|||||||
|
|
||||||
functionCallTemplate := `{"id": "","index": 0,"type": "function","function": {"name": "","arguments": ""}}`
|
functionCallTemplate := `{"id": "","index": 0,"type": "function","function": {"name": "","arguments": ""}}`
|
||||||
fcName := functionCallResult.Get("name").String()
|
fcName := functionCallResult.Get("name").String()
|
||||||
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "id", fmt.Sprintf("%s-%d", fcName, time.Now().UnixNano()))
|
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "id", fmt.Sprintf("%s-%d-%d", fcName, time.Now().UnixNano(), atomic.AddUint64(&functionCallIDCounter, 1)))
|
||||||
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "index", functionCallIndex)
|
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "index", functionCallIndex)
|
||||||
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "function.name", fcName)
|
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "function.name", fcName)
|
||||||
if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() {
|
if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
@@ -26,6 +27,9 @@ type Params struct {
|
|||||||
ResponseIndex int
|
ResponseIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toolUseIDCounter provides a process-wide unique counter for tool use identifiers.
|
||||||
|
var toolUseIDCounter uint64
|
||||||
|
|
||||||
// ConvertGeminiResponseToClaude performs sophisticated streaming response format conversion.
|
// ConvertGeminiResponseToClaude performs sophisticated streaming response format conversion.
|
||||||
// This function implements a complex state machine that translates backend client responses
|
// This function implements a complex state machine that translates backend client responses
|
||||||
// into Claude-compatible Server-Sent Events (SSE) format. It manages different response types
|
// into Claude-compatible Server-Sent Events (SSE) format. It manages different response types
|
||||||
@@ -197,7 +201,7 @@ func ConvertGeminiResponseToClaude(_ context.Context, _ string, originalRequestR
|
|||||||
|
|
||||||
// Create the tool use block with unique ID and function details
|
// Create the tool use block with unique ID and function details
|
||||||
data := fmt.Sprintf(`{"type":"content_block_start","index":%d,"content_block":{"type":"tool_use","id":"","name":"","input":{}}}`, (*param).(*Params).ResponseIndex)
|
data := fmt.Sprintf(`{"type":"content_block_start","index":%d,"content_block":{"type":"tool_use","id":"","name":"","input":{}}}`, (*param).(*Params).ResponseIndex)
|
||||||
data, _ = sjson.Set(data, "content_block.id", fmt.Sprintf("%s-%d", fcName, time.Now().UnixNano()))
|
data, _ = sjson.Set(data, "content_block.id", fmt.Sprintf("%s-%d-%d", fcName, time.Now().UnixNano(), atomic.AddUint64(&toolUseIDCounter, 1)))
|
||||||
data, _ = sjson.Set(data, "content_block.name", fcName)
|
data, _ = sjson.Set(data, "content_block.name", fcName)
|
||||||
output = output + fmt.Sprintf("data: %s\n\n\n", data)
|
output = output + fmt.Sprintf("data: %s\n\n\n", data)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
@@ -23,6 +24,9 @@ type convertGeminiResponseToOpenAIChatParams struct {
|
|||||||
FunctionIndex int
|
FunctionIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// functionCallIDCounter provides a process-wide unique counter for function call identifiers.
|
||||||
|
var functionCallIDCounter uint64
|
||||||
|
|
||||||
// ConvertGeminiResponseToOpenAI translates a single chunk of a streaming response from the
|
// ConvertGeminiResponseToOpenAI translates a single chunk of a streaming response from the
|
||||||
// Gemini API format to the OpenAI Chat Completions streaming format.
|
// Gemini API format to the OpenAI Chat Completions streaming format.
|
||||||
// It processes various Gemini event types and transforms them into OpenAI-compatible JSON responses.
|
// It processes various Gemini event types and transforms them into OpenAI-compatible JSON responses.
|
||||||
@@ -148,7 +152,7 @@ func ConvertGeminiResponseToOpenAI(_ context.Context, _ string, originalRequestR
|
|||||||
|
|
||||||
functionCallTemplate := `{"id": "","index": 0,"type": "function","function": {"name": "","arguments": ""}}`
|
functionCallTemplate := `{"id": "","index": 0,"type": "function","function": {"name": "","arguments": ""}}`
|
||||||
fcName := functionCallResult.Get("name").String()
|
fcName := functionCallResult.Get("name").String()
|
||||||
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "id", fmt.Sprintf("%s-%d", fcName, time.Now().UnixNano()))
|
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "id", fmt.Sprintf("%s-%d-%d", fcName, time.Now().UnixNano(), atomic.AddUint64(&functionCallIDCounter, 1)))
|
||||||
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "index", functionCallIndex)
|
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "index", functionCallIndex)
|
||||||
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "function.name", fcName)
|
functionCallTemplate, _ = sjson.Set(functionCallTemplate, "function.name", fcName)
|
||||||
if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() {
|
if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() {
|
||||||
@@ -281,7 +285,7 @@ func ConvertGeminiResponseToOpenAINonStream(_ context.Context, _ string, origina
|
|||||||
}
|
}
|
||||||
functionCallItemTemplate := `{"id": "","type": "function","function": {"name": "","arguments": ""}}`
|
functionCallItemTemplate := `{"id": "","type": "function","function": {"name": "","arguments": ""}}`
|
||||||
fcName := functionCallResult.Get("name").String()
|
fcName := functionCallResult.Get("name").String()
|
||||||
functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "id", fmt.Sprintf("%s-%d", fcName, time.Now().UnixNano()))
|
functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "id", fmt.Sprintf("%s-%d-%d", fcName, time.Now().UnixNano(), atomic.AddUint64(&functionCallIDCounter, 1)))
|
||||||
functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "function.name", fcName)
|
functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "function.name", fcName)
|
||||||
if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() {
|
if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() {
|
||||||
functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "function.arguments", fcArgsResult.Raw)
|
functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "function.arguments", fcArgsResult.Raw)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
@@ -37,6 +38,12 @@ type geminiToResponsesState struct {
|
|||||||
FuncCallIDs map[int]string
|
FuncCallIDs map[int]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// responseIDCounter provides a process-wide unique counter for synthesized response identifiers.
|
||||||
|
var responseIDCounter uint64
|
||||||
|
|
||||||
|
// funcCallIDCounter provides a process-wide unique counter for function call identifiers.
|
||||||
|
var funcCallIDCounter uint64
|
||||||
|
|
||||||
func emitEvent(event string, payload string) string {
|
func emitEvent(event string, payload string) string {
|
||||||
return fmt.Sprintf("event: %s\ndata: %s", event, payload)
|
return fmt.Sprintf("event: %s\ndata: %s", event, payload)
|
||||||
}
|
}
|
||||||
@@ -205,7 +212,7 @@ func ConvertGeminiResponseToOpenAIResponses(_ context.Context, modelName string,
|
|||||||
st.FuncArgsBuf[idx] = &strings.Builder{}
|
st.FuncArgsBuf[idx] = &strings.Builder{}
|
||||||
}
|
}
|
||||||
if st.FuncCallIDs[idx] == "" {
|
if st.FuncCallIDs[idx] == "" {
|
||||||
st.FuncCallIDs[idx] = fmt.Sprintf("call_%d", time.Now().UnixNano())
|
st.FuncCallIDs[idx] = fmt.Sprintf("call_%d_%d", time.Now().UnixNano(), atomic.AddUint64(&funcCallIDCounter, 1))
|
||||||
}
|
}
|
||||||
st.FuncNames[idx] = name
|
st.FuncNames[idx] = name
|
||||||
|
|
||||||
@@ -464,7 +471,7 @@ func ConvertGeminiResponseToOpenAIResponsesNonStream(_ context.Context, _ string
|
|||||||
// id: prefer provider responseId, otherwise synthesize
|
// id: prefer provider responseId, otherwise synthesize
|
||||||
id := root.Get("responseId").String()
|
id := root.Get("responseId").String()
|
||||||
if id == "" {
|
if id == "" {
|
||||||
id = fmt.Sprintf("resp_%x", time.Now().UnixNano())
|
id = fmt.Sprintf("resp_%x_%d", time.Now().UnixNano(), atomic.AddUint64(&responseIDCounter, 1))
|
||||||
}
|
}
|
||||||
// Normalize to response-style id (prefix resp_ if missing)
|
// Normalize to response-style id (prefix resp_ if missing)
|
||||||
if !strings.HasPrefix(id, "resp_") {
|
if !strings.HasPrefix(id, "resp_") {
|
||||||
@@ -575,7 +582,7 @@ func ConvertGeminiResponseToOpenAIResponsesNonStream(_ context.Context, _ string
|
|||||||
if fc := p.Get("functionCall"); fc.Exists() {
|
if fc := p.Get("functionCall"); fc.Exists() {
|
||||||
name := fc.Get("name").String()
|
name := fc.Get("name").String()
|
||||||
args := fc.Get("args")
|
args := fc.Get("args")
|
||||||
callID := fmt.Sprintf("call_%x", time.Now().UnixNano())
|
callID := fmt.Sprintf("call_%x_%d", time.Now().UnixNano(), atomic.AddUint64(&funcCallIDCounter, 1))
|
||||||
outputs = append(outputs, map[string]interface{}{
|
outputs = append(outputs, map[string]interface{}{
|
||||||
"id": fmt.Sprintf("fc_%s", callID),
|
"id": fmt.Sprintf("fc_%s", callID),
|
||||||
"type": "function_call",
|
"type": "function_call",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
@@ -41,6 +42,9 @@ type oaiToResponsesState struct {
|
|||||||
UsageSeen bool
|
UsageSeen bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// responseIDCounter provides a process-wide unique counter for synthesized response identifiers.
|
||||||
|
var responseIDCounter uint64
|
||||||
|
|
||||||
func emitRespEvent(event string, payload string) string {
|
func emitRespEvent(event string, payload string) string {
|
||||||
return fmt.Sprintf("event: %s\ndata: %s", event, payload)
|
return fmt.Sprintf("event: %s\ndata: %s", event, payload)
|
||||||
}
|
}
|
||||||
@@ -590,7 +594,7 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream(_ context.Co
|
|||||||
// id: use provider id if present, otherwise synthesize
|
// id: use provider id if present, otherwise synthesize
|
||||||
id := root.Get("id").String()
|
id := root.Get("id").String()
|
||||||
if id == "" {
|
if id == "" {
|
||||||
id = fmt.Sprintf("resp_%x", time.Now().UnixNano())
|
id = fmt.Sprintf("resp_%x_%d", time.Now().UnixNano(), atomic.AddUint64(&responseIDCounter, 1))
|
||||||
}
|
}
|
||||||
resp, _ = sjson.Set(resp, "id", id)
|
resp, _ = sjson.Set(resp, "id", id)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user