package probe import ( "bytes" "context" "encoding/json" "fmt" "net/http" "strings" "time" ) type TransportProfile struct { SupportsOpenAIModels bool `json:"supports_openai_models"` SupportsOpenAIChatCompletions bool `json:"supports_openai_chat_completions"` SupportsOpenAIResponses bool `json:"supports_openai_responses"` SupportsAnthropicMessages bool `json:"supports_anthropic_messages"` AuthStyle string `json:"auth_style"` ModelIDStyle string `json:"model_id_style"` KnownAdvisories []string `json:"known_advisories"` } type ModelCapabilityProfile struct { RawModelID string `json:"raw_model_id"` NormalizedModelID string `json:"normalized_model_id"` CanonicalModelFamily string `json:"canonical_model_family"` SupportsStream string `json:"supports_stream"` SupportsTools string `json:"supports_tools"` SupportsReasoningFields string `json:"supports_reasoning_fields"` SmokeChatOK bool `json:"smoke_chat_ok"` } type CapabilityProfile struct { TransportProfile TransportProfile `json:"transport_profile"` ModelProfiles []ModelCapabilityProfile `json:"model_profiles"` } func ProbeCapabilities(ctx context.Context, baseURL, apiKey string, rawModels []string) (*CapabilityProfile, error) { profile := &CapabilityProfile{ TransportProfile: TransportProfile{ SupportsOpenAIModels: len(rawModels) > 0, AuthStyle: "bearer", ModelIDStyle: detectModelIDStyle(rawModels), KnownAdvisories: []string{}, }, ModelProfiles: make([]ModelCapabilityProfile, 0, len(rawModels)), } responsesStatus, err := probeJSONEndpoint(ctx, baseURL, apiKey, "/v1/responses", map[string]any{ "model": firstNonEmptyModel(rawModels), "input": "ping", }) if err != nil { return nil, err } profile.TransportProfile.SupportsOpenAIResponses = responsesStatus >= http.StatusOK && responsesStatus < http.StatusMultipleChoices for _, rawModel := range rawModels { modelProfile := ModelCapabilityProfile{ RawModelID: strings.TrimSpace(rawModel), NormalizedModelID: NormalizeModelID(rawModel), CanonicalModelFamily: CanonicalModelFamily(rawModel), SupportsStream: "unknown", SupportsTools: "unknown", SupportsReasoningFields: "unknown", } chatStatus, err := probeJSONEndpoint(ctx, baseURL, apiKey, "/v1/chat/completions", map[string]any{ "model": modelProfile.RawModelID, "messages": []map[string]string{ {"role": "user", "content": "ping"}, }, "max_tokens": 8, "temperature": 0, }) if err != nil { return nil, err } modelProfile.SmokeChatOK = chatStatus >= http.StatusOK && chatStatus < http.StatusMultipleChoices if modelProfile.SmokeChatOK { profile.TransportProfile.SupportsOpenAIChatCompletions = true } if chatStatus == http.StatusForbidden { appendAdvisory(&profile.TransportProfile.KnownAdvisories, "initial_probe_race_expected") } profile.ModelProfiles = append(profile.ModelProfiles, modelProfile) } if !profile.TransportProfile.SupportsOpenAIResponses && profile.TransportProfile.SupportsOpenAIChatCompletions { appendAdvisory(&profile.TransportProfile.KnownAdvisories, "responses_unsupported_but_chat_ok") } return profile, nil } func probeJSONEndpoint(ctx context.Context, baseURL, apiKey, path string, payload any) (int, error) { requestURL, err := joinGatewayPath(baseURL, path) if err != nil { return 0, fmt.Errorf("resolve %s endpoint: %w", path, err) } var body bytes.Buffer if payload != nil { if err := json.NewEncoder(&body).Encode(payload); err != nil { return 0, fmt.Errorf("encode %s probe payload: %w", path, err) } } req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, &body) if err != nil { return 0, fmt.Errorf("build %s request: %w", path, err) } req.Header.Set("Content-Type", "application/json") if token := strings.TrimSpace(apiKey); token != "" { req.Header.Set("Authorization", "Bearer "+token) } resp, err := (&http.Client{Timeout: 15 * time.Second}).Do(req) if err != nil { return 0, fmt.Errorf("request %s: %w", path, err) } defer resp.Body.Close() return resp.StatusCode, nil } func detectModelIDStyle(rawModels []string) string { for _, rawModel := range rawModels { if strings.Contains(strings.TrimSpace(rawModel), "/") { return "vendor_prefixed" } } return "canonical" } func appendAdvisory(values *[]string, advisory string) { if advisory == "" { return } for _, existing := range *values { if existing == advisory { return } } *values = append(*values, advisory) } func firstNonEmptyModel(rawModels []string) string { for _, rawModel := range rawModels { if trimmed := strings.TrimSpace(rawModel); trimmed != "" { return trimmed } } return "ping" }