mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 07:33:06 +08:00
230 lines
9.3 KiB
Go
230 lines
9.3 KiB
Go
// 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
|
||
}
|