CherryHQ/cherry-studio · error · Error

Unknown model: ${input.modelId}

Error message

Unknown model: ${input.modelId}

What it means

Thrown at the top of PpioTransport.submit() when input.modelDescriptor is missing. The descriptor carries the per-model endpoint path and sync/async flag that buildRequestParams needs; without it the transport cannot construct a valid request. Indicates a registry/descriptor resolution gap rather than a network problem.

Source

Thrown at src/main/ai/provider/custom/ppio/ppioTransport.ts:183

      if (error instanceof Error && error.name === 'AbortError') {
        if (externallyAborted) {
          throw createAbortError('PPIO API request aborted')
        }

        throw new Error(`PPIO API request timeout after ${timeout / 1000}s`)
      }
      throw error
    } finally {
      clearTimeout(timeoutId)
      externalSignal?.removeEventListener('abort', onExternalAbort)
    }
  }

  async submit(input: ImageGenerationSubmitInput): Promise<{ taskId?: string; imageUrls?: string[] }> {
    const bagParams = input.providerParams as PpioProviderParams
    const descriptor = input.modelDescriptor
    if (!descriptor) {
      throw new Error(`Unknown model: ${input.modelId}`)
    }

    // Native AI SDK fields (size / seed) land on `input.*` post-canonicalGenerate
    // partition, not in the providerOptions bag. Merge them into a unified
    // view so the per-model builders below can read uniformly. `ppioSeed`
    // remains PPIO's bespoke wire field name; if the bag carries one
    // explicitly we keep it, otherwise fall back to `input.seed`.
    const params: PpioProviderParams = {
      ...bagParams,
      size: bagParams.size ?? input.size,
      ppioSeed: bagParams.ppioSeed ?? input.seed
    }

    const requestParams = this.buildRequestParams(input, params, descriptor)

    if (descriptor.isSync) {
      const result = await this.request<PpioSyncResult>(descriptor.endpoint, requestParams, 'POST', {
        signal: input.signal

View on GitHub (pinned to 726446b54c)

Solutions

  1. Confirm the modelId in input matches a registered PpioModelDescriptor (id, endpoint, isSync, mode).
  2. If the model is genuinely new, add its descriptor to the registry before exposing it for selection.
  3. Validate modelId against the descriptor map at the call site before invoking submit.
  4. Surface a user-facing 'this PPIO model is not yet supported' message instead of the generic throw.

Example fix

// before
if (!descriptor) throw new Error(`Unknown model: ${input.modelId}`)
// after — typed, attributable
if (!descriptor) throw new Error(`PPIO model '${input.modelId}' has no registered descriptor; add it to the descriptor map`)
Defensive patterns

Strategy: validation

Validate before calling

// Validate the modelId has a descriptor before submit
const descriptor = resolvePpioModelDescriptor(input.modelId)
if (!descriptor) {
  throw new Error(`PPIO model '${input.modelId}' has no registered descriptor`)
}

Type guard

export function isUnknownPpioModelError(e: unknown): boolean {
  return e instanceof Error && /^Unknown model:/.test(e.message)
}

Try / catch

try {
  await transport.submit(input)
} catch (e) {
  if (isUnknownPpioModelError(e)) {
    // surface 'model not yet supported' to the user
  }
  throw e
}

Prevention

When it happens

Trigger: submit() is invoked with an input whose modelId has no entry in the PPIO model descriptor map (the source that produces PpioModelDescriptor). Concrete causes: a new PPIO model was added to the catalog but its descriptor was not wired in, the modelId string drifted (typo, version suffix), or descriptor resolution silently returned undefined.

Common situations: User picks a newly-listed PPIO model before its descriptor is registered, model id case/spacing mismatch, descriptor registry out of sync with the model catalog.

Related errors


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