package classification import ( "context" "encoding/json" "errors" "io" "net/http" "net/url" "slices" "strings" "time" ) // VerifiedModel is one OpenRouter model that currently satisfies every routing // control this app sends fail-closed: at least one live zero-data-retention // endpoint that supports strict structured outputs. Anything outside this list // is routed to zero providers and fails with HTTP 404. type VerifiedModel struct { ID string `json:"id"` Name string `json:"name"` } // endpointBase validates and returns the provider API root shared by every // provider request. Redirect and scheme rules exist because prompts contain // payee text; they must never travel to an endpoint with different policies. func (c *Client) endpointBase() (string, error) { base := strings.TrimRight(c.BaseURL, "/") if base == "" { base = "https://openrouter.ai/api/v1" } endpoint, err := url.Parse(base) if err != nil || endpoint.Host == "" || endpoint.User != nil || endpoint.RawQuery != "" || endpoint.Fragment != "" { return "", errors.New("invalid AI endpoint") } if endpoint.Scheme != "https" && !(endpoint.Scheme == "http" && (endpoint.Hostname() == "localhost" || endpoint.Hostname() == "127.0.0.1" || endpoint.Hostname() == "::1")) { return "", errors.New("AI endpoint must use HTTPS") } return base, nil } func (c *Client) httpClient() http.Client { client := http.Client{Timeout: 45 * time.Second} if c.HTTPClient != nil { client = *c.HTTPClient if client.Timeout == 0 { client.Timeout = 45 * time.Second } } // Redirects could send sensitive prompts to endpoints with different policies. client.CheckRedirect = func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse } return client } // VerifiedModels queries the provider's public zero-data-retention catalog and // keeps only models with at least one live endpoint supporting strict // structured outputs — the exact conditions completions are routed under. The // catalog is public: no credential is attached to the request. func (c *Client) VerifiedModels(ctx context.Context) ([]VerifiedModel, error) { base, err := c.endpointBase() if err != nil { return nil, err } client := c.httpClient() req, err := http.NewRequestWithContext(ctx, http.MethodGet, base+"/endpoints/zdr", nil) if err != nil { return nil, errors.New("cannot create model catalog request") } resp, err := client.Do(req) if err != nil { if cause := requestContextError(ctx, err); cause != nil { return nil, cause } return nil, errors.New("model catalog request failed") } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, errors.New("model catalog is unavailable") } var catalog struct { Data []struct { ModelID string `json:"model_id"` ModelName string `json:"model_name"` Status int `json:"status"` SupportedParameters []string `json:"supported_parameters"` } `json:"data"` } const maxCatalog = 16 << 20 raw, err := io.ReadAll(io.LimitReader(resp.Body, maxCatalog+1)) if err != nil || len(raw) > maxCatalog { return nil, errors.New("invalid model catalog response") } if json.Unmarshal(raw, &catalog) != nil { return nil, errors.New("invalid model catalog response") } names := map[string]string{} for _, endpoint := range catalog.Data { if endpoint.ModelID == "" || endpoint.Status < 0 { continue } if !slices.Contains(endpoint.SupportedParameters, "structured_outputs") || !slices.Contains(endpoint.SupportedParameters, "response_format") { continue } if _, ok := names[endpoint.ModelID]; !ok { names[endpoint.ModelID] = endpoint.ModelName } } models := make([]VerifiedModel, 0, len(names)) for id, name := range names { models = append(models, VerifiedModel{ID: id, Name: name}) } slices.SortFunc(models, func(a, b VerifiedModel) int { return strings.Compare(a.ID, b.ID) }) return models, nil }