ListModels fetches available models from the GitHub Models API. The API returns a plain JSON array (not OpenAI format) with fields: name, friendly_name, task, publisher, model_family, etc. We filter to chat-completion models only.
(ctx context.Context)
| 198 | // |
| 199 | // We filter to chat-completion models only. |
| 200 | func (c *GitHubModelsClient) ListModels(ctx context.Context) ([]client.ModelInfo, error) { |
| 201 | modelsURL := c.getModelsURL() |
| 202 | |
| 203 | resp, err := auth.DoWithRefresh(ctx, c.provider, func(token string) (*http.Response, error) { |
| 204 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, modelsURL, nil) |
| 205 | if err != nil { |
| 206 | return nil, fmt.Errorf("%s: %w", i18n.T("llm.error.create_request_for", "GitHub Models"), err) |
| 207 | } |
| 208 | req.Header.Set("Authorization", "Bearer "+token) |
| 209 | return c.client.Do(req) |
| 210 | }) |
| 211 | if err != nil { |
| 212 | return nil, fmt.Errorf("%s: %w", i18n.T("llm.error.request_failed", "GitHub Models"), err) |
| 213 | } |
| 214 | defer func() { _ = resp.Body.Close() }() |
| 215 | |
| 216 | bodyBytes, err := io.ReadAll(resp.Body) |
| 217 | if err != nil { |
| 218 | return nil, fmt.Errorf("%s: %w", i18n.T("llm.error.read_response_for", "GitHub Models"), err) |
| 219 | } |
| 220 | |
| 221 | if resp.StatusCode != http.StatusOK { |
| 222 | return nil, fmt.Errorf("%s", i18n.T("llm.error.api_error_code", "GitHub Models", resp.StatusCode, utils.SanitizeSensitiveText(string(bodyBytes)))) |
| 223 | } |
| 224 | |
| 225 | // GitHub Models returns a plain JSON array (NOT OpenAI {"data":[...]} format) |
| 226 | type ghModel struct { |
| 227 | ID string `json:"id"` // Azure registry path (long) |
| 228 | Name string `json:"name"` // Model name used for inference |
| 229 | FriendlyName string `json:"friendly_name"` |
| 230 | Publisher string `json:"publisher"` |
| 231 | ModelFamily string `json:"model_family"` |
| 232 | Task string `json:"task"` // "chat-completion", "embeddings", etc. |
| 233 | } |
| 234 | |
| 235 | // Try plain array first (actual GitHub Models format) |
| 236 | var models []ghModel |
| 237 | if err := json.Unmarshal(bodyBytes, &models); err != nil { |
| 238 | // Fallback: try OpenAI-compatible {"data": [...]} format |
| 239 | var wrapped struct { |
| 240 | Data []ghModel `json:"data"` |
| 241 | } |
| 242 | if err2 := json.Unmarshal(bodyBytes, &wrapped); err2 != nil { |
| 243 | return nil, fmt.Errorf("%s: %w", i18n.T("llm.error.decode_response_for", "GitHub Models"), err) |
| 244 | } |
| 245 | models = wrapped.Data |
| 246 | } |
| 247 | |
| 248 | modelList := make([]client.ModelInfo, 0, len(models)) |
| 249 | for _, m := range models { |
| 250 | // Filter to chat-capable models only |
| 251 | if m.Task != "" && m.Task != "chat-completion" { |
| 252 | continue |
| 253 | } |
| 254 | |
| 255 | // Use Name for inference (not the long Azure registry ID) |
| 256 | modelID := m.Name |
| 257 | if modelID == "" { |