( systemPrompt: string, messages: Anthropic.Messages.MessageParam[], metadata?: ApiHandlerCreateMessageMetadata, )
| 87 | } |
| 88 | |
| 89 | override async *createMessage( |
| 90 | systemPrompt: string, |
| 91 | messages: Anthropic.Messages.MessageParam[], |
| 92 | metadata?: ApiHandlerCreateMessageMetadata, |
| 93 | ): ApiStream { |
| 94 | const modelId = this.options.apiModelId ?? deepSeekDefaultModelId |
| 95 | const { info: modelInfo, temperature, reasoningEffort, maxTokens } = this.getModel() |
| 96 | |
| 97 | const isThinkingModel = isDeepSeekThinkingEnabled(modelId, this.options) |
| 98 | const thinking = supportsDeepSeekThinkingToggle(modelId) |
| 99 | ? ({ type: isThinkingModel ? "enabled" : "disabled" } as const) |
| 100 | : isThinkingModel |
| 101 | ? ({ type: "enabled" } as const) |
| 102 | : undefined |
| 103 | const deepSeekReasoningEffort = isThinkingModel ? normalizeDeepSeekReasoningEffort(reasoningEffort) : undefined |
| 104 | |
| 105 | // Convert messages to R1 format (merges consecutive same-role messages) |
| 106 | // This is required for DeepSeek which does not support successive messages with the same role |
| 107 | // For thinking models, enable mergeToolResultText to preserve reasoning_content |
| 108 | // during tool call sequences. Without this, environment_details text after tool_results would |
| 109 | // create user messages that cause DeepSeek to drop all previous reasoning_content. |
| 110 | // See: https://api-docs.deepseek.com/guides/thinking_mode |
| 111 | const convertedMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages], { |
| 112 | mergeToolResultText: isThinkingModel, |
| 113 | }) |
| 114 | |
| 115 | const requestOptions: DeepSeekChatCompletionParams = { |
| 116 | model: modelId, |
| 117 | ...(!isThinkingModel && { temperature: temperature ?? DEEP_SEEK_DEFAULT_TEMPERATURE }), |
| 118 | messages: convertedMessages, |
| 119 | stream: true as const, |
| 120 | stream_options: { include_usage: true }, |
| 121 | ...(thinking && { thinking }), |
| 122 | ...(deepSeekReasoningEffort && { reasoning_effort: deepSeekReasoningEffort }), |
| 123 | tools: this.convertToolsForOpenAI(metadata?.tools), |
| 124 | tool_choice: metadata?.tool_choice, |
| 125 | parallel_tool_calls: metadata?.parallelToolCalls ?? true, |
| 126 | } |
| 127 | |
| 128 | addDeepSeekMaxTokensIfNeeded(requestOptions, this.options, maxTokens) |
| 129 | |
| 130 | // Check if base URL is Azure AI Inference (for DeepSeek via Azure) |
| 131 | const isAzureAiInference = this._isAzureAiInference(this.options.deepSeekBaseUrl) |
| 132 | |
| 133 | let stream |
| 134 | try { |
| 135 | stream = await this.client.chat.completions.create( |
| 136 | requestOptions as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming, |
| 137 | isAzureAiInference ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {}, |
| 138 | ) |
| 139 | } catch (error) { |
| 140 | const { handleOpenAIError } = await import("./utils/openai-error-handler") |
| 141 | throw handleOpenAIError(error, "DeepSeek") |
| 142 | } |
| 143 | |
| 144 | let lastUsage |
| 145 | |
| 146 | for await (const chunk of stream) { |
nothing calls this directly
no test coverage detected