CherryHQ/cherry-studio · error · Error

OpenAI-compatible reranking model only supports text documen

Error message

OpenAI-compatible reranking model only supports text documents

What it means

Thrown by the custom-provider async image-generation job when a transport's submit() returns a response carrying neither `imageUrls` nor a `taskId` (both fields are optional on the submit return type). The handler treats this as a malformed vendor response and fails the job hard, rather than silently completing with zero generated files — which would be a paid no-op the user never sees. It is distinct from error 401 (a successful but empty URL list) and from 402 (a poll-less transport returning a task id).

Source

Thrown at packages/ai-sdk-provider/src/openai-compatible-reranking-model.ts:52

type OpenAICompatibleRerankResponse = {
  results?: OpenAICompatibleRerankResponseItem[]
}

export class OpenAICompatibleRerankingModel implements RerankingModelV3 {
  readonly specificationVersion = 'v3'

  constructor(
    readonly modelId: string,
    private readonly config: OpenAICompatibleRerankingModelConfig
  ) {}

  get provider(): string {
    return this.config.provider
  }

  async doRerank({ documents, headers, query, topN, abortSignal }: DoRerankOptions): Promise<DoRerankResult> {
    if (documents.type !== 'text') {
      throw new Error('OpenAI-compatible reranking model only supports text documents')
    }

    const { value, rawValue } = await postJsonToApi({
      url: this.config.url({ path: '/rerank', modelId: this.modelId }),
      headers: combineHeaders(this.config.headers(), headers),
      body: {
        model: this.modelId,
        query,
        documents: documents.values,
        top_n: topN
      },
      failedResponseHandler: createStatusCodeErrorResponseHandler(),
      successfulResponseHandler: async ({ response }) => {
        const rawValue = await response.json()
        return {
          value: parseRerankResponse(rawValue, documents.values.length),
          rawValue
        }

View on GitHub (pinned to 726446b54c)

Solutions

  1. Capture the raw submit response body (add a temporary log in the transport's submit()) and compare its keys against what the adapter expects to populate imageUrls/taskId from.
  2. Verify the provider's apiKey and quota in the vendor console — an auth/quota rejection masquerading as a 200 body is the most common cause.
  3. Confirm sdkConfig.modelId matches a model the vendor actually serves for image generation on that endpoint.
  4. If the vendor changed its response schema, update the transport adapter in the matching provider package (ppio/dashscope/modelscope/dmxapi) to map the new fields onto imageUrls or taskId.
  5. Check the vendor's API changelog for the model family in use.

Example fix

// before: transport parses only the happy-path fields
submit(input) {
  const body = await fetchJson(url, req)
  return { imageUrls: body.data?.map(d => d.url) }
}
// after: surface non-2xx / error envelopes so submit() never returns a bare {}
submit(input) {
  const body = await fetchJson(url, req)
  if (body.code || body.error) {
    throw new Error(`vendor rejected submit: ${body.message ?? JSON.stringify(body)}`)
  }
  return { imageUrls: body.data?.map(d => d.url), taskId: body.task_id }
}
Defensive patterns

Strategy: validation

Validate before calling

// Before invoking the job, assert the transport is one the registry knows
// and that submit() cannot return a bare object. Validate at adapter
// registration time so a malformed submit fails loudly in tests, not in prod.
function assertTransportContract(t: ImageGenerationTransport) {
  if (typeof t.submit !== 'function') throw new Error('transport.submit missing')
  // optional poll, but if absent the adapter MUST guarantee sync imageUrls
}
// In the job handler, guard the submit result before branching:
const submit = await transport.submit(...)
if (!submit || (submit.imageUrls == null && submit.taskId == null)) {
  throw new Error(`submit response malformed: ${JSON.stringify(submit)}`)
}

Type guard

function hasSubmitResult(s: unknown): s is { taskId?: string; imageUrls?: string[] } {
  return typeof s === 'object' && s !== null && ('taskId' in s || 'imageUrls' in s)
}

Try / catch

try {
  await generateImageViaJob(payload)
} catch (e) {
  if (e instanceof Error && /returned neither imageUrls nor a taskId/.test(e.message)) {
    // vendor-shape failure: log the raw modelId and surface to the user; do NOT auto-retry
    notifyUser(`Image model '${modelId}' returned an unexpected response. Check the provider config.`)
  } else throw e
}

Prevention

When it happens

Trigger: The vendor's image-generation submit endpoint replies with an unexpected body shape: an error envelope sent with HTTP 200 (e.g. `{"error":{"code":...}}` or `{"message":"..."}`), a success body whose fields the transport adapter maps to different keys, or a quota/auth rejection that did not surface as a non-2xx status. Also fires when a newly-added transport returns `{}` because its response parser was never wired to populate either field.

Common situations: Vendor API version bump renames the response fields; the provider's apiKey is invalid and the gateway returns a JSON error with status 200; the model id in the SDK config does not exist on the vendor and the body is an error object; a transport adapter bug leaves both fields undefined after parsing.

Related errors


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