{"record":{"id":"917573cddeec10d7","repo":"linshenkx/prompt-optimizer","slug":"api-returned-empty-model-list-917573","errorCode":null,"errorMessage":"API returned empty model list","messagePattern":"API returned empty model list","errorType":"exception","errorClass":"APIError","httpStatus":null,"severity":"warning","filePath":"packages/core/src/services/llm/adapters/openai-adapter.ts","lineNumber":159,"sourceCode":"    // 验证baseURL以/v1结尾\n    const baseURL = config.connectionConfig.baseURL || this.getProvider().defaultBaseURL\n\n    const openai = this.createOpenAIInstance(config, false)\n\n    try {\n      const response = await openai.models.list()\n\n      // 检查返回格式\n      if (response && response.data && Array.isArray(response.data)) {\n        const models = response.data\n          .map((model) => {\n            // 使用buildDefaultModel为每个模型ID创建TextModel对象\n            return this.buildDefaultModel(model.id)\n          })\n          .sort((a, b) => a.id.localeCompare(b.id))\n\n        if (models.length === 0) {\n          throw new APIError('API returned empty model list')\n        }\n\n        return models\n      }\n\n      throw new APIError('Unexpected API response format')\n    } catch (error: any) {\n      console.error('[OpenAIAdapter] Failed to fetch models:', error)\n\n      // 连接错误处理（包括跨域检测）\n      if (error.message && (error.message.includes('Failed to fetch') ||\n          error.message.includes('Connection error'))) {\n        const isCrossOriginError = this.detectCrossOriginError(error, baseURL)\n\n        if (isCrossOriginError) {\n          throw new APIError(`Cross-origin connection failed: ${error.message}`)\n        } else {\n          throw new APIError(`Connection failed: ${error.message}`)","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/linshenkx/prompt-optimizer/blob/3e677b1d9f7e0493c142c175560531e7ae786dce/packages/core/src/services/llm/adapters/openai-adapter.ts#L141-L177","documentation":"OpenAIAdapter.getModelsAsync successfully fetched a models array (data: [...]) but after filtering/mapping it produced zero entries, so it throws rather than return an empty list — the endpoint is reachable and the shape is right, but there is nothing usable.","triggerScenarios":"Listing models from a server whose model list is empty, entries filtered out because model.id is missing/non-string, or a gateway exposing zero deployed models.","commonSituations":"Local gateways (vLLM/llama.cpp/Ollama bridges) with no models loaded yet, an OpenAI-compatible service where all entries lack the id field, freshly provisioned projects with no model access granted.","solutions":["Check the backing server actually serves models: curl $BASE_URL/v1/models","If local, load/pull a model (ollama pull, vllm serve ...) before listing","If a project-scoped key is used, grant the project access to at least one model","Treat this as a non-fatal empty state in UI rather than crashing"],"exampleFix":"// before\nconst models = await adapter.getModelsAsync()\nsetModels(models)\n\n// after\nlet models: TextModel[] = []\ntry { models = await adapter.getModelsAsync() }\ncatch (e) { if (!(e instanceof APIError && /empty model list/.test(e.message))) throw e }\nsetModels(models) // render empty state gracefully","handlingStrategy":"try-catch","validationCode":"const resp = await fetch(`${baseURL}/models`, { headers })\nconst body = await resp.json()\nif (!Array.isArray(body?.data) || body.data.length === 0) warn('No models available on server')","typeGuard":"null","tryCatchPattern":"try { models = await adapter.getModelsAsync() }\ncatch (e) {\n  if (e instanceof APIError && /empty model list/.test(e.message)) models = []\n  else throw e\n}","preventionTips":["Ensure at least one model is loaded/pulled on local gateways","Grant model access to project-scoped keys","Treat empty lists as a UI state, not an exception"],"tags":["openai","model-list","empty-result"],"backgroundTag":"empty-model-list","analyzedSha":"3e677b1d9f7e0493c142c175560531e7ae786dce","analyzedAt":"2026-08-27T21:29:16.709Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}