package llm import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "time" ) // modelPageSize is what one GET /v1/models asks for. The list is short enough // that this is almost always the only request. const modelPageSize = 100 // maxModelPages stops a paginating endpoint that never says it is done from // looping forever behind a picker that is waiting on it. const maxModelPages = 10 // Model is one entry from GET /v1/models. MaxInputTokens is the context // window; it was added to the API in 2026 and is zero on an endpoint that // does not report it, which the caller has to treat as "not said" rather than // as a model with no context. type Model struct { ID string `json:"id"` DisplayName string `json:"display_name"` CreatedAt time.Time `json:"created_at"` MaxInputTokens int `json:"max_input_tokens"` MaxTokens int `json:"max_tokens"` } type modelPage struct { Data []Model `json:"data"` HasMore bool `json:"has_more"` LastID string `json:"last_id"` } // ListModels returns the models the endpoint offers, in the order it gave // them — which from Anthropic is newest first. // // Not every endpoint serves this route: a gateway that only proxies // /v1/messages answers 404, and the error says so rather than being turned // into an empty list, because "no models" and "this endpoint cannot be asked" // are different things to tell someone. func (c *Client) ListModels(ctx context.Context) ([]Model, error) { if !c.HasCredentials() { return nil, errNoCredentials } // Resolved once for the whole pagination loop rather than per page: a // borrowed credential may need refreshing, and that is a network call. a, err := c.resolveAuth(ctx) if err != nil { return nil, err } var out []Model after := "" for page := 0; page < maxModelPages; page++ { q := url.Values{"limit": {fmt.Sprint(modelPageSize)}} if after != "" { q.Set("after_id", after) } req, err := c.newRequest(ctx, http.MethodGet, "/v1/models?"+q.Encode(), nil, a) if err != nil { return nil, err } resp, err := c.HTTP.Do(req) if err != nil { return nil, fmt.Errorf("llm: %w", err) } raw, err := io.ReadAll(resp.Body) resp.Body.Close() if err != nil { return nil, fmt.Errorf("llm: reading models: %w", err) } if resp.StatusCode != http.StatusOK { return nil, parseAPIError(resp.StatusCode, resp.Header.Get("retry-after"), raw) } var p modelPage if err := json.Unmarshal(raw, &p); err != nil { return nil, fmt.Errorf("llm: decoding models: %w", err) } out = append(out, p.Data...) if !p.HasMore || p.LastID == "" { break } after = p.LastID } return out, nil }