Files
sub2api/backend/internal/pkg/openai/constants.go
T

230 lines
9.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package openai provides helpers and types for OpenAI API integration.
package openai
import (
_ "embed"
"encoding/json"
"fmt"
"strings"
)
// Model represents an OpenAI model
type Model struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
OwnedBy string `json:"owned_by"`
Type string `json:"type"`
DisplayName string `json:"display_name"`
}
// DefaultModels OpenAI models list
var DefaultModels = []Model{
{ID: "gpt-5.6-sol", Object: "model", Created: 1780876800, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.6 Sol"},
{ID: "gpt-6", Object: "model", Created: 1788480000, OwnedBy: "openai", Type: "model", DisplayName: "GPT-6 (Astra)"},
{ID: "gpt-5.6", Object: "model", Created: 1780876800, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.6 (Sol)"},
{ID: "gpt-5.6-terra", Object: "model", Created: 1780876800, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.6 Terra"},
{ID: "gpt-5.6-luna", Object: "model", Created: 1780876800, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.6 Luna"},
{ID: "gpt-6.1-sol", Object: "model", Created: 1790640000, OwnedBy: "openai", Type: "model", DisplayName: "GPT-6.1 Sol"},
{ID: "gpt-6-sol", Object: "model", Created: 1790035200, OwnedBy: "openai", Type: "model", DisplayName: "GPT-6 Sol"},
{ID: "gpt-6-luna", Object: "model", Created: 1790035200, OwnedBy: "openai", Type: "model", DisplayName: "GPT-6 Luna"},
{ID: "gpt-6-astra", Object: "model", Created: 1788480000, OwnedBy: "openai", Type: "model", DisplayName: "GPT-6 Astra"},
{ID: "gpt-5.5", Object: "model", Created: 1776873600, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.5"},
{ID: "gpt-5.4", Object: "model", Created: 1738368000, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.4"},
{ID: "gpt-5.4-mini", Object: "model", Created: 1738368000, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.4 Mini"},
{ID: "gpt-5.3-codex-spark", Object: "model", Created: 1735689600, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.3 Codex Spark"},
{ID: "codex-auto-review", Object: "model", Created: 1776902400, OwnedBy: "openai", Type: "model", DisplayName: "Codex Auto Review"},
{ID: "gpt-5.2", Object: "model", Created: 1733875200, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.2"},
{ID: "gpt-image-1", Object: "model", Created: 1733875200, OwnedBy: "openai", Type: "model", DisplayName: "GPT Image 1"},
{ID: "gpt-image-1.5", Object: "model", Created: 1735689600, OwnedBy: "openai", Type: "model", DisplayName: "GPT Image 1.5"},
{ID: "gpt-image-2", Object: "model", Created: 1738368000, OwnedBy: "openai", Type: "model", DisplayName: "GPT Image 2"},
{ID: "gpt-image-2.5-flare", Object: "model", Created: 1788825600, OwnedBy: "openai", Type: "model", DisplayName: "GPT Image 2.5 Flare"},
{ID: "gpt-image-2.5-sunburst", Object: "model", Created: 1788825600, OwnedBy: "openai", Type: "model", DisplayName: "GPT Image 2.5 Sunburst"},
}
// DefaultModelIDs returns the default model ID list
func DefaultModelIDs() []string {
ids := make([]string, len(DefaultModels))
for i, m := range DefaultModels {
ids[i] = m.ID
}
return ids
}
// DefaultTestModel default model for testing OpenAI accounts
const DefaultTestModel = "gpt-5.4"
// CodexUsageProbeModel is the model used for OAuth Codex usage probes.
const CodexUsageProbeModel = "codex-auto-review"
// DefaultInstructions default instructions for non-Codex CLI requests.
// 内容为真实 Codex CLI 的 GPT-5-Codex base prompt(codex 系模型默认)。
//
//go:embed instructions.txt
var DefaultInstructions string
// instructionsGPT51 / instructionsGPT52 / instructionsGPT55 / instructionsGPT6Astra
// 为对应非 codex 模型的真实 Codex 编码 agent base prompt,用于模型感知的 instructions 选择。
// GPT-5.5 同时作为 GPT-5 系列的 fallback(覆盖 5.3 / 5.4 等未单独维护 prompt 的版本)。
//
//go:embed instructions_gpt5_1.txt
var instructionsGPT51 string
//go:embed instructions_gpt5_2.txt
var instructionsGPT52 string
//go:embed instructions_gpt5_5.txt
var instructionsGPT55 string
// Source: openai/codex codex-rs/models-manager/models.json at 121f91fd5d9d.
//
//go:embed instructions_gpt6_astra.txt
var instructionsGPT6Astra string
// CodexGPT61SolMetadata is the complete official descriptor from openai/codex
// b1e72963c3b71a9265a551e54beff078384efed9, codex-rs/models-manager/models.json.
//
//go:embed codex_gpt61_sol.json
var CodexGPT61SolMetadata []byte
// latestCodexInstructions 返回当前已知最新版本的 Codex base instructions,
// 当前为 GPT-5.5;若 5.5 prompt 意外为空则回退到 DefaultInstructions 保证非空。
func latestCodexInstructions() string {
if v := strings.TrimSpace(instructionsGPT55); v != "" {
return instructionsGPT55
}
return DefaultInstructions
}
// CanonicalizeOpenAIModelAliasSpelling normalizes provider prefixes, case,
// separators, and known compact spellings used by OpenAI model aliases.
func CanonicalizeOpenAIModelAliasSpelling(model string) string {
model = strings.TrimSpace(model)
if slash := strings.LastIndexByte(model, '/'); slash >= 0 {
model = strings.TrimSpace(model[slash+1:])
}
model = strings.ToLower(model)
if model == "" {
return ""
}
normalized := strings.ReplaceAll(model, "_", "-")
normalized = strings.Join(strings.Fields(normalized), "-")
for strings.Contains(normalized, "--") {
normalized = strings.ReplaceAll(normalized, "--", "-")
}
if strings.HasPrefix(normalized, "gpt5") {
normalized = "gpt-5" + strings.TrimPrefix(normalized, "gpt5")
}
if !strings.HasPrefix(normalized, "gpt-") && !strings.Contains(normalized, "codex") {
return ""
}
replacements := []struct {
from string
to string
}{
{"gpt-5.4mini", "gpt-5.4-mini"},
{"gpt-5.4nano", "gpt-5.4-nano"},
{"gpt-5.3-codexspark", "gpt-5.3-codex-spark"},
{"gpt-5.3codexspark", "gpt-5.3-codex-spark"},
{"gpt-5.3codex", "gpt-5.3-codex"},
}
for _, replacement := range replacements {
normalized = strings.ReplaceAll(normalized, replacement.from, replacement.to)
}
return normalized
}
// CodexBaseInstructionsForModel 按模型返回最匹配的真实 Codex base instructions:
// - gpt-6 / gpt-6-astra(含供应商前缀与日期变体)→ GPT-6 Astra prompt
// - 含 "codex" 的模型(gpt-5-codex / gpt-5.x-codex / codex-max / spark 等)→ GPT-5-Codex prompt
// - gpt-5.5 系非 codex 模型 → GPT-5.5 prompt
// - gpt-5.2 系非 codex 模型 → GPT-5.2 prompt
// - gpt-5.1 系非 codex 模型 → GPT-5.1 prompt
// - 其它(含 gpt-5.3 / gpt-5.4 / 裸 gpt-5 / 未知模型)→ 回退到最新版本(当前 GPT-5.5)
//
// 任一专用 prompt 意外为空时回退链最终落到 DefaultInstructions,保证返回非空。
func CodexBaseInstructionsForModel(model string) string {
canonical := CanonicalizeOpenAIModelAliasSpelling(model)
switch {
case IsGPT61SolModelSpelling(canonical):
var metadata struct {
ModelMessages struct {
InstructionsTemplate string `json:"instructions_template"`
} `json:"model_messages"`
}
if err := json.Unmarshal(CodexGPT61SolMetadata, &metadata); err != nil {
panic(err)
}
return metadata.ModelMessages.InstructionsTemplate
case canonical == "gpt-6" || canonical == "gpt-6-astra" || strings.HasPrefix(canonical, "gpt-6-astra-"):
if v := strings.TrimSpace(instructionsGPT6Astra); v != "" {
return instructionsGPT6Astra
}
case strings.Contains(canonical, "codex"):
return DefaultInstructions
case strings.HasPrefix(canonical, "gpt-5.5"):
return latestCodexInstructions()
case strings.HasPrefix(canonical, "gpt-5.2"):
if v := strings.TrimSpace(instructionsGPT52); v != "" {
return instructionsGPT52
}
case strings.HasPrefix(canonical, "gpt-5.1"):
if v := strings.TrimSpace(instructionsGPT51); v != "" {
return instructionsGPT51
}
}
return latestCodexInstructions()
}
// IsGPT6SolOrLunaModelSpelling recognizes official IDs and existing local effort/compact suffixes.
func IsGPT6SolOrLunaModelSpelling(model string) bool {
canonical := CanonicalizeOpenAIModelAliasSpelling(model)
for _, base := range []string{"gpt-6-sol", "gpt-6-luna"} {
if canonical == base {
return true
}
suffix, ok := strings.CutPrefix(canonical, base+"-")
if ok {
switch suffix {
case "none", "low", "medium", "high", "xhigh", "max", "openai-compact":
return true
}
}
}
return false
}
// IsGPT61SolModelSpelling recognizes the published model and local effort/compact
// spellings. Invalid effort suffixes remain identifiable for request validation.
func IsGPT61SolModelSpelling(model string) bool {
canonical := CanonicalizeOpenAIModelAliasSpelling(model)
if canonical == "gpt-6.1-sol" {
return true
}
suffix, ok := strings.CutPrefix(canonical, "gpt-6.1-sol-")
if !ok {
return false
}
switch suffix {
case "none", "minimal", "low", "medium", "high", "xhigh", "max", "openai-compact":
return true
default:
return false
}
}
// ValidateGPT61SolReasoningEffort rejects disabled reasoning instead of silently
// increasing the client's requested effort on compatibility paths.
func ValidateGPT61SolReasoningEffort(model, effort string) error {
if IsGPT61SolModelSpelling(model) {
switch strings.ToLower(strings.TrimSpace(effort)) {
case "none", "minimal":
return fmt.Errorf("gpt-6.1-sol does not support reasoning effort %q; use low, medium, high, xhigh or max", effort)
}
}
return nil
}