Compare commits

...

14 Commits

Author SHA1 Message Date
Luis Pater
2eb2dbb266 Merge branch 'router-for-me:main' into main 2025-12-10 23:56:59 +08:00
Luis Pater
e717939edb Fixed: #478
feat(antigravity): add support for inline image data in client responses
2025-12-10 23:55:53 +08:00
Luis Pater
9f72a875f8 Merge branch 'router-for-me:main' into main 2025-12-10 16:54:31 +08:00
Luis Pater
94d61c7b2b fix(logging): update response aggregation logic to include all attempts 2025-12-10 16:53:48 +08:00
Luis Pater
f999650322 Merge branch 'router-for-me:main' into main 2025-12-10 16:04:18 +08:00
Luis Pater
1249b07eb8 feat(responses): add unique identifiers for responses, function calls, and tool uses 2025-12-10 16:02:54 +08:00
Luis Pater
38319b0483 Merge branch 'router-for-me:main' into main 2025-12-10 15:29:19 +08:00
Luis Pater
6b37f33d31 feat(antigravity): add unique identifier for tool use blocks in response 2025-12-10 15:27:57 +08:00
Luis Pater
af543238aa Merge pull request #16 from fuko2935/fix/remove-unstable-kiro-auto
fix(registry): remove unstable kiro-auto model
2025-12-10 00:23:31 +08:00
Luis Pater
2de27c560b Merge pull request #15 from StriveMario/feature/fix-kiro-refreshtoken
fix kiro cannot refresh the token
2025-12-10 00:22:36 +08:00
Luis Pater
773ed6cc64 Merge branch 'router-for-me:main' into main 2025-12-10 00:17:43 +08:00
fuko2935
a594338bc5 fix(registry): remove unstable kiro-auto model
- Removes kiro-auto from static model registry
- Removes kiro-auto mapping from executor
- Fixes compatibility issues reported in #7

Fixes #7
2025-12-09 19:14:40 +03:00
Luis Pater
f25f419e5a fix(antigravity): remove references to autopush endpoint and update fallback logic 2025-12-10 00:13:20 +08:00
Mario
1fa5514d56 fix kiro cannot refresh the token 2025-12-09 20:13:16 +08:00
14 changed files with 88 additions and 40 deletions

View File

@@ -1195,17 +1195,6 @@ func GetGitHubCopilotModels() []*ModelInfo {
// GetKiroModels returns the Kiro (AWS CodeWhisperer) model definitions // GetKiroModels returns the Kiro (AWS CodeWhisperer) model definitions
func GetKiroModels() []*ModelInfo { func GetKiroModels() []*ModelInfo {
return []*ModelInfo{ return []*ModelInfo{
{
ID: "kiro-auto",
Object: "model",
Created: 1732752000, // 2024-11-28
OwnedBy: "aws",
Type: "kiro",
DisplayName: "Kiro Auto",
Description: "Automatic model selection by AWS CodeWhisperer",
ContextLength: 200000,
MaxCompletionTokens: 64000,
},
{ {
ID: "kiro-claude-opus-4.5", ID: "kiro-claude-opus-4.5",
Object: "model", Object: "model",

View File

@@ -28,18 +28,18 @@ import (
) )
const ( const (
antigravityBaseURLDaily = "https://daily-cloudcode-pa.sandbox.googleapis.com" antigravityBaseURLDaily = "https://daily-cloudcode-pa.sandbox.googleapis.com"
antigravityBaseURLAutopush = "https://autopush-cloudcode-pa.sandbox.googleapis.com" // antigravityBaseURLAutopush = "https://autopush-cloudcode-pa.sandbox.googleapis.com"
antigravityBaseURLProd = "https://cloudcode-pa.googleapis.com" antigravityBaseURLProd = "https://cloudcode-pa.googleapis.com"
antigravityStreamPath = "/v1internal:streamGenerateContent" antigravityStreamPath = "/v1internal:streamGenerateContent"
antigravityGeneratePath = "/v1internal:generateContent" antigravityGeneratePath = "/v1internal:generateContent"
antigravityModelsPath = "/v1internal:fetchAvailableModels" antigravityModelsPath = "/v1internal:fetchAvailableModels"
antigravityClientID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com" antigravityClientID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com"
antigravityClientSecret = "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" antigravityClientSecret = "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"
defaultAntigravityAgent = "antigravity/1.11.5 windows/amd64" defaultAntigravityAgent = "antigravity/1.11.5 windows/amd64"
antigravityAuthType = "antigravity" antigravityAuthType = "antigravity"
refreshSkew = 3000 * time.Second refreshSkew = 3000 * time.Second
streamScannerBuffer int = 20_971_520 streamScannerBuffer int = 20_971_520
) )
var ( var (
@@ -665,7 +665,7 @@ func buildBaseURL(auth *cliproxyauth.Auth) string {
if baseURLs := antigravityBaseURLFallbackOrder(auth); len(baseURLs) > 0 { if baseURLs := antigravityBaseURLFallbackOrder(auth); len(baseURLs) > 0 {
return baseURLs[0] return baseURLs[0]
} }
return antigravityBaseURLAutopush return antigravityBaseURLDaily
} }
func resolveHost(base string) string { func resolveHost(base string) string {
@@ -701,7 +701,7 @@ func antigravityBaseURLFallbackOrder(auth *cliproxyauth.Auth) []string {
} }
return []string{ return []string{
antigravityBaseURLDaily, antigravityBaseURLDaily,
antigravityBaseURLAutopush, // antigravityBaseURLAutopush,
antigravityBaseURLProd, antigravityBaseURLProd,
} }
} }

View File

@@ -547,8 +547,6 @@ func kiroCredentials(auth *cliproxyauth.Auth) (accessToken, profileArn string) {
// Agentic variants (-agentic suffix) map to the same backend model IDs. // Agentic variants (-agentic suffix) map to the same backend model IDs.
func (e *KiroExecutor) mapModelToKiro(model string) string { func (e *KiroExecutor) mapModelToKiro(model string) string {
modelMap := map[string]string{ modelMap := map[string]string{
// Proxy format (kiro- prefix)
"kiro-auto": "auto",
"kiro-claude-opus-4.5": "claude-opus-4.5", "kiro-claude-opus-4.5": "claude-opus-4.5",
"kiro-claude-sonnet-4.5": "claude-sonnet-4.5", "kiro-claude-sonnet-4.5": "claude-sonnet-4.5",
"kiro-claude-sonnet-4": "claude-sonnet-4", "kiro-claude-sonnet-4": "claude-sonnet-4",

View File

@@ -157,7 +157,7 @@ func appendAPIResponseChunk(ctx context.Context, cfg *config.Config, chunk []byt
if ginCtx == nil { if ginCtx == nil {
return return
} }
_, attempt := ensureAttempt(ginCtx) attempts, attempt := ensureAttempt(ginCtx)
ensureResponseIntro(attempt) ensureResponseIntro(attempt)
if !attempt.headersWritten { if !attempt.headersWritten {
@@ -175,6 +175,8 @@ func appendAPIResponseChunk(ctx context.Context, cfg *config.Config, chunk []byt
} }
attempt.response.WriteString(string(data)) attempt.response.WriteString(string(data))
attempt.bodyHasContent = true attempt.bodyHasContent = true
updateAggregatedResponse(ginCtx, attempts)
} }
func ginContextFrom(ctx context.Context) *gin.Context { func ginContextFrom(ctx context.Context) *gin.Context {

View File

@@ -123,6 +123,15 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
functionResponse := client.FunctionResponse{ID: toolCallID, Name: funcName, Response: map[string]interface{}{"result": responseData}} functionResponse := client.FunctionResponse{ID: toolCallID, Name: funcName, Response: map[string]interface{}{"result": responseData}}
clientContent.Parts = append(clientContent.Parts, client.Part{FunctionResponse: &functionResponse}) clientContent.Parts = append(clientContent.Parts, client.Part{FunctionResponse: &functionResponse})
} }
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "image" {
sourceResult := contentResult.Get("source")
if sourceResult.Get("type").String() == "base64" {
inlineData := &client.InlineData{
MimeType: sourceResult.Get("media_type").String(),
Data: sourceResult.Get("data").String(),
}
clientContent.Parts = append(clientContent.Parts, client.Part{InlineData: inlineData})
}
} }
} }
contents = append(contents, clientContent) contents = append(contents, clientContent)

View File

@@ -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"
@@ -36,6 +37,9 @@ type Params struct {
HasToolUse bool // Indicates if tool use was observed in the stream HasToolUse bool // Indicates if tool use was observed in the stream
} }
// toolUseIDCounter provides a process-wide unique counter for tool use identifiers.
var toolUseIDCounter uint64
// ConvertAntigravityResponseToClaude performs sophisticated streaming response format conversion. // ConvertAntigravityResponseToClaude 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
@@ -216,7 +220,7 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq
// 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":{}}}`, params.ResponseIndex) data := fmt.Sprintf(`{"type":"content_block_start","index":%d,"content_block":{"type":"tool_use","id":"","name":"","input":{}}}`, 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)

View File

@@ -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() {

View File

@@ -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
@@ -180,7 +184,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)
sb.WriteString(fmt.Sprintf("data: %s\n\n\n", data)) sb.WriteString(fmt.Sprintf("data: %s\n\n\n", data))

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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",

View File

@@ -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)

View File

@@ -1272,7 +1272,7 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
} }
for i := range cfg.KiroKey { for i := range cfg.KiroKey {
kk := cfg.KiroKey[i] kk := cfg.KiroKey[i]
var accessToken, profileArn string var accessToken, profileArn, refreshToken string
// Try to load from token file first // Try to load from token file first
if kk.TokenFile != "" && kAuth != nil { if kk.TokenFile != "" && kAuth != nil {
@@ -1282,6 +1282,7 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
} else { } else {
accessToken = tokenData.AccessToken accessToken = tokenData.AccessToken
profileArn = tokenData.ProfileArn profileArn = tokenData.ProfileArn
refreshToken = tokenData.RefreshToken
} }
} }
@@ -1292,6 +1293,9 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
if kk.ProfileArn != "" { if kk.ProfileArn != "" {
profileArn = kk.ProfileArn profileArn = kk.ProfileArn
} }
if kk.RefreshToken != "" {
refreshToken = kk.RefreshToken
}
if accessToken == "" { if accessToken == "" {
log.Warnf("kiro config[%d] missing access_token, skipping", i) log.Warnf("kiro config[%d] missing access_token, skipping", i)
@@ -1313,6 +1317,9 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
if kk.AgentTaskType != "" { if kk.AgentTaskType != "" {
attrs["agent_task_type"] = kk.AgentTaskType attrs["agent_task_type"] = kk.AgentTaskType
} }
if refreshToken != "" {
attrs["refresh_token"] = refreshToken
}
proxyURL := strings.TrimSpace(kk.ProxyURL) proxyURL := strings.TrimSpace(kk.ProxyURL)
a := &coreauth.Auth{ a := &coreauth.Auth{
ID: id, ID: id,
@@ -1324,6 +1331,14 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
if refreshToken != "" {
if a.Metadata == nil {
a.Metadata = make(map[string]any)
}
a.Metadata["refresh_token"] = refreshToken
}
out = append(out, a) out = append(out, a)
} }
for i := range cfg.OpenAICompatibility { for i := range cfg.OpenAICompatibility {