Merge pull request #1895 from gaoren002/fix/codex-spark-limitations
fix(openai): handle codex spark model limitations
This commit is contained in:
@@ -48,6 +48,8 @@ type codexTransformResult struct {
|
||||
const (
|
||||
codexImageGenerationBridgeMarker = "<sub2api-codex-image-generation>"
|
||||
codexImageGenerationBridgeText = codexImageGenerationBridgeMarker + "\nWhen the user asks for raster image generation or editing, use the OpenAI Responses native `image_generation` tool attached to this request. The local Codex client may not expose an `image_gen` namespace, but that does not mean image generation is unavailable. Do not ask the user to switch to CLI fallback solely because `image_gen` is absent.\n</sub2api-codex-image-generation>"
|
||||
codexSparkImageUnsupportedMarker = "<sub2api-codex-spark-image-unsupported>"
|
||||
codexSparkImageUnsupportedText = codexSparkImageUnsupportedMarker + "\nThe current model is gpt-5.3-codex-spark, which does not support image generation, image editing, image input, the `image_generation` tool, or Codex `image_gen`/`$imagegen` workflows. If the user asks for image generation or image editing, clearly explain this model limitation and ask them to switch to a non-Spark Codex model such as gpt-5.3-codex or gpt-5.4. Do not claim that the local environment merely lacks image_gen tooling, and do not suggest CLI fallback as the primary fix while the model remains Spark.\n</sub2api-codex-spark-image-unsupported>"
|
||||
)
|
||||
|
||||
func applyCodexOAuthTransform(reqBody map[string]any, isCodexCLI bool, isCompact bool) codexTransformResult {
|
||||
@@ -165,6 +167,9 @@ func applyCodexOAuthTransform(reqBody map[string]any, isCodexCLI bool, isCompact
|
||||
if applyInstructions(reqBody, isCodexCLI) {
|
||||
result.Modified = true
|
||||
}
|
||||
if isCodexSparkModel(normalizedModel) && applyCodexSparkImageUnsupportedInstructions(reqBody) {
|
||||
result.Modified = true
|
||||
}
|
||||
|
||||
// 续链场景保留 item_reference 与 id,避免 call_id 上下文丢失。
|
||||
if input, ok := reqBody["input"].([]any); ok {
|
||||
@@ -244,6 +249,10 @@ func normalizeCodexModel(model string) string {
|
||||
return "gpt-5.4"
|
||||
}
|
||||
|
||||
func isCodexSparkModel(model string) bool {
|
||||
return normalizeCodexModel(model) == "gpt-5.3-codex-spark"
|
||||
}
|
||||
|
||||
func hasOpenAIImageGenerationTool(reqBody map[string]any) bool {
|
||||
rawTools, ok := reqBody["tools"]
|
||||
if !ok || rawTools == nil {
|
||||
@@ -265,6 +274,40 @@ func hasOpenAIImageGenerationTool(reqBody map[string]any) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func hasOpenAIInputImage(reqBody map[string]any) bool {
|
||||
if reqBody == nil {
|
||||
return false
|
||||
}
|
||||
return hasOpenAIInputImageValue(reqBody["input"]) || hasOpenAIInputImageValue(reqBody["messages"])
|
||||
}
|
||||
|
||||
func hasOpenAIInputImageValue(value any) bool {
|
||||
switch v := value.(type) {
|
||||
case []any:
|
||||
for _, item := range v {
|
||||
if hasOpenAIInputImageValue(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case map[string]any:
|
||||
if strings.TrimSpace(firstNonEmptyString(v["type"])) == "input_image" {
|
||||
return true
|
||||
}
|
||||
if _, ok := v["image_url"]; ok {
|
||||
return true
|
||||
}
|
||||
return hasOpenAIInputImageValue(v["content"])
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validateCodexSparkInput(reqBody map[string]any, model string) error {
|
||||
if !isCodexSparkModel(model) || !hasOpenAIInputImage(reqBody) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("model %q does not support image input", strings.TrimSpace(model))
|
||||
}
|
||||
|
||||
func normalizeOpenAIResponsesImageGenerationTools(reqBody map[string]any) bool {
|
||||
rawTools, ok := reqBody["tools"]
|
||||
if !ok || rawTools == nil {
|
||||
@@ -309,6 +352,9 @@ func ensureOpenAIResponsesImageGenerationTool(reqBody map[string]any) bool {
|
||||
if len(reqBody) == 0 {
|
||||
return false
|
||||
}
|
||||
if isCodexSparkModel(firstNonEmptyString(reqBody["model"])) {
|
||||
return false
|
||||
}
|
||||
|
||||
tool := map[string]any{
|
||||
"type": "image_generation",
|
||||
@@ -344,6 +390,9 @@ func applyCodexImageGenerationBridgeInstructions(reqBody map[string]any) bool {
|
||||
if len(reqBody) == 0 || !hasOpenAIImageGenerationTool(reqBody) {
|
||||
return false
|
||||
}
|
||||
if isCodexSparkModel(firstNonEmptyString(reqBody["model"])) {
|
||||
return false
|
||||
}
|
||||
|
||||
existing, _ := reqBody["instructions"].(string)
|
||||
if strings.Contains(existing, codexImageGenerationBridgeMarker) {
|
||||
@@ -360,6 +409,23 @@ func applyCodexImageGenerationBridgeInstructions(reqBody map[string]any) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func applyCodexSparkImageUnsupportedInstructions(reqBody map[string]any) bool {
|
||||
if len(reqBody) == 0 {
|
||||
return false
|
||||
}
|
||||
existing, _ := reqBody["instructions"].(string)
|
||||
if strings.Contains(existing, codexSparkImageUnsupportedMarker) {
|
||||
return false
|
||||
}
|
||||
existing = strings.TrimRight(existing, " \t\r\n")
|
||||
if strings.TrimSpace(existing) == "" {
|
||||
reqBody["instructions"] = codexSparkImageUnsupportedText
|
||||
return true
|
||||
}
|
||||
reqBody["instructions"] = existing + "\n\n" + codexSparkImageUnsupportedText
|
||||
return true
|
||||
}
|
||||
|
||||
func validateOpenAIResponsesImageModel(reqBody map[string]any, model string) error {
|
||||
if !hasOpenAIImageGenerationTool(reqBody) {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user