CherryHQ/cherry-studio · error · Error

PPIO provider requires a non-empty `baseURL`. An empty value

Error message

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.

What it means

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.

Source

Thrown at src/main/ai/provider/custom/ppio/ppioProvider.ts:53

 */
export function buildPpioTransport(settings: PpioProviderSettings): ImageGenerationTransport {
  return createPpioTransport({
    apiKey: settings.apiKey ?? '',
    baseURL: settings.imageBaseURL || DEFAULT_PPIO_BASE_URL
  })
}

/**
 * Unified PPIO provider — chat, embedding, and image off one `ProviderV3`,
 * mirroring `newapi-provider.ts`. Chat/embedding go through the OpenAI-
 * compatible SDK aimed at `settings.baseURL`; the image model keeps its
 * bespoke submit/poll behavior via `createImageGenerationModel + createPpioTransport`
 * aimed at `settings.imageBaseURL` (defaults to `DEFAULT_PPIO_BASE_URL`).
 */
export function createPpioProvider(settings: PpioProviderSettings = {}): PpioProvider {
  const { baseURL, fetch: customFetch } = settings
  if (!baseURL) {
    throw new Error(
      '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.'
    )
  }

  const resolveApiKey = () =>
    loadApiKey({ apiKey: settings.apiKey, environmentVariableName: 'PPIO_API_KEY', description: 'PPIO' })

  const authHeaders = () => ({
    Authorization: `Bearer ${resolveApiKey()}`,
    ...settings.headers
  })

  const url = ({ path }: { path: string; modelId: string }) => `${withoutTrailingSlash(baseURL)}${path}`

  const createChatModel = (modelId: string) =>
    new OpenAICompatibleChatLanguageModel(modelId, {
      provider: `${PPIO_PROVIDER_NAME}.chat`,
      url,

View on GitHub (pinned to 726446b54c)

Solutions

  1. Set a non-empty PPIO host (e.g. https://api.ppinfra.com/v3/openai) before constructing the provider.
  2. Read the host from Preference/BootConfig before calling createPpioProvider.
  3. Mark the baseURL field required in the provider UI.
  4. Defer provider construction until settings are populated; do not instantiate with a blank host.

Example fix

// before
const provider = createPpioProvider({ baseURL: settings.ppio?.baseURL })
// after
if (!settings.ppio?.baseURL) throw new Error('PPIO API host is required')
const provider = createPpioProvider({ baseURL: settings.ppio.baseURL })
Defensive patterns

Strategy: validation

Validate before calling

if (!settings.baseURL) {
  throw new Error('PPIO API host is required (e.g. https://api.ppinfra.com/v3/openai)')
}
const provider = createPpioProvider(settings)

Type guard

export function isPpioBaseURLError(e: unknown): boolean {
  return e instanceof Error && /PPIO provider requires a non-empty `baseURL`/.test(e.message)
}

Try / catch

try {
  createPpioProvider(settings)
} catch (e) {
  if (isPpioBaseURLError(e)) {
    // prompt the user for the PPIO host before retrying
  }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: User adds a PPIO provider but leaves the API host blank, settings migration leaves baseURL undefined, or programmatic construction without the host.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/b9353c0f772e8dac. Report an issue: GitHub.