* Streams a chat completion response, yielding typed chunks for text, * reasoning, partial tool calls, and token usage. * * Anthropic-format models (Qwen/MiniMax) are streamed via * streamAnthropicMessage against `/v1/messages`; all other models * use the OpenAI-compatible chat com
( systemPrompt: string, messages: Anthropic.Messages.MessageParam[], metadata?: ApiHandlerCreateMessageMetadata, )
| 158 | * model advertises `supportsReasoningEffort`. |
| 159 | */ |
| 160 | override async *createMessage( |
| 161 | systemPrompt: string, |
| 162 | messages: Anthropic.Messages.MessageParam[], |
| 163 | metadata?: ApiHandlerCreateMessageMetadata, |
| 164 | ): ApiStream { |
| 165 | const { id: modelId, info, format, temperature, reasoningEffort, maxTokens } = await this.resolveModel() |
| 166 | |
| 167 | if (format === "anthropic") { |
| 168 | yield* this.streamAnthropicMessage(modelId, info, temperature, maxTokens, systemPrompt, messages, metadata) |
| 169 | return |
| 170 | } |
| 171 | |
| 172 | // preserveReasoning models (GLM/DeepSeek/MiMo/MiniMax/Qwen) require |
| 173 | // reasoning_content to be carried across tool-call continuations. |
| 174 | const preserveReasoning = info.preserveReasoning === true |
| 175 | const convertedMessages = preserveReasoning |
| 176 | ? convertToR1Format(messages, { mergeToolResultText: true }) |
| 177 | : convertToOpenAiMessages(messages) |
| 178 | |
| 179 | const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ |
| 180 | { role: "system", content: systemPrompt }, |
| 181 | ...convertedMessages, |
| 182 | ] |
| 183 | |
| 184 | const body: OpenAI.Chat.ChatCompletionCreateParams = { |
| 185 | model: modelId, |
| 186 | messages: openAiMessages, |
| 187 | temperature: this.supportsTemperature(modelId) ? temperature : undefined, |
| 188 | max_completion_tokens: |
| 189 | this.options.includeMaxTokens === true ? this.options.modelMaxTokens || maxTokens : maxTokens, |
| 190 | stream: true, |
| 191 | stream_options: { include_usage: true }, |
| 192 | tools: this.convertToolsForOpenAI(metadata?.tools), |
| 193 | tool_choice: metadata?.tool_choice, |
| 194 | parallel_tool_calls: metadata?.parallelToolCalls ?? true, |
| 195 | ...(reasoningEffort && { |
| 196 | reasoning_effort: reasoningEffort as OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"], |
| 197 | }), |
| 198 | } |
| 199 | |
| 200 | const completion = await this.client.chat.completions.create(body) |
| 201 | |
| 202 | for await (const chunk of completion) { |
| 203 | const delta = chunk.choices[0]?.delta |
| 204 | |
| 205 | if (delta?.content) { |
| 206 | yield { type: "text", text: delta.content } |
| 207 | } |
| 208 | |
| 209 | // Several Go-plan models (GLM, DeepSeek) stream reasoning via this field. |
| 210 | const reasoningText = extractReasoningFromDelta(delta) |
| 211 | if (reasoningText) { |
| 212 | yield { type: "reasoning", text: reasoningText } |
| 213 | } |
| 214 | |
| 215 | // Emit raw tool call chunks - NativeToolCallParser handles state management. |
| 216 | if (delta?.tool_calls) { |
| 217 | for (const toolCall of delta.tool_calls) { |
nothing calls this directly
no test coverage detected