Unverified Commit 55796a11 authored by Wesley Liddick's avatar Wesley Liddick Committed by GitHub
Browse files

Merge pull request #264 from IanShaw027/fix/openai-opencode-compatibility

fix(openai): 增强 OpenCode 兼容性和模型规范化
parents 93db889a d7fa47d7
package service package service
import ( import (
_ "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
...@@ -16,6 +17,9 @@ const ( ...@@ -16,6 +17,9 @@ const (
codexCacheTTL = 15 * time.Minute codexCacheTTL = 15 * time.Minute
) )
//go:embed prompts/codex_cli_instructions.md
var codexCLIInstructions string
var codexModelMap = map[string]string{ var codexModelMap = map[string]string{
"gpt-5.1-codex": "gpt-5.1-codex", "gpt-5.1-codex": "gpt-5.1-codex",
"gpt-5.1-codex-low": "gpt-5.1-codex", "gpt-5.1-codex-low": "gpt-5.1-codex",
...@@ -119,6 +123,13 @@ func applyCodexOAuthTransform(reqBody map[string]any) codexTransformResult { ...@@ -119,6 +123,13 @@ func applyCodexOAuthTransform(reqBody map[string]any) codexTransformResult {
reqBody["instructions"] = instructions reqBody["instructions"] = instructions
result.Modified = true result.Modified = true
} }
} else if existingInstructions == "" {
// If no opencode instructions available, try codex CLI instructions
codexInstructions := strings.TrimSpace(getCodexCLIInstructions())
if codexInstructions != "" {
reqBody["instructions"] = codexInstructions
result.Modified = true
}
} }
if input, ok := reqBody["input"].([]any); ok { if input, ok := reqBody["input"].([]any); ok {
...@@ -235,13 +246,69 @@ func getOpenCodeCachedPrompt(url, cacheFileName, metaFileName string) string { ...@@ -235,13 +246,69 @@ func getOpenCodeCachedPrompt(url, cacheFileName, metaFileName string) string {
} }
func getOpenCodeCodexHeader() string { func getOpenCodeCodexHeader() string {
return getOpenCodeCachedPrompt(opencodeCodexHeaderURL, "opencode-codex-header.txt", "opencode-codex-header-meta.json") // Try to get from opencode repository first
opencodeInstructions := getOpenCodeCachedPrompt(opencodeCodexHeaderURL, "opencode-codex-header.txt", "opencode-codex-header-meta.json")
// If opencode instructions are available, return them
if opencodeInstructions != "" {
return opencodeInstructions
}
// Fallback to local codex CLI instructions
return getCodexCLIInstructions()
}
func getCodexCLIInstructions() string {
return codexCLIInstructions
} }
func GetOpenCodeInstructions() string { func GetOpenCodeInstructions() string {
return getOpenCodeCodexHeader() return getOpenCodeCodexHeader()
} }
func GetCodexCLIInstructions() string {
return getCodexCLIInstructions()
}
func ReplaceWithCodexInstructions(reqBody map[string]any) bool {
codexInstructions := strings.TrimSpace(getCodexCLIInstructions())
if codexInstructions == "" {
return false
}
existingInstructions, _ := reqBody["instructions"].(string)
if strings.TrimSpace(existingInstructions) != codexInstructions {
reqBody["instructions"] = codexInstructions
return true
}
return false
}
func IsInstructionError(errorMessage string) bool {
if errorMessage == "" {
return false
}
lowerMsg := strings.ToLower(errorMessage)
instructionKeywords := []string{
"instruction",
"instructions",
"system prompt",
"system message",
"invalid prompt",
"prompt format",
}
for _, keyword := range instructionKeywords {
if strings.Contains(lowerMsg, keyword) {
return true
}
}
return false
}
func filterCodexInput(input []any) []any { func filterCodexInput(input []any) []any {
filtered := make([]any, 0, len(input)) filtered := make([]any, 0, len(input))
for _, item := range input { for _, item := range input {
...@@ -250,15 +317,36 @@ func filterCodexInput(input []any) []any { ...@@ -250,15 +317,36 @@ func filterCodexInput(input []any) []any {
filtered = append(filtered, item) filtered = append(filtered, item)
continue continue
} }
if typ, ok := m["type"].(string); ok && typ == "item_reference" { typ, _ := m["type"].(string)
if typ == "item_reference" {
filtered = append(filtered, m)
continue continue
} }
// Strip per-item ids; keep call_id only for tool call items so outputs can match.
if isCodexToolCallItemType(typ) {
callID, _ := m["call_id"].(string)
if strings.TrimSpace(callID) == "" {
if id, ok := m["id"].(string); ok && strings.TrimSpace(id) != "" {
m["call_id"] = id
}
}
}
delete(m, "id") delete(m, "id")
if !isCodexToolCallItemType(typ) {
delete(m, "call_id")
}
filtered = append(filtered, m) filtered = append(filtered, m)
} }
return filtered return filtered
} }
func isCodexToolCallItemType(typ string) bool {
if typ == "" {
return false
}
return strings.HasSuffix(typ, "_call") || strings.HasSuffix(typ, "_call_output")
}
func normalizeCodexTools(reqBody map[string]any) bool { func normalizeCodexTools(reqBody map[string]any) bool {
rawTools, ok := reqBody["tools"] rawTools, ok := reqBody["tools"]
if !ok || rawTools == nil { if !ok || rawTools == nil {
......
...@@ -42,6 +42,7 @@ var openaiSSEDataRe = regexp.MustCompile(`^data:\s*`) ...@@ -42,6 +42,7 @@ var openaiSSEDataRe = regexp.MustCompile(`^data:\s*`)
var openaiAllowedHeaders = map[string]bool{ var openaiAllowedHeaders = map[string]bool{
"accept-language": true, "accept-language": true,
"content-type": true, "content-type": true,
"conversation_id": true,
"user-agent": true, "user-agent": true,
"originator": true, "originator": true,
"session_id": true, "session_id": true,
...@@ -553,6 +554,27 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco ...@@ -553,6 +554,27 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
bodyModified = true bodyModified = true
} }
// Apply Codex model normalization for all OpenAI accounts
if model, ok := reqBody["model"].(string); ok {
normalizedModel := normalizeCodexModel(model)
if normalizedModel != "" && normalizedModel != model {
log.Printf("[OpenAI] Codex model normalization: %s -> %s (account: %s, type: %s, isCodexCLI: %v)",
model, normalizedModel, account.Name, account.Type, isCodexCLI)
reqBody["model"] = normalizedModel
mappedModel = normalizedModel
bodyModified = true
}
}
// Normalize reasoning.effort parameter (minimal -> none)
if reasoning, ok := reqBody["reasoning"].(map[string]any); ok {
if effort, ok := reasoning["effort"].(string); ok && effort == "minimal" {
reasoning["effort"] = "none"
bodyModified = true
log.Printf("[OpenAI] Normalized reasoning.effort: minimal -> none (account: %s)", account.Name)
}
}
if account.Type == AccountTypeOAuth && !isCodexCLI { if account.Type == AccountTypeOAuth && !isCodexCLI {
codexResult := applyCodexOAuthTransform(reqBody) codexResult := applyCodexOAuthTransform(reqBody)
if codexResult.Modified { if codexResult.Modified {
...@@ -783,9 +805,6 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin. ...@@ -783,9 +805,6 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
if promptCacheKey != "" { if promptCacheKey != "" {
req.Header.Set("conversation_id", promptCacheKey) req.Header.Set("conversation_id", promptCacheKey)
req.Header.Set("session_id", promptCacheKey) req.Header.Set("session_id", promptCacheKey)
} else {
req.Header.Del("conversation_id")
req.Header.Del("session_id")
} }
} }
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment