MCPcopy Create free account
hub / github.com/Zoo-Code-Org/Zoo-Code / createMessage

Method createMessage

src/api/providers/openrouter.ts:209–534  ·  view source on GitHub ↗
(
		systemPrompt: string,
		messages: Anthropic.Messages.MessageParam[],
		metadata?: ApiHandlerCreateMessageMetadata,
	)

Source from the content-addressed store, hash-verified

207 }
208
209 override async *createMessage(
210 systemPrompt: string,
211 messages: Anthropic.Messages.MessageParam[],
212 metadata?: ApiHandlerCreateMessageMetadata,
213 ): AsyncGenerator<ApiStreamChunk> {
214 const model = await this.fetchModel()
215
216 let { id: modelId, maxTokens, temperature, topP, reasoning } = model
217
218 // Reset reasoning_details accumulator for this request
219 this.currentReasoningDetails = []
220
221 // OpenRouter sends reasoning tokens by default for Gemini 2.5 Pro models
222 // even if you don't request them. This is not the default for
223 // other providers (including Gemini), so we need to explicitly disable
224 // them unless the user has explicitly configured reasoning.
225 // Note: Gemini 3 models use reasoning_details format with thought signatures,
226 // but we handle this via skip_thought_signature_validator injection below.
227 if (
228 (modelId === "google/gemini-2.5-pro-preview" || modelId === "google/gemini-2.5-pro") &&
229 typeof reasoning === "undefined"
230 ) {
231 reasoning = { exclude: true }
232 }
233
234 // Convert Anthropic messages to OpenAI format.
235 // Pass normalization function for Mistral compatibility (requires 9-char alphanumeric IDs)
236 const isMistral = modelId.toLowerCase().includes("mistral")
237 let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
238 { role: "system", content: systemPrompt },
239 ...convertToOpenAiMessages(
240 messages,
241 isMistral ? { normalizeToolCallId: normalizeMistralToolCallId } : undefined,
242 ),
243 ]
244
245 // DeepSeek highly recommends using user instead of system role.
246 if (modelId.startsWith("deepseek/deepseek-r1") || modelId === "perplexity/sonar-reasoning") {
247 openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
248 }
249
250 // Process reasoning_details when switching models to Gemini.
251 const isGemini = modelId.startsWith("google/gemini")
252
253 // For Gemini models with native protocol:
254 // 1. Sanitize messages to handle thought signature validation issues.
255 // This must happen BEFORE fake encrypted block injection to avoid injecting for
256 // tool calls that will be dropped due to missing/mismatched reasoning_details.
257 // 2. Inject fake reasoning.encrypted block for tool calls without existing encrypted reasoning.
258 // This is required when switching from other models to Gemini to satisfy API validation.
259 // Per OpenRouter documentation (conversation with Toven, Nov 2025):
260 // - Create ONE reasoning_details entry per assistant message with tool calls
261 // - Set `id` to the FIRST tool call's ID from the tool_calls array
262 // - Set `data` to "skip_thought_signature_validator" to bypass signature validation
263 // - Set `index` to 0
264 // See: https://github.com/cline/cline/issues/8214
265 if (isGemini) {
266 // Step 1: Sanitize messages - filter out tool calls with missing/mismatched reasoning_details

Callers

nothing calls this directly

Calls 15

fetchModelMethod · 0.95
handleStreamingErrorMethod · 0.95
convertToOpenAiMessagesFunction · 0.90
convertToR1FormatFunction · 0.90
sanitizeGeminiMessagesFunction · 0.90
handleOpenAIErrorFunction · 0.90
processFinishReasonMethod · 0.80
fromMethod · 0.80
hasMethod · 0.65
captureExceptionMethod · 0.65

Tested by

no test coverage detected