diff --git a/.gitignore b/.gitignore index bab49132..2b9c215a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ logs/* conv/* temp/* refs/* +tmp/* # Storage backends pgstore/* diff --git a/go.mod b/go.mod index 73b40e9b..78451bc4 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.0 require ( github.com/andybalholm/brotli v1.0.6 github.com/fsnotify/fsnotify v1.9.0 + github.com/fxamacker/cbor/v2 v2.9.0 github.com/gin-gonic/gin v1.10.1 github.com/go-git/go-git/v6 v6.0.0-20251009132922-75a182125145 github.com/google/uuid v1.6.0 @@ -13,8 +14,8 @@ require ( github.com/joho/godotenv v1.5.1 github.com/klauspost/compress v1.17.4 github.com/minio/minio-go/v7 v7.0.66 - github.com/refraction-networking/utls v1.8.2 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c + github.com/refraction-networking/utls v1.8.2 github.com/sirupsen/logrus v1.9.3 github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 @@ -41,7 +42,6 @@ require ( github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-git/gcfg/v2 v2.0.2 // indirect diff --git a/internal/api/handlers/management/config_lists.go b/internal/api/handlers/management/config_lists.go index 4e0e0284..15ec3a24 100644 --- a/internal/api/handlers/management/config_lists.go +++ b/internal/api/handlers/management/config_lists.go @@ -754,18 +754,22 @@ func (h *Handler) PatchOAuthModelAlias(c *gin.Context) { normalizedMap := sanitizedOAuthModelAlias(map[string][]config.OAuthModelAlias{channel: body.Aliases}) normalized := normalizedMap[channel] if len(normalized) == 0 { + // Only delete if channel exists, otherwise just create empty entry + if h.cfg.OAuthModelAlias != nil { + if _, ok := h.cfg.OAuthModelAlias[channel]; ok { + delete(h.cfg.OAuthModelAlias, channel) + if len(h.cfg.OAuthModelAlias) == 0 { + h.cfg.OAuthModelAlias = nil + } + h.persist(c) + return + } + } + // Create new channel with empty aliases if h.cfg.OAuthModelAlias == nil { - c.JSON(404, gin.H{"error": "channel not found"}) - return - } - if _, ok := h.cfg.OAuthModelAlias[channel]; !ok { - c.JSON(404, gin.H{"error": "channel not found"}) - return - } - delete(h.cfg.OAuthModelAlias, channel) - if len(h.cfg.OAuthModelAlias) == 0 { - h.cfg.OAuthModelAlias = nil + h.cfg.OAuthModelAlias = make(map[string][]config.OAuthModelAlias) } + h.cfg.OAuthModelAlias[channel] = []config.OAuthModelAlias{} h.persist(c) return } diff --git a/internal/registry/model_definitions.go b/internal/registry/model_definitions.go index 7bf4aae2..954ecc7f 100644 --- a/internal/registry/model_definitions.go +++ b/internal/registry/model_definitions.go @@ -19,6 +19,7 @@ import ( // - codex // - qwen // - iflow +// - github-copilot // - antigravity (returns static overrides only) func GetStaticModelDefinitionsByChannel(channel string) []*ModelInfo { key := strings.ToLower(strings.TrimSpace(channel)) @@ -39,6 +40,8 @@ func GetStaticModelDefinitionsByChannel(channel string) []*ModelInfo { return GetQwenModels() case "iflow": return GetIFlowModels() + case "github-copilot": + return GetGitHubCopilotModels() case "antigravity": cfg := GetAntigravityModelConfig() if len(cfg) == 0 { diff --git a/internal/runtime/executor/gemini_vertex_executor.go b/internal/runtime/executor/gemini_vertex_executor.go index 83456a86..2db0e37c 100644 --- a/internal/runtime/executor/gemini_vertex_executor.go +++ b/internal/runtime/executor/gemini_vertex_executor.go @@ -1003,6 +1003,8 @@ func vertexBaseURL(location string) string { loc := strings.TrimSpace(location) if loc == "" { loc = "us-central1" + } else if loc == "global" { + return "https://aiplatform.googleapis.com" } return fmt.Sprintf("https://%s-aiplatform.googleapis.com", loc) } diff --git a/internal/translator/kiro/claude/kiro_claude_request.go b/internal/translator/kiro/claude/kiro_claude_request.go index c9e7a3db..4e498c24 100644 --- a/internal/translator/kiro/claude/kiro_claude_request.go +++ b/internal/translator/kiro/claude/kiro_claude_request.go @@ -219,7 +219,26 @@ func BuildKiroPayload(claudeBody []byte, modelID, profileArn, origin string, isA } // Convert Claude tools to Kiro format - kiroTools := convertClaudeToolsToKiro(tools) + kiroTools, hasWebSearch := convertClaudeToolsToKiro(tools) + + // If web_search was requested but filtered, inject alternative hint + if hasWebSearch { + webSearchHint := `[CRITICAL WEB ACCESS INSTRUCTION] +You have the Fetch/read_url_content tool available. When the user asks about current events, weather, news, or any information that requires web access: +- DO NOT say you cannot search the web +- DO NOT refuse to help with web-related queries +- IMMEDIATELY use the Fetch tool to access relevant URLs +- Use well-known official websites, documentation sites, or API endpoints +- Construct appropriate URLs based on the query context + +IMPORTANT: Always attempt to fetch information FIRST before declining. You CAN access the web via Fetch.` + if systemPrompt != "" { + systemPrompt += "\n" + webSearchHint + } else { + systemPrompt = webSearchHint + } + log.Infof("kiro: injected web_search alternative hint (tool was filtered)") + } // Thinking mode implementation: // Kiro API supports official thinking/reasoning mode via tag. @@ -508,15 +527,27 @@ func ensureKiroInputSchema(parameters interface{}) interface{} { } } -// convertClaudeToolsToKiro converts Claude tools to Kiro format -func convertClaudeToolsToKiro(tools gjson.Result) []KiroToolWrapper { +// convertClaudeToolsToKiro converts Claude tools to Kiro format. +// Returns the converted tools and a boolean indicating if web_search was filtered. +func convertClaudeToolsToKiro(tools gjson.Result) ([]KiroToolWrapper, bool) { var kiroTools []KiroToolWrapper + hasWebSearch := false if !tools.IsArray() { - return kiroTools + return kiroTools, hasWebSearch } for _, tool := range tools.Array() { name := tool.Get("name").String() + + // Filter out web_search/websearch tools (Kiro API doesn't support them) + // This matches the behavior in AIClient-2-API/claude-kiro.js + nameLower := strings.ToLower(name) + if nameLower == "web_search" || nameLower == "websearch" { + log.Debugf("kiro: skipping unsupported tool: %s", name) + hasWebSearch = true + continue + } + description := tool.Get("description").String() inputSchemaResult := tool.Get("input_schema") var inputSchema interface{} @@ -560,7 +591,7 @@ func convertClaudeToolsToKiro(tools gjson.Result) []KiroToolWrapper { // This prevents 500 errors when Claude Code sends too many tools kiroTools = compressToolsIfNeeded(kiroTools) - return kiroTools + return kiroTools, hasWebSearch } // processMessages processes Claude messages and builds Kiro history