Skip to content

Bug Report: url_citation annotation validation fails when title field is missing in OpenRouter responses #10199

@nekorytaylor666

Description

@nekorytaylor666

Description

When using OpenRouter models (specifically moonshotai/kimi-k2-thinking and potentially others), the SDK throws a validation
error when processing streaming responses that contain url_citation annotations without a title field.

Error Message

Error message: [{"code":"invalid_union","errors":[[{"code":"invalid_value","values":["url_citation"],"path":["choices",0,"de
lta","annotations",0,"type"],"message":"Invalid input: expected "url_citation""},{"expected":"object","code":"invalid_type
","path":["choices",0,"delta","annotations",0,"url_citation"],"message":"Invalid input: expected object, received
undefined"}],[{"expected":"object","code":"invalid_type","path":["error"],"message":"Invalid input: expected object,
received undefined"}]],"path":[],"message":"Invalid input"}]

Response Object

{
id: "gen-1763021780-S4a2zSv2JDJY3vr43V9J",
provider: "Moonshot AI",
model: "moonshotai/kimi-k2-thinking",
object: "chat.completion.chunk",
created: 1763021783,
choices: [...],
system_fingerprint: "fpv0_f0186953",
}

Error type: AI_TypeValidationError

Root Cause

The schema validation in packages/openai/src/responses/openai-responses-api.ts requires both url and title fields for
url_citation annotations:

Line 560-564 (streaming chunks):
z.object({
type: z.literal('url_citation'),
url: z.string(),
title: z.string(), // ← Required but not always provided by OpenRouter
}),

Line 680-686 (non-streaming responses):
z.object({
type: z.literal('url_citation'),
start_index: z.number(),
end_index: z.number(),
url: z.string(),
title: z.string(), // ← Required but not always provided by OpenRouter
}),

However, OpenRouter (and potentially other providers) may send url_citation annotations without a title field, causing
validation to fail.

Expected Behavior

The SDK should gracefully handle url_citation annotations when the title field is missing, either by:

  1. Making the title field optional
  2. Using an empty string or the URL as a fallback when title is missing

Proposed Fix

Make the title field optional in both schemas:

z.object({
type: z.literal('url_citation'),
url: z.string(),
title: z.string().optional(), // ← Make optional
}),

And update the annotation handling in packages/openai/src/responses/openai-responses-language-model.ts (around lines 475-482
and 1373-1380) to provide a default value:

if (annotation.type === 'url_citation') {
controller.enqueue({
type: 'source',
sourceType: 'url',
id: self.config.generateId?.() ?? generateId(),
url: annotation.url,
title: annotation.title ?? annotation.url, // ← Use URL as fallback
});
}

Environment

  • Package: @ai-sdk/openai (OpenAI-compatible provider)
  • Provider: OpenRouter
  • Model: moonshotai/kimi-k2-thinking (confirmed), likely affects other OpenRouter models
  • SDK Version: [Your version here]

Reproduction

import { streamText } from 'ai';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';

const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY
});

const result = await streamText({
model: openrouter('moonshotai/kimi-k2-thinking'),
prompt: 'What is XAI?', // Or any prompt that triggers web search citations
});

// Error occurs when processing streaming response with url_citation annotations

AI SDK Version

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions