{"record":{"id":"b9353c0f772e8dac","repo":"CherryHQ/cherry-studio","slug":"ppio-provider-requires-a-non-empty-baseurl-an-e","errorCode":null,"errorMessage":"PPIO provider requires a non-empty `baseURL`. An empty value would resolve fetch paths against the renderer process origin (app://, file://) and surface as opaque \"Failed to fetch\" errors.","messagePattern":"PPIO provider requires a non-empty `baseURL`\\. An empty value would resolve fetch paths against the renderer process origin \\(app://, file://\\) and surface as opaque \"Failed to fetch\" errors\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/provider/custom/ppio/ppioProvider.ts","lineNumber":53,"sourceCode":" */\nexport function buildPpioTransport(settings: PpioProviderSettings): ImageGenerationTransport {\n  return createPpioTransport({\n    apiKey: settings.apiKey ?? '',\n    baseURL: settings.imageBaseURL || DEFAULT_PPIO_BASE_URL\n  })\n}\n\n/**\n * Unified PPIO provider — chat, embedding, and image off one `ProviderV3`,\n * mirroring `newapi-provider.ts`. Chat/embedding go through the OpenAI-\n * compatible SDK aimed at `settings.baseURL`; the image model keeps its\n * bespoke submit/poll behavior via `createImageGenerationModel + createPpioTransport`\n * aimed at `settings.imageBaseURL` (defaults to `DEFAULT_PPIO_BASE_URL`).\n */\nexport function createPpioProvider(settings: PpioProviderSettings = {}): PpioProvider {\n  const { baseURL, fetch: customFetch } = settings\n  if (!baseURL) {\n    throw new Error(\n      'PPIO provider requires a non-empty `baseURL`. An empty value would resolve fetch paths against the renderer process origin (app://, file://) and surface as opaque \"Failed to fetch\" errors.'\n    )\n  }\n\n  const resolveApiKey = () =>\n    loadApiKey({ apiKey: settings.apiKey, environmentVariableName: 'PPIO_API_KEY', description: 'PPIO' })\n\n  const authHeaders = () => ({\n    Authorization: `Bearer ${resolveApiKey()}`,\n    ...settings.headers\n  })\n\n  const url = ({ path }: { path: string; modelId: string }) => `${withoutTrailingSlash(baseURL)}${path}`\n\n  const createChatModel = (modelId: string) =>\n    new OpenAICompatibleChatLanguageModel(modelId, {\n      provider: `${PPIO_PROVIDER_NAME}.chat`,\n      url,","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/provider/custom/ppio/ppioProvider.ts#L35-L71","documentation":"Identical fail-fast guard to the OVMS one, applied in createPpioProvider(): an empty settings.baseURL would make the OpenAI-compatible SDK resolve fetch paths against the renderer origin, surfacing as opaque 'Failed to fetch'. The throw at construction replaces that with an explicit, attributable error.","triggerScenarios":"createPpioProvider({}) or createPpioProvider({ baseURL: '' }) — PPIO provider instantiated without a chat/embedding host. Note: imageBaseURL has its own default (DEFAULT_PPIO_BASE_URL), so the image path is unaffected; this guard is specifically about the chat/embedding baseURL.","commonSituations":"User adds a PPIO provider but leaves the API host blank, settings migration leaves baseURL undefined, or programmatic construction without the host.","solutions":["Set a non-empty PPIO host (e.g. https://api.ppinfra.com/v3/openai) before constructing the provider.","Read the host from Preference/BootConfig before calling createPpioProvider.","Mark the baseURL field required in the provider UI.","Defer provider construction until settings are populated; do not instantiate with a blank host."],"exampleFix":"// before\nconst provider = createPpioProvider({ baseURL: settings.ppio?.baseURL })\n// after\nif (!settings.ppio?.baseURL) throw new Error('PPIO API host is required')\nconst provider = createPpioProvider({ baseURL: settings.ppio.baseURL })","handlingStrategy":"validation","validationCode":"if (!settings.baseURL) {\n  throw new Error('PPIO API host is required (e.g. https://api.ppinfra.com/v3/openai)')\n}\nconst provider = createPpioProvider(settings)","typeGuard":"export function isPpioBaseURLError(e: unknown): boolean {\n  return e instanceof Error && /PPIO provider requires a non-empty `baseURL`/.test(e.message)\n}","tryCatchPattern":"try {\n  createPpioProvider(settings)\n} catch (e) {\n  if (isPpioBaseURLError(e)) {\n    // prompt the user for the PPIO host before retrying\n  }\n  throw e\n}","preventionTips":["Make the PPIO host field required in the provider UI.","Read host from settings before constructing the provider.","Remember imageBaseURL has its own default; this guard only covers chat/embedding."],"tags":["validation","configuration","ppio","base-url","fail-fast"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}