refactor: replace sjson.Set usage with sjson.SetBytes to optimize mutable JSON transformations
This commit is contained in:
@@ -38,7 +38,7 @@ import (
|
||||
func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool) []byte {
|
||||
rawJSON := inputRawJSON
|
||||
// Base template
|
||||
out := `{"model":"","instructions":"","input":[]}`
|
||||
out := []byte(`{"model":"","instructions":"","input":[]}`)
|
||||
|
||||
root := gjson.ParseBytes(rawJSON)
|
||||
|
||||
@@ -82,24 +82,24 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
}
|
||||
|
||||
// Model
|
||||
out, _ = sjson.Set(out, "model", modelName)
|
||||
out, _ = sjson.SetBytes(out, "model", modelName)
|
||||
|
||||
// System instruction -> as a user message with input_text parts
|
||||
sysParts := root.Get("system_instruction.parts")
|
||||
if sysParts.IsArray() {
|
||||
msg := `{"type":"message","role":"developer","content":[]}`
|
||||
msg := []byte(`{"type":"message","role":"developer","content":[]}`)
|
||||
arr := sysParts.Array()
|
||||
for i := 0; i < len(arr); i++ {
|
||||
p := arr[i]
|
||||
if t := p.Get("text"); t.Exists() {
|
||||
part := `{}`
|
||||
part, _ = sjson.Set(part, "type", "input_text")
|
||||
part, _ = sjson.Set(part, "text", t.String())
|
||||
msg, _ = sjson.SetRaw(msg, "content.-1", part)
|
||||
part := []byte(`{}`)
|
||||
part, _ = sjson.SetBytes(part, "type", "input_text")
|
||||
part, _ = sjson.SetBytes(part, "text", t.String())
|
||||
msg, _ = sjson.SetRawBytes(msg, "content.-1", part)
|
||||
}
|
||||
}
|
||||
if len(gjson.Get(msg, "content").Array()) > 0 {
|
||||
out, _ = sjson.SetRaw(out, "input.-1", msg)
|
||||
if len(gjson.GetBytes(msg, "content").Array()) > 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "input.-1", msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,23 +123,23 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
p := parr[j]
|
||||
// text part
|
||||
if t := p.Get("text"); t.Exists() {
|
||||
msg := `{"type":"message","role":"","content":[]}`
|
||||
msg, _ = sjson.Set(msg, "role", role)
|
||||
msg := []byte(`{"type":"message","role":"","content":[]}`)
|
||||
msg, _ = sjson.SetBytes(msg, "role", role)
|
||||
partType := "input_text"
|
||||
if role == "assistant" {
|
||||
partType = "output_text"
|
||||
}
|
||||
part := `{}`
|
||||
part, _ = sjson.Set(part, "type", partType)
|
||||
part, _ = sjson.Set(part, "text", t.String())
|
||||
msg, _ = sjson.SetRaw(msg, "content.-1", part)
|
||||
out, _ = sjson.SetRaw(out, "input.-1", msg)
|
||||
part := []byte(`{}`)
|
||||
part, _ = sjson.SetBytes(part, "type", partType)
|
||||
part, _ = sjson.SetBytes(part, "text", t.String())
|
||||
msg, _ = sjson.SetRawBytes(msg, "content.-1", part)
|
||||
out, _ = sjson.SetRawBytes(out, "input.-1", msg)
|
||||
continue
|
||||
}
|
||||
|
||||
// function call from model
|
||||
if fc := p.Get("functionCall"); fc.Exists() {
|
||||
fn := `{"type":"function_call"}`
|
||||
fn := []byte(`{"type":"function_call"}`)
|
||||
if name := fc.Get("name"); name.Exists() {
|
||||
n := name.String()
|
||||
if short, ok := shortMap[n]; ok {
|
||||
@@ -147,31 +147,31 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
} else {
|
||||
n = shortenNameIfNeeded(n)
|
||||
}
|
||||
fn, _ = sjson.Set(fn, "name", n)
|
||||
fn, _ = sjson.SetBytes(fn, "name", n)
|
||||
}
|
||||
if args := fc.Get("args"); args.Exists() {
|
||||
fn, _ = sjson.Set(fn, "arguments", args.Raw)
|
||||
fn, _ = sjson.SetBytes(fn, "arguments", args.Raw)
|
||||
}
|
||||
// generate a paired random call_id and enqueue it so the
|
||||
// corresponding functionResponse can pop the earliest id
|
||||
// to preserve ordering when multiple calls are present.
|
||||
id := genCallID()
|
||||
fn, _ = sjson.Set(fn, "call_id", id)
|
||||
fn, _ = sjson.SetBytes(fn, "call_id", id)
|
||||
pendingCallIDs = append(pendingCallIDs, id)
|
||||
out, _ = sjson.SetRaw(out, "input.-1", fn)
|
||||
out, _ = sjson.SetRawBytes(out, "input.-1", fn)
|
||||
continue
|
||||
}
|
||||
|
||||
// function response from user
|
||||
if fr := p.Get("functionResponse"); fr.Exists() {
|
||||
fno := `{"type":"function_call_output"}`
|
||||
fno := []byte(`{"type":"function_call_output"}`)
|
||||
// Prefer a string result if present; otherwise embed the raw response as a string
|
||||
if res := fr.Get("response.result"); res.Exists() {
|
||||
fno, _ = sjson.Set(fno, "output", res.String())
|
||||
fno, _ = sjson.SetBytes(fno, "output", res.String())
|
||||
} else if resp := fr.Get("response"); resp.Exists() {
|
||||
fno, _ = sjson.Set(fno, "output", resp.Raw)
|
||||
fno, _ = sjson.SetBytes(fno, "output", resp.Raw)
|
||||
}
|
||||
// fno, _ = sjson.Set(fno, "call_id", "call_W6nRJzFXyPM2LFBbfo98qAbq")
|
||||
// fno, _ = sjson.SetBytes(fno, "call_id", "call_W6nRJzFXyPM2LFBbfo98qAbq")
|
||||
// attach the oldest queued call_id to pair the response
|
||||
// with its call. If the queue is empty, generate a new id.
|
||||
var id string
|
||||
@@ -182,8 +182,8 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
} else {
|
||||
id = genCallID()
|
||||
}
|
||||
fno, _ = sjson.Set(fno, "call_id", id)
|
||||
out, _ = sjson.SetRaw(out, "input.-1", fno)
|
||||
fno, _ = sjson.SetBytes(fno, "call_id", id)
|
||||
out, _ = sjson.SetRawBytes(out, "input.-1", fno)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -193,8 +193,8 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
// Tools mapping: Gemini functionDeclarations -> Codex tools
|
||||
tools := root.Get("tools")
|
||||
if tools.IsArray() {
|
||||
out, _ = sjson.SetRaw(out, "tools", `[]`)
|
||||
out, _ = sjson.Set(out, "tool_choice", "auto")
|
||||
out, _ = sjson.SetRawBytes(out, "tools", []byte(`[]`))
|
||||
out, _ = sjson.SetBytes(out, "tool_choice", "auto")
|
||||
tarr := tools.Array()
|
||||
for i := 0; i < len(tarr); i++ {
|
||||
td := tarr[i]
|
||||
@@ -205,8 +205,8 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
farr := fns.Array()
|
||||
for j := 0; j < len(farr); j++ {
|
||||
fn := farr[j]
|
||||
tool := `{}`
|
||||
tool, _ = sjson.Set(tool, "type", "function")
|
||||
tool := []byte(`{}`)
|
||||
tool, _ = sjson.SetBytes(tool, "type", "function")
|
||||
if v := fn.Get("name"); v.Exists() {
|
||||
name := v.String()
|
||||
if short, ok := shortMap[name]; ok {
|
||||
@@ -214,32 +214,32 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
} else {
|
||||
name = shortenNameIfNeeded(name)
|
||||
}
|
||||
tool, _ = sjson.Set(tool, "name", name)
|
||||
tool, _ = sjson.SetBytes(tool, "name", name)
|
||||
}
|
||||
if v := fn.Get("description"); v.Exists() {
|
||||
tool, _ = sjson.Set(tool, "description", v.String())
|
||||
tool, _ = sjson.SetBytes(tool, "description", v.String())
|
||||
}
|
||||
if prm := fn.Get("parameters"); prm.Exists() {
|
||||
// Remove optional $schema field if present
|
||||
cleaned := prm.Raw
|
||||
cleaned, _ = sjson.Delete(cleaned, "$schema")
|
||||
cleaned, _ = sjson.Set(cleaned, "additionalProperties", false)
|
||||
tool, _ = sjson.SetRaw(tool, "parameters", cleaned)
|
||||
cleaned := []byte(prm.Raw)
|
||||
cleaned, _ = sjson.DeleteBytes(cleaned, "$schema")
|
||||
cleaned, _ = sjson.SetBytes(cleaned, "additionalProperties", false)
|
||||
tool, _ = sjson.SetRawBytes(tool, "parameters", cleaned)
|
||||
} else if prm = fn.Get("parametersJsonSchema"); prm.Exists() {
|
||||
// Remove optional $schema field if present
|
||||
cleaned := prm.Raw
|
||||
cleaned, _ = sjson.Delete(cleaned, "$schema")
|
||||
cleaned, _ = sjson.Set(cleaned, "additionalProperties", false)
|
||||
tool, _ = sjson.SetRaw(tool, "parameters", cleaned)
|
||||
cleaned := []byte(prm.Raw)
|
||||
cleaned, _ = sjson.DeleteBytes(cleaned, "$schema")
|
||||
cleaned, _ = sjson.SetBytes(cleaned, "additionalProperties", false)
|
||||
tool, _ = sjson.SetRawBytes(tool, "parameters", cleaned)
|
||||
}
|
||||
tool, _ = sjson.Set(tool, "strict", false)
|
||||
out, _ = sjson.SetRaw(out, "tools.-1", tool)
|
||||
tool, _ = sjson.SetBytes(tool, "strict", false)
|
||||
out, _ = sjson.SetRawBytes(out, "tools.-1", tool)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed flags aligning with Codex expectations
|
||||
out, _ = sjson.Set(out, "parallel_tool_calls", true)
|
||||
out, _ = sjson.SetBytes(out, "parallel_tool_calls", true)
|
||||
|
||||
// Convert Gemini thinkingConfig to Codex reasoning.effort.
|
||||
// Note: Google official Python SDK sends snake_case fields (thinking_level/thinking_budget).
|
||||
@@ -253,7 +253,7 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
if thinkingLevel.Exists() {
|
||||
effort := strings.ToLower(strings.TrimSpace(thinkingLevel.String()))
|
||||
if effort != "" {
|
||||
out, _ = sjson.Set(out, "reasoning.effort", effort)
|
||||
out, _ = sjson.SetBytes(out, "reasoning.effort", effort)
|
||||
effortSet = true
|
||||
}
|
||||
} else {
|
||||
@@ -263,7 +263,7 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
}
|
||||
if thinkingBudget.Exists() {
|
||||
if effort, ok := thinking.ConvertBudgetToLevel(int(thinkingBudget.Int())); ok {
|
||||
out, _ = sjson.Set(out, "reasoning.effort", effort)
|
||||
out, _ = sjson.SetBytes(out, "reasoning.effort", effort)
|
||||
effortSet = true
|
||||
}
|
||||
}
|
||||
@@ -272,22 +272,22 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
}
|
||||
if !effortSet {
|
||||
// No thinking config, set default effort
|
||||
out, _ = sjson.Set(out, "reasoning.effort", "medium")
|
||||
out, _ = sjson.SetBytes(out, "reasoning.effort", "medium")
|
||||
}
|
||||
out, _ = sjson.Set(out, "reasoning.summary", "auto")
|
||||
out, _ = sjson.Set(out, "stream", true)
|
||||
out, _ = sjson.Set(out, "store", false)
|
||||
out, _ = sjson.Set(out, "include", []string{"reasoning.encrypted_content"})
|
||||
out, _ = sjson.SetBytes(out, "reasoning.summary", "auto")
|
||||
out, _ = sjson.SetBytes(out, "stream", true)
|
||||
out, _ = sjson.SetBytes(out, "store", false)
|
||||
out, _ = sjson.SetBytes(out, "include", []string{"reasoning.encrypted_content"})
|
||||
|
||||
var pathsToLower []string
|
||||
toolsResult := gjson.Get(out, "tools")
|
||||
toolsResult := gjson.GetBytes(out, "tools")
|
||||
util.Walk(toolsResult, "", "type", &pathsToLower)
|
||||
for _, p := range pathsToLower {
|
||||
fullPath := fmt.Sprintf("tools.%s", p)
|
||||
out, _ = sjson.Set(out, fullPath, strings.ToLower(gjson.Get(out, fullPath).String()))
|
||||
out, _ = sjson.SetBytes(out, fullPath, strings.ToLower(gjson.GetBytes(out, fullPath).String()))
|
||||
}
|
||||
|
||||
return []byte(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// shortenNameIfNeeded applies the simple shortening rule for a single name.
|
||||
|
||||
@@ -7,9 +7,9 @@ package gemini
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
@@ -23,7 +23,7 @@ type ConvertCodexResponseToGeminiParams struct {
|
||||
Model string
|
||||
CreatedAt int64
|
||||
ResponseID string
|
||||
LastStorageOutput string
|
||||
LastStorageOutput []byte
|
||||
}
|
||||
|
||||
// ConvertCodexResponseToGemini converts Codex streaming response format to Gemini format.
|
||||
@@ -38,19 +38,19 @@ type ConvertCodexResponseToGeminiParams struct {
|
||||
// - param: A pointer to a parameter object for maintaining state between calls
|
||||
//
|
||||
// Returns:
|
||||
// - []string: A slice of strings, each containing a Gemini-compatible JSON response
|
||||
func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []string {
|
||||
// - [][]byte: A slice of Gemini-compatible JSON responses
|
||||
func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte {
|
||||
if *param == nil {
|
||||
*param = &ConvertCodexResponseToGeminiParams{
|
||||
Model: modelName,
|
||||
CreatedAt: 0,
|
||||
ResponseID: "",
|
||||
LastStorageOutput: "",
|
||||
LastStorageOutput: nil,
|
||||
}
|
||||
}
|
||||
|
||||
if !bytes.HasPrefix(rawJSON, dataTag) {
|
||||
return []string{}
|
||||
return [][]byte{}
|
||||
}
|
||||
rawJSON = bytes.TrimSpace(rawJSON[5:])
|
||||
|
||||
@@ -59,17 +59,17 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR
|
||||
typeStr := typeResult.String()
|
||||
|
||||
// Base Gemini response template
|
||||
template := `{"candidates":[{"content":{"role":"model","parts":[]}}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"},"modelVersion":"gemini-2.5-pro","createTime":"2025-08-15T02:52:03.884209Z","responseId":"06CeaPH7NaCU48APvNXDyA4"}`
|
||||
if (*param).(*ConvertCodexResponseToGeminiParams).LastStorageOutput != "" && typeStr == "response.output_item.done" {
|
||||
template = (*param).(*ConvertCodexResponseToGeminiParams).LastStorageOutput
|
||||
template := []byte(`{"candidates":[{"content":{"role":"model","parts":[]}}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"},"modelVersion":"gemini-2.5-pro","createTime":"2025-08-15T02:52:03.884209Z","responseId":"06CeaPH7NaCU48APvNXDyA4"}`)
|
||||
if len((*param).(*ConvertCodexResponseToGeminiParams).LastStorageOutput) > 0 && typeStr == "response.output_item.done" {
|
||||
template = append([]byte(nil), (*param).(*ConvertCodexResponseToGeminiParams).LastStorageOutput...)
|
||||
} else {
|
||||
template, _ = sjson.Set(template, "modelVersion", (*param).(*ConvertCodexResponseToGeminiParams).Model)
|
||||
template, _ = sjson.SetBytes(template, "modelVersion", (*param).(*ConvertCodexResponseToGeminiParams).Model)
|
||||
createdAtResult := rootResult.Get("response.created_at")
|
||||
if createdAtResult.Exists() {
|
||||
(*param).(*ConvertCodexResponseToGeminiParams).CreatedAt = createdAtResult.Int()
|
||||
template, _ = sjson.Set(template, "createTime", time.Unix((*param).(*ConvertCodexResponseToGeminiParams).CreatedAt, 0).Format(time.RFC3339Nano))
|
||||
template, _ = sjson.SetBytes(template, "createTime", time.Unix((*param).(*ConvertCodexResponseToGeminiParams).CreatedAt, 0).Format(time.RFC3339Nano))
|
||||
}
|
||||
template, _ = sjson.Set(template, "responseId", (*param).(*ConvertCodexResponseToGeminiParams).ResponseID)
|
||||
template, _ = sjson.SetBytes(template, "responseId", (*param).(*ConvertCodexResponseToGeminiParams).ResponseID)
|
||||
}
|
||||
|
||||
// Handle function call completion
|
||||
@@ -78,7 +78,7 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR
|
||||
itemType := itemResult.Get("type").String()
|
||||
if itemType == "function_call" {
|
||||
// Create function call part
|
||||
functionCall := `{"functionCall":{"name":"","args":{}}}`
|
||||
functionCall := []byte(`{"functionCall":{"name":"","args":{}}}`)
|
||||
{
|
||||
// Restore original tool name if shortened
|
||||
n := itemResult.Get("name").String()
|
||||
@@ -86,7 +86,7 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR
|
||||
if orig, ok := rev[n]; ok {
|
||||
n = orig
|
||||
}
|
||||
functionCall, _ = sjson.Set(functionCall, "functionCall.name", n)
|
||||
functionCall, _ = sjson.SetBytes(functionCall, "functionCall.name", n)
|
||||
}
|
||||
|
||||
// Parse and set arguments
|
||||
@@ -94,47 +94,48 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR
|
||||
if argsStr != "" {
|
||||
argsResult := gjson.Parse(argsStr)
|
||||
if argsResult.IsObject() {
|
||||
functionCall, _ = sjson.SetRaw(functionCall, "functionCall.args", argsStr)
|
||||
functionCall, _ = sjson.SetRawBytes(functionCall, "functionCall.args", []byte(argsStr))
|
||||
}
|
||||
}
|
||||
|
||||
template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", functionCall)
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP")
|
||||
template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", functionCall)
|
||||
template, _ = sjson.SetBytes(template, "candidates.0.finishReason", "STOP")
|
||||
|
||||
(*param).(*ConvertCodexResponseToGeminiParams).LastStorageOutput = template
|
||||
(*param).(*ConvertCodexResponseToGeminiParams).LastStorageOutput = append([]byte(nil), template...)
|
||||
|
||||
// Use this return to storage message
|
||||
return []string{}
|
||||
return [][]byte{}
|
||||
}
|
||||
}
|
||||
|
||||
if typeStr == "response.created" { // Handle response creation - set model and response ID
|
||||
template, _ = sjson.Set(template, "modelVersion", rootResult.Get("response.model").String())
|
||||
template, _ = sjson.Set(template, "responseId", rootResult.Get("response.id").String())
|
||||
template, _ = sjson.SetBytes(template, "modelVersion", rootResult.Get("response.model").String())
|
||||
template, _ = sjson.SetBytes(template, "responseId", rootResult.Get("response.id").String())
|
||||
(*param).(*ConvertCodexResponseToGeminiParams).ResponseID = rootResult.Get("response.id").String()
|
||||
} else if typeStr == "response.reasoning_summary_text.delta" { // Handle reasoning/thinking content delta
|
||||
part := `{"thought":true,"text":""}`
|
||||
part, _ = sjson.Set(part, "text", rootResult.Get("delta").String())
|
||||
template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", part)
|
||||
part := []byte(`{"thought":true,"text":""}`)
|
||||
part, _ = sjson.SetBytes(part, "text", rootResult.Get("delta").String())
|
||||
template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", part)
|
||||
} else if typeStr == "response.output_text.delta" { // Handle regular text content delta
|
||||
part := `{"text":""}`
|
||||
part, _ = sjson.Set(part, "text", rootResult.Get("delta").String())
|
||||
template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", part)
|
||||
part := []byte(`{"text":""}`)
|
||||
part, _ = sjson.SetBytes(part, "text", rootResult.Get("delta").String())
|
||||
template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", part)
|
||||
} else if typeStr == "response.completed" { // Handle response completion with usage metadata
|
||||
template, _ = sjson.Set(template, "usageMetadata.promptTokenCount", rootResult.Get("response.usage.input_tokens").Int())
|
||||
template, _ = sjson.Set(template, "usageMetadata.candidatesTokenCount", rootResult.Get("response.usage.output_tokens").Int())
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.promptTokenCount", rootResult.Get("response.usage.input_tokens").Int())
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.candidatesTokenCount", rootResult.Get("response.usage.output_tokens").Int())
|
||||
totalTokens := rootResult.Get("response.usage.input_tokens").Int() + rootResult.Get("response.usage.output_tokens").Int()
|
||||
template, _ = sjson.Set(template, "usageMetadata.totalTokenCount", totalTokens)
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.totalTokenCount", totalTokens)
|
||||
} else {
|
||||
return []string{}
|
||||
return [][]byte{}
|
||||
}
|
||||
|
||||
if (*param).(*ConvertCodexResponseToGeminiParams).LastStorageOutput != "" {
|
||||
return []string{(*param).(*ConvertCodexResponseToGeminiParams).LastStorageOutput, template}
|
||||
} else {
|
||||
return []string{template}
|
||||
if len((*param).(*ConvertCodexResponseToGeminiParams).LastStorageOutput) > 0 {
|
||||
return [][]byte{
|
||||
append([]byte(nil), (*param).(*ConvertCodexResponseToGeminiParams).LastStorageOutput...),
|
||||
template,
|
||||
}
|
||||
}
|
||||
|
||||
return [][]byte{template}
|
||||
}
|
||||
|
||||
// ConvertCodexResponseToGeminiNonStream converts a non-streaming Codex response to a non-streaming Gemini response.
|
||||
@@ -149,32 +150,32 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR
|
||||
// - param: A pointer to a parameter object for the conversion (unused in current implementation)
|
||||
//
|
||||
// Returns:
|
||||
// - string: A Gemini-compatible JSON response containing all message content and metadata
|
||||
func ConvertCodexResponseToGeminiNonStream(_ context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) string {
|
||||
// - []byte: A Gemini-compatible JSON response containing all message content and metadata
|
||||
func ConvertCodexResponseToGeminiNonStream(_ context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) []byte {
|
||||
rootResult := gjson.ParseBytes(rawJSON)
|
||||
|
||||
// Verify this is a response.completed event
|
||||
if rootResult.Get("type").String() != "response.completed" {
|
||||
return ""
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
// Base Gemini response template for non-streaming
|
||||
template := `{"candidates":[{"content":{"role":"model","parts":[]},"finishReason":"STOP"}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"},"modelVersion":"","createTime":"","responseId":""}`
|
||||
template := []byte(`{"candidates":[{"content":{"role":"model","parts":[]},"finishReason":"STOP"}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"},"modelVersion":"","createTime":"","responseId":""}`)
|
||||
|
||||
// Set model version
|
||||
template, _ = sjson.Set(template, "modelVersion", modelName)
|
||||
template, _ = sjson.SetBytes(template, "modelVersion", modelName)
|
||||
|
||||
// Set response metadata from the completed response
|
||||
responseData := rootResult.Get("response")
|
||||
if responseData.Exists() {
|
||||
// Set response ID
|
||||
if responseId := responseData.Get("id"); responseId.Exists() {
|
||||
template, _ = sjson.Set(template, "responseId", responseId.String())
|
||||
template, _ = sjson.SetBytes(template, "responseId", responseId.String())
|
||||
}
|
||||
|
||||
// Set creation time
|
||||
if createdAt := responseData.Get("created_at"); createdAt.Exists() {
|
||||
template, _ = sjson.Set(template, "createTime", time.Unix(createdAt.Int(), 0).Format(time.RFC3339Nano))
|
||||
template, _ = sjson.SetBytes(template, "createTime", time.Unix(createdAt.Int(), 0).Format(time.RFC3339Nano))
|
||||
}
|
||||
|
||||
// Set usage metadata
|
||||
@@ -183,14 +184,14 @@ func ConvertCodexResponseToGeminiNonStream(_ context.Context, modelName string,
|
||||
outputTokens := usage.Get("output_tokens").Int()
|
||||
totalTokens := inputTokens + outputTokens
|
||||
|
||||
template, _ = sjson.Set(template, "usageMetadata.promptTokenCount", inputTokens)
|
||||
template, _ = sjson.Set(template, "usageMetadata.candidatesTokenCount", outputTokens)
|
||||
template, _ = sjson.Set(template, "usageMetadata.totalTokenCount", totalTokens)
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.promptTokenCount", inputTokens)
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.candidatesTokenCount", outputTokens)
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.totalTokenCount", totalTokens)
|
||||
}
|
||||
|
||||
// Process output content to build parts array
|
||||
hasToolCall := false
|
||||
var pendingFunctionCalls []string
|
||||
var pendingFunctionCalls [][]byte
|
||||
|
||||
flushPendingFunctionCalls := func() {
|
||||
if len(pendingFunctionCalls) == 0 {
|
||||
@@ -199,7 +200,7 @@ func ConvertCodexResponseToGeminiNonStream(_ context.Context, modelName string,
|
||||
// Add all pending function calls as individual parts
|
||||
// This maintains the original Gemini API format while ensuring consecutive calls are grouped together
|
||||
for _, fc := range pendingFunctionCalls {
|
||||
template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", fc)
|
||||
template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", fc)
|
||||
}
|
||||
pendingFunctionCalls = nil
|
||||
}
|
||||
@@ -215,9 +216,9 @@ func ConvertCodexResponseToGeminiNonStream(_ context.Context, modelName string,
|
||||
|
||||
// Add thinking content
|
||||
if content := value.Get("content"); content.Exists() {
|
||||
part := `{"text":"","thought":true}`
|
||||
part, _ = sjson.Set(part, "text", content.String())
|
||||
template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", part)
|
||||
part := []byte(`{"text":"","thought":true}`)
|
||||
part, _ = sjson.SetBytes(part, "text", content.String())
|
||||
template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", part)
|
||||
}
|
||||
|
||||
case "message":
|
||||
@@ -229,9 +230,9 @@ func ConvertCodexResponseToGeminiNonStream(_ context.Context, modelName string,
|
||||
content.ForEach(func(_, contentItem gjson.Result) bool {
|
||||
if contentItem.Get("type").String() == "output_text" {
|
||||
if text := contentItem.Get("text"); text.Exists() {
|
||||
part := `{"text":""}`
|
||||
part, _ = sjson.Set(part, "text", text.String())
|
||||
template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", part)
|
||||
part := []byte(`{"text":""}`)
|
||||
part, _ = sjson.SetBytes(part, "text", text.String())
|
||||
template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", part)
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -241,21 +242,21 @@ func ConvertCodexResponseToGeminiNonStream(_ context.Context, modelName string,
|
||||
case "function_call":
|
||||
// Collect function call for potential merging with consecutive ones
|
||||
hasToolCall = true
|
||||
functionCall := `{"functionCall":{"args":{},"name":""}}`
|
||||
functionCall := []byte(`{"functionCall":{"args":{},"name":""}}`)
|
||||
{
|
||||
n := value.Get("name").String()
|
||||
rev := buildReverseMapFromGeminiOriginal(originalRequestRawJSON)
|
||||
if orig, ok := rev[n]; ok {
|
||||
n = orig
|
||||
}
|
||||
functionCall, _ = sjson.Set(functionCall, "functionCall.name", n)
|
||||
functionCall, _ = sjson.SetBytes(functionCall, "functionCall.name", n)
|
||||
}
|
||||
|
||||
// Parse and set arguments
|
||||
if argsStr := value.Get("arguments").String(); argsStr != "" {
|
||||
argsResult := gjson.Parse(argsStr)
|
||||
if argsResult.IsObject() {
|
||||
functionCall, _ = sjson.SetRaw(functionCall, "functionCall.args", argsStr)
|
||||
functionCall, _ = sjson.SetRawBytes(functionCall, "functionCall.args", []byte(argsStr))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,9 +271,9 @@ func ConvertCodexResponseToGeminiNonStream(_ context.Context, modelName string,
|
||||
|
||||
// Set finish reason based on whether there were tool calls
|
||||
if hasToolCall {
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP")
|
||||
template, _ = sjson.SetBytes(template, "candidates.0.finishReason", "STOP")
|
||||
} else {
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP")
|
||||
template, _ = sjson.SetBytes(template, "candidates.0.finishReason", "STOP")
|
||||
}
|
||||
}
|
||||
return template
|
||||
@@ -307,6 +308,6 @@ func buildReverseMapFromGeminiOriginal(original []byte) map[string]string {
|
||||
return rev
|
||||
}
|
||||
|
||||
func GeminiTokenCount(ctx context.Context, count int64) string {
|
||||
return fmt.Sprintf(`{"totalTokens":%d,"promptTokensDetails":[{"modality":"TEXT","tokenCount":%d}]}`, count, count)
|
||||
func GeminiTokenCount(ctx context.Context, count int64) []byte {
|
||||
return translatorcommon.GeminiTokenCountJSON(count)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user