{"record":{"id":"ae3890a00f7ee35c","repo":"CherryHQ/cherry-studio","slug":"openai-compatible-reranking-model-requires-baseurl","errorCode":null,"errorMessage":"OpenAI-compatible reranking model requires baseURL","messagePattern":"OpenAI-compatible reranking model requires baseURL","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ai-sdk-provider/src/openai-compatible-reranking-model.ts","lineNumber":114,"sourceCode":"    if (typeof result.index !== 'number' || typeof result.relevance_score !== 'number') {\n      throw new Error('Rerank response results must contain numeric index and relevance_score')\n    }\n\n    if (!Number.isInteger(result.index) || result.index < 0 || result.index >= documentCount) {\n      throw new Error('Rerank response results must reference a valid document index')\n    }\n\n    return { index: result.index, relevanceScore: result.relevance_score }\n  })\n}\n\nexport function createOpenAICompatibleRerankingModel(\n  modelId: string,\n  settings: OpenAICompatibleRerankingModelSettings\n): RerankingModelV3 {\n  const baseURL = withoutTrailingSlash(settings.baseURL)\n  if (!baseURL) {\n    throw new Error('OpenAI-compatible reranking model requires baseURL')\n  }\n\n  return new OpenAICompatibleRerankingModel(modelId, {\n    provider: `${settings.name}.rerank`,\n    url: ({ path }) => {\n      const url = new URL(`${baseURL}${path}`)\n      if (settings.queryParams) {\n        url.search = new URLSearchParams(settings.queryParams).toString()\n      }\n      return url.toString()\n    },\n    headers: () => ({\n      ...(settings.apiKey ? { Authorization: `Bearer ${settings.apiKey}` } : {}),\n      ...settings.headers\n    }),\n    fetch: settings.fetch\n  })\n}","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/packages/ai-sdk-provider/src/openai-compatible-reranking-model.ts#L96-L132","documentation":"Final fallback in the Vertex listing throwOnError path, reached only when a publisher group is null (failed) but `firstPublisherError` is still `undefined`. Normally every null group records its error in the catch, so this branch is a defensive guard against a logic gap where a group became null without setting the error variable. It exists so throwOnError can never silently pass a partial failure; it never carries the real cause.","triggerScenarios":"A code path inside the per-publisher map returns null without going through the catch (e.g. an early `return null` added outside the try, or a Promise.all rejection path that bypasses the per-publisher try/catch). In practice this is an internal invariant violation, not an operator-triggerable condition.","commonSituations":"A future refactor of vertexFetcher that adds a null-returning branch outside the try/catch; an await on a helper that rejects in a way Promise.all surfaces as a rejected group rather than a caught null. Should not occur in normal operation with valid/invalid auth.","solutions":["Treat this error as a bug report: it means a publisher group failed without recording its cause — inspect the vertexFetcher implementation for a null-returning path outside the try/catch.","Temporarily run listing without throwOnError and watch the warn logs; if a publisher still shows no models and no error log, that is the path missing the firstPublisherError assignment.","Ensure any new null-returning branch in the per-publisher loop assigns firstPublisherError before returning null.","File/fix an issue in the listModels vertexFetcher so every null result carries a recorded error."],"exampleFix":"// before: a branch returns null without recording the error\nconst group = await (async () => {\n  if (!shouldFetch(publisher)) return null   // firstPublisherError never set -> fallback message\n  return await fetchPublisher(publisher)\n})()\n// after: record the reason before returning null\nif (!shouldFetch(publisher)) {\n  if (firstPublisherError === undefined) firstPublisherError = new Error(`publisher ${publisher} skipped`)\n  return null\n}","handlingStrategy":"validation","validationCode":"// Defensive: before throwing throwOnError, ensure a cause was recorded.\n// (This guard belongs in vertexFetcher internals, not at call sites.)\nif (options?.throwOnError && publisherModelGroups.some(g => g === null) && firstPublisherError === undefined) {\n  firstPublisherError = new Error('publisher group failed without a recorded cause (internal bug)')\n}","typeGuard":null,"tryCatchPattern":"try {\n  await listModels(provider, signal, { throwOnError: true })\n} catch (e) {\n  if (e instanceof Error && /One or more Vertex AI publisher requests failed/.test(e.message)) {\n    // invariant gap — report as a bug, then degrade to non-strict listing\n    reportBug('vertexFetcher returned a null group without recording firstPublisherError')\n    await listModels(provider, signal)\n  } else throw e\n}","preventionTips":["In vertexFetcher, ensure every null-returning branch assigns firstPublisherError first.","Add a test that force-fails one publisher and asserts the thrown error carries the original cause, not the fallback message.","Code-review null returns in the per-publisher map to confirm they all pass through the try/catch."],"tags":["vertex-ai","model-listing","invariant","defensive"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}