Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

216 lines
6.7 KiB
Go

package modelcatalog
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
// ParseCodexModelListLine parses one JSON-RPC stdout line. handled is false
// for notifications, logs, and responses to other request ids.
func ParseCodexModelListLine(line []byte, requestID string) ([]ModelOption, bool, error) {
var payload map[string]json.RawMessage
if err := json.Unmarshal(line, &payload); err != nil {
return nil, false, nil
}
if !codexRPCIDMatches(payload["id"], requestID) {
return nil, false, nil
}
if rawError, ok := payload["error"]; ok && string(rawError) != "null" {
return nil, true, errors.New(extractCodexRPCError(rawError))
}
var result struct {
Data []json.RawMessage `json:"data"`
}
if err := json.Unmarshal(payload["result"], &result); err != nil {
return nil, true, fmt.Errorf("parse codex model/list result: %w", err)
}
models := make([]ModelOption, 0, len(result.Data))
for _, rawModel := range result.Data {
if model, ok := NormalizeCodexModel(rawModel); ok {
models = append(models, model)
}
}
return models, true, nil
}
func NormalizeCodexModel(raw json.RawMessage) (ModelOption, bool) {
var object map[string]any
if err := json.Unmarshal(raw, &object); err != nil {
return ModelOption{}, false
}
id := firstNonBlank(stringMapValue(object, "model"), stringMapValue(object, "id"))
if id == "" {
return ModelOption{}, false
}
displayName := firstNonBlank(stringMapValue(object, "displayName"), stringMapValue(object, "display_name"), id)
reasoningValue, reasoningAdvertised := advertisedValue(object, "supportedReasoningEfforts", "supported_reasoning_efforts")
speedValue, speedAdvertised := advertisedValue(object, "serviceTiers", "service_tiers")
legacySpeedValue, legacySpeedAdvertised := advertisedValue(object, "additionalSpeedTiers", "additional_speed_tiers")
speeds := normalizeCodexSpeeds(speedValue, legacySpeedValue)
defaultSpeed := canonicalCodexSpeed(firstNonBlank(stringMapValue(object, "defaultServiceTier"), stringMapValue(object, "default_service_tier")))
defaultSpeed = validSpeedDefault(speeds, defaultSpeed)
return ModelOption{
ID: id,
DisplayName: displayName,
Description: stringMapValue(object, "description"),
DefaultReasoningEffort: firstNonBlank(stringMapValue(object, "defaultReasoningEffort"), stringMapValue(object, "default_reasoning_effort")),
DefaultSpeed: defaultSpeed,
IsDefault: boolMapValue(object, "isDefault") || boolMapValue(object, "is_default"),
ReasoningEffortsAdvertised: reasoningAdvertised,
SupportedReasoningEfforts: normalizeCodexReasoningEfforts(reasoningValue),
SpeedsAdvertised: speedAdvertised || legacySpeedAdvertised,
SupportedSpeeds: speeds,
SupportsImageInput: codexImageInputSupport(object),
}, true
}
func normalizeCodexReasoningEfforts(value any) []ReasoningEffortOption {
rawOptions, ok := value.([]any)
if !ok {
return nil
}
options := make([]ReasoningEffortOption, 0, len(rawOptions))
seen := make(map[string]struct{}, len(rawOptions))
for _, rawOption := range rawOptions {
var option ReasoningEffortOption
switch typed := rawOption.(type) {
case string:
option.Value = strings.TrimSpace(typed)
case map[string]any:
option.Value = firstNonBlank(stringMapValue(typed, "reasoningEffort"), stringMapValue(typed, "effort"), stringMapValue(typed, "value"))
option.Label = firstNonBlank(stringMapValue(typed, "label"), stringMapValue(typed, "name"))
option.Description = stringMapValue(typed, "description")
option.Default = boolMapValue(typed, "default") || boolMapValue(typed, "isDefault")
}
if option.Value == "" {
continue
}
if _, exists := seen[option.Value]; exists {
continue
}
seen[option.Value] = struct{}{}
options = append(options, option)
}
return options
}
func normalizeCodexSpeeds(values ...any) []SpeedOption {
options := []SpeedOption{{Value: "standard", Label: "Standard"}}
seen := map[string]struct{}{"standard": {}}
for _, rawValue := range values {
rawOptions, ok := rawValue.([]any)
if !ok {
continue
}
for _, rawOption := range rawOptions {
var option SpeedOption
switch typed := rawOption.(type) {
case string:
option.Value = canonicalCodexSpeed(typed)
case map[string]any:
option.Value = canonicalCodexSpeed(stringMapValue(typed, "id"))
option.Label = stringMapValue(typed, "name")
option.Description = stringMapValue(typed, "description")
}
if option.Value == "" {
continue
}
if _, exists := seen[option.Value]; exists {
continue
}
seen[option.Value] = struct{}{}
options = append(options, option)
}
}
return options
}
func canonicalCodexSpeed(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "", "default", "standard":
return "standard"
case "priority", "fast":
return "fast"
default:
return strings.TrimSpace(value)
}
}
func codexImageInputSupport(object map[string]any) *bool {
raw, advertised := object["inputModalities"]
if !advertised {
raw, advertised = object["input_modalities"]
}
if !advertised {
return nil
}
modalities, ok := raw.([]any)
if !ok {
return nil
}
supported := false
for _, modality := range modalities {
if value, ok := modality.(string); ok && strings.EqualFold(strings.TrimSpace(value), "image") {
supported = true
break
}
}
return &supported
}
func advertisedValue(object map[string]any, keys ...string) (any, bool) {
for _, key := range keys {
if value, ok := object[key]; ok {
return value, true
}
}
return nil, false
}
func codexRPCIDMatches(raw json.RawMessage, want string) bool {
var stringID string
if err := json.Unmarshal(raw, &stringID); err == nil {
return stringID == want
}
var numberID int
if err := json.Unmarshal(raw, &numberID); err == nil {
return fmt.Sprintf("%d", numberID) == want
}
return false
}
func extractCodexRPCError(raw json.RawMessage) string {
var message string
if err := json.Unmarshal(raw, &message); err == nil && strings.TrimSpace(message) != "" {
return strings.TrimSpace(message)
}
var object struct {
Message string `json:"message"`
}
if err := json.Unmarshal(raw, &object); err == nil && strings.TrimSpace(object.Message) != "" {
return strings.TrimSpace(object.Message)
}
return "unknown codex app-server RPC error"
}
func stringMapValue(object map[string]any, key string) string {
value, _ := object[key].(string)
return strings.TrimSpace(value)
}
func boolMapValue(object map[string]any, key string) bool {
value, ok := object[key].(bool)
return ok && value
}
func firstNonBlank(values ...string) string {
for _, value := range values {
if value = strings.TrimSpace(value); value != "" {
return value
}
}
return ""
}