ccpoad / internal /protocol /builtin /codex_gemini.go
anyalerob's picture
Upload folder using huggingface_hub
2986042 verified
Raw
History Blame Contribute Delete
11.6 kB
package builtin
import (
"context"
"encoding/json"
"fmt"
"strings"
"ccLoad/internal/protocol"
"ccLoad/internal/util"
"github.com/bytedance/sonic"
)
type codexRequest struct {
Model string `json:"model"`
Instructions string `json:"instructions,omitempty"`
Stream util.FlexibleBool `json:"stream,omitempty"`
Tools json.RawMessage `json:"tools,omitempty"`
ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
Input []json.RawMessage `json:"input"`
Reasoning *codexReasoning `json:"reasoning,omitempty"`
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
TopK *int `json:"top_k,omitempty"`
MaxOutputTokens *int `json:"max_output_tokens,omitempty"`
Stop json.RawMessage `json:"stop,omitempty"`
Seed *int64 `json:"seed,omitempty"`
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"`
PresencePenalty *float64 `json:"presence_penalty,omitempty"`
User string `json:"user,omitempty"`
}
type codexReasoning struct {
Effort string `json:"effort,omitempty"`
Summary string `json:"summary,omitempty"`
}
type codexToGeminiStreamState struct {
model string
responseID string
hasOutputTextDelta bool
toolNameMap map[string]string
}
func convertCodexRequestToGemini(_ string, rawJSON []byte, _ bool) ([]byte, error) {
var req codexRequest
if err := sonic.Unmarshal(rawJSON, &req); err != nil {
return nil, err
}
conv, err := normalizeCodexConversation(req)
if err != nil {
return nil, err
}
return encodeGeminiRequest(conv)
}
func convertGeminiRequestToCodex(model string, rawJSON []byte, stream bool) ([]byte, error) {
var req geminiRequestPayload
if err := sonic.Unmarshal(rawJSON, &req); err != nil {
return nil, err
}
conv, err := normalizeGeminiConversation(req)
if err != nil {
return nil, err
}
return encodeCodexRequest(model, conv, stream)
}
func convertGeminiResponseToCodexNonStream(_ context.Context, model string, _, _, rawJSON []byte) ([]byte, error) {
var resp geminiResponse
if err := sonic.Unmarshal(rawJSON, &resp); err != nil {
return nil, err
}
output := make([]map[string]any, 0)
if len(resp.Candidates) > 0 {
parts, err := conversationPartsFromGeminiParts(resp.Candidates[0].Content.Parts)
if err != nil {
return nil, err
}
output, err = codexOutputItemsFromConversationParts(parts)
if err != nil {
return nil, err
}
}
out := map[string]any{
"id": "resp-proxy",
"object": "response",
"status": "completed",
"model": coalesceModel(model, resp.ModelVersion),
"output": output,
}
if resp.UsageMetadata.PromptTokenCount != 0 || resp.UsageMetadata.CandidatesTokenCount != 0 || resp.UsageMetadata.TotalTokenCount != 0 {
out["usage"] = map[string]any{
"input_tokens": resp.UsageMetadata.PromptTokenCount,
"output_tokens": resp.UsageMetadata.CandidatesTokenCount,
"total_tokens": resp.UsageMetadata.TotalTokenCount,
}
}
return sonic.Marshal(out)
}
func convertCodexResponseToGeminiNonStream(_ context.Context, model string, rawReq, translatedReq, rawJSON []byte) ([]byte, error) {
var resp map[string]any
if err := sonic.Unmarshal(rawJSON, &resp); err != nil {
return nil, err
}
aliases := codexToolAliasesFromRequests(protocol.Gemini, rawReq, translatedReq)
parts, err := geminiPartsFromCodexOutput(resp["output"], aliases.restore)
if err != nil {
return nil, err
}
var promptTokens, candidateTokens, totalTokens int64
includeUsage := false
if usage := codexUsageFromMap(resp["usage"]); usage != nil {
promptTokens = usage.inputTokens
candidateTokens = usage.outputTokens
totalTokens = usage.totalTokens
includeUsage = true
}
return sonic.Marshal(buildGeminiPayloadFromParts(coalesceModel(model, resp["model"]), stringValue(resp["id"]), parts, "STOP", promptTokens, candidateTokens, totalTokens, includeUsage))
}
type codexStreamState struct {
responseID string
model string
pendingToolCallIDs []string
nextToolCallID int
usage struct {
inputTokens int64
outputTokens int64
totalTokens int64
seen bool
}
}
func convertGeminiResponseToCodexStream(_ context.Context, model string, _, _, rawJSON []byte, param *any) ([][]byte, error) {
if param == nil {
var local any
param = &local
}
if *param == nil {
*param = &codexStreamState{responseID: "resp-proxy", model: model, nextToolCallID: 1}
}
st := (*param).(*codexStreamState)
if st.model == "" {
st.model = model
}
if st.nextToolCallID == 0 {
st.nextToolCallID = 1
}
line := strings.TrimSpace(string(rawJSON))
if line == "" {
return nil, nil
}
if strings.HasPrefix(line, "data:") {
line = strings.TrimSpace(strings.TrimPrefix(line, "data:"))
}
if line == "[DONE]" {
response := map[string]any{
"id": st.responseID,
"object": "response",
"status": "completed",
}
if st.model != "" {
response["model"] = st.model
}
if st.usage.seen {
response["usage"] = map[string]any{
"input_tokens": st.usage.inputTokens,
"output_tokens": st.usage.outputTokens,
"total_tokens": st.usage.totalTokens,
}
}
done := map[string]any{
"type": "response.completed",
"response": response,
}
body, err := sonic.Marshal(done)
if err != nil {
return nil, err
}
return [][]byte{append([]byte("event: response.completed\ndata: "), append(body, []byte("\n\n")...)...)}, nil
}
var resp geminiResponse
if err := sonic.Unmarshal([]byte(line), &resp); err != nil {
return nil, err
}
if resp.ResponseID != "" {
st.responseID = resp.ResponseID
}
if st.model == "" {
st.model = resp.ModelVersion
}
if resp.UsageMetadata.PromptTokenCount != 0 || resp.UsageMetadata.CandidatesTokenCount != 0 || resp.UsageMetadata.TotalTokenCount != 0 {
st.usage.inputTokens = resp.UsageMetadata.PromptTokenCount
st.usage.outputTokens = resp.UsageMetadata.CandidatesTokenCount
st.usage.totalTokens = resp.UsageMetadata.TotalTokenCount
st.usage.seen = true
}
if len(resp.Candidates) == 0 {
return nil, nil
}
parts, err := extractGeminiParts(resp.Candidates[0].Content.Parts, &st.pendingToolCallIDs, &st.nextToolCallID)
if err != nil {
return nil, err
}
outputs := make([][]byte, 0, len(parts))
for _, part := range parts {
switch part.Kind {
case partKindText:
if part.Text == "" {
continue
}
chunk := map[string]any{
"type": "response.output_text.delta",
"delta": part.Text,
}
body, err := sonic.Marshal(chunk)
if err != nil {
return nil, err
}
outputs = append(outputs, append([]byte("event: response.output_text.delta\ndata: "), append(body, []byte("\n\n")...)...))
case partKindToolCall:
encoded, err := encodeCodexToolCall(part.ToolCall)
if err != nil {
return nil, err
}
chunk := map[string]any{
"type": "response.output_item.done",
"item": encoded,
}
body, err := sonic.Marshal(chunk)
if err != nil {
return nil, err
}
outputs = append(outputs, append([]byte("event: response.output_item.done\ndata: "), append(body, []byte("\n\n")...)...))
default:
return nil, fmt.Errorf("unsupported gemini response part kind %q", part.Kind)
}
}
if len(outputs) == 0 {
return nil, nil
}
return outputs, nil
}
func convertCodexResponseToGeminiStream(_ context.Context, model string, rawReq, translatedReq, rawJSON []byte, param *any) ([][]byte, error) {
if param == nil {
var local any
param = &local
}
if *param == nil {
*param = &codexToGeminiStreamState{model: model}
}
st := (*param).(*codexToGeminiStreamState)
if st.model == "" {
st.model = model
}
raw := strings.TrimSpace(string(rawJSON))
if raw == "" {
return nil, nil
}
eventType, line := parseSSEEventBlock(raw)
if line == "" {
return nil, nil
}
var payload map[string]any
if err := sonic.Unmarshal([]byte(line), &payload); err != nil {
return nil, err
}
if response, _ := payload["response"].(map[string]any); response != nil {
if responseID := stringValue(response["id"]); responseID != "" {
st.responseID = responseID
}
if responseModel := stringValue(response["model"]); responseModel != "" {
st.model = responseModel
}
if eventType == "response.completed" || stringValue(payload["type"]) == "response.completed" {
includeUsage := false
var promptTokens, candidateTokens, totalTokens int64
if usage := codexUsageFromMap(response["usage"]); usage != nil {
promptTokens = usage.inputTokens
candidateTokens = usage.outputTokens
totalTokens = usage.totalTokens
includeUsage = true
}
body, err := marshalDataSSE(buildGeminiPayloadFromParts(st.model, st.responseID, nil, "STOP", promptTokens, candidateTokens, totalTokens, includeUsage))
if err != nil {
return nil, err
}
return [][]byte{body}, nil
}
}
if eventType == "response.output_text.delta" || stringValue(payload["type"]) == "response.output_text.delta" {
if content := stringValue(payload["delta"]); content != "" {
st.hasOutputTextDelta = true
body, err := marshalDataSSE(buildGeminiPayload(st.model, content, "", 0, 0, 0, false))
if err != nil {
return nil, err
}
return [][]byte{body}, nil
}
}
if eventType == "response.output_item.done" || stringValue(payload["type"]) == "response.output_item.done" {
if item, _ := payload["item"].(map[string]any); item != nil {
switch normalizeRole(stringValue(item["type"])) {
case "function_call":
call, err := decodeCodexToolCall(item)
if err != nil {
return nil, err
}
call.Name = st.restoreToolName(rawReq, translatedReq, call.Name)
args, err := rawJSONToAny(call.Arguments)
if err != nil {
return nil, err
}
body, err := marshalDataSSE(buildGeminiPayloadFromParts(st.model, "", []geminiPart{{
FunctionCall: &geminiFunctionCall{Name: call.Name, Args: args},
}}, "", 0, 0, 0, false))
if err != nil {
return nil, err
}
return [][]byte{body}, nil
case "message":
if st.hasOutputTextDelta {
return nil, nil
}
parts, err := extractCodexContentParts(item["content"])
if err != nil {
return nil, err
}
geminiParts := make([]geminiPart, 0, len(parts))
for _, part := range parts {
if part.Kind != partKindText || part.Text == "" {
continue
}
geminiParts = append(geminiParts, geminiPart{Text: part.Text})
}
if len(geminiParts) == 0 {
return nil, nil
}
st.hasOutputTextDelta = true
body, err := marshalDataSSE(buildGeminiPayloadFromParts(st.model, st.responseID, geminiParts, "", 0, 0, 0, false))
if err != nil {
return nil, err
}
return [][]byte{body}, nil
case "reasoning":
text := extractCodexReasoningText(item)
if text == "" {
return nil, nil
}
body, err := marshalDataSSE(buildGeminiPayload(st.model, text, "", 0, 0, 0, false))
if err != nil {
return nil, err
}
return [][]byte{body}, nil
}
}
}
return nil, nil
}
func (st *codexToGeminiStreamState) restoreToolName(rawReq, translatedReq []byte, name string) string {
if st.toolNameMap == nil {
st.toolNameMap = codexToolAliasesFromRequests(protocol.Gemini, rawReq, translatedReq).ShortToOriginal
}
if original := st.toolNameMap[name]; original != "" {
return original
}
return name
}