Roo-Code/src/api/index.ts
Hannes Rudolph 1b75d59a68
fix(ai-sdk): preserve reasoning parts in message conversion (#11217)
* fix(ai-sdk): preserve reasoning parts in message conversion

* fix(ai-sdk): convert message-level reasoning_content to reasoning part

* fix(task): remove invalid openai-compatible from reasoning allowlist

* feat: add isAiSdkProvider() method for dynamic AI SDK provider detection

- Add isAiSdkProvider() method to ApiHandler interface
- Default implementation in BaseProvider returns false
- Override to return true in 11 AI SDK providers:
  deepseek, fireworks, mistral, groq, xai, cerebras,
  sambanova, huggingface, gemini, vertex, openai-compatible
- Update Task.ts to use dynamic detection instead of hardcoded Set
- Add method to FakeAIHandler and update test mocks

* fix: handle reasoning parts in flattenAiSdkMessagesToStringContent

- Strip reasoning parts when flattening messages for string-only models
- Allow flattening when message contains only text and reasoning parts
- Add tests for reasoning part handling in string-only model contexts

This addresses the review feedback about ensuring flattenAiSdkMessagesToStringContent
works correctly when reasoning parts are present (e.g., SambaNova DeepSeek).

---------

Co-authored-by: daniel-lxs <ricciodaniel98@gmail.com>
2026-02-05 10:48:21 -07:00

212 lines
6.4 KiB
TypeScript

import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import type { ProviderSettings, ModelInfo } from "@roo-code/types"
import { ApiStream } from "./transform/stream"
import {
AnthropicHandler,
AwsBedrockHandler,
CerebrasHandler,
OpenRouterHandler,
VertexHandler,
AnthropicVertexHandler,
OpenAiHandler,
OpenAiCodexHandler,
LmStudioHandler,
GeminiHandler,
OpenAiNativeHandler,
DeepSeekHandler,
MoonshotHandler,
MistralHandler,
VsCodeLmHandler,
UnboundHandler,
RequestyHandler,
FakeAIHandler,
XAIHandler,
GroqHandler,
HuggingFaceHandler,
ChutesHandler,
LiteLLMHandler,
QwenCodeHandler,
SambaNovaHandler,
IOIntelligenceHandler,
DoubaoHandler,
ZAiHandler,
FireworksHandler,
RooHandler,
FeatherlessHandler,
VercelAiGatewayHandler,
DeepInfraHandler,
MiniMaxHandler,
BasetenHandler,
} from "./providers"
import { NativeOllamaHandler } from "./providers/native-ollama"
export interface SingleCompletionHandler {
completePrompt(prompt: string): Promise<string>
}
export interface ApiHandlerCreateMessageMetadata {
/**
* Task ID used for tracking and provider-specific features:
* - DeepInfra: Used as prompt_cache_key for caching
* - Roo: Sent as X-Roo-Task-ID header
* - Requesty: Sent as trace_id
* - Unbound: Sent in unbound_metadata
*/
taskId: string
/**
* Current mode slug for provider-specific tracking:
* - Requesty: Sent in extra metadata
* - Unbound: Sent in unbound_metadata
*/
mode?: string
suppressPreviousResponseId?: boolean
/**
* Controls whether the response should be stored for 30 days in OpenAI's Responses API.
* When true (default), responses are stored and can be referenced in future requests
* using the previous_response_id for efficient conversation continuity.
* Set to false to opt out of response storage for privacy or compliance reasons.
* @default true
*/
store?: boolean
/**
* Optional array of tool definitions to pass to the model.
* For OpenAI-compatible providers, these are ChatCompletionTool definitions.
*/
tools?: OpenAI.Chat.ChatCompletionTool[]
/**
* Controls which (if any) tool is called by the model.
* Can be "none", "auto", "required", or a specific tool choice.
*/
tool_choice?: OpenAI.Chat.ChatCompletionCreateParams["tool_choice"]
/**
* Controls whether the model can return multiple tool calls in a single response.
* When true (default), parallel tool calls are enabled (OpenAI's parallel_tool_calls=true).
* When false, only one tool call is returned per response.
*/
parallelToolCalls?: boolean
/**
* Optional array of tool names that the model is allowed to call.
* When provided, all tool definitions are passed to the model (so it can reference
* historical tool calls), but only the specified tools can actually be invoked.
* This is used when switching modes to prevent model errors from missing tool
* definitions while still restricting callable tools to the current mode's permissions.
* Only applies to providers that support function calling restrictions (e.g., Gemini).
*/
allowedFunctionNames?: string[]
}
export interface ApiHandler {
createMessage(
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
metadata?: ApiHandlerCreateMessageMetadata,
): ApiStream
getModel(): { id: string; info: ModelInfo }
/**
* Counts tokens for content blocks
* All providers extend BaseProvider which provides a default tiktoken implementation,
* but they can override this to use their native token counting endpoints
*
* @param content The content to count tokens for
* @returns A promise resolving to the token count
*/
countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number>
/**
* Indicates whether this provider uses the Vercel AI SDK for streaming.
* AI SDK providers handle reasoning blocks differently and need to preserve
* them in conversation history for proper round-tripping.
*
* @returns true if the provider uses AI SDK, false otherwise
*/
isAiSdkProvider(): boolean
}
export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
const { apiProvider, ...options } = configuration
switch (apiProvider) {
case "anthropic":
return new AnthropicHandler(options)
case "openrouter":
return new OpenRouterHandler(options)
case "bedrock":
return new AwsBedrockHandler(options)
case "vertex":
return options.apiModelId?.startsWith("claude")
? new AnthropicVertexHandler(options)
: new VertexHandler(options)
case "openai":
return new OpenAiHandler(options)
case "ollama":
return new NativeOllamaHandler(options)
case "lmstudio":
return new LmStudioHandler(options)
case "gemini":
return new GeminiHandler(options)
case "openai-codex":
return new OpenAiCodexHandler(options)
case "openai-native":
return new OpenAiNativeHandler(options)
case "deepseek":
return new DeepSeekHandler(options)
case "doubao":
return new DoubaoHandler(options)
case "qwen-code":
return new QwenCodeHandler(options)
case "moonshot":
return new MoonshotHandler(options)
case "vscode-lm":
return new VsCodeLmHandler(options)
case "mistral":
return new MistralHandler(options)
case "unbound":
return new UnboundHandler(options)
case "requesty":
return new RequestyHandler(options)
case "fake-ai":
return new FakeAIHandler(options)
case "xai":
return new XAIHandler(options)
case "groq":
return new GroqHandler(options)
case "deepinfra":
return new DeepInfraHandler(options)
case "huggingface":
return new HuggingFaceHandler(options)
case "chutes":
return new ChutesHandler(options)
case "litellm":
return new LiteLLMHandler(options)
case "cerebras":
return new CerebrasHandler(options)
case "sambanova":
return new SambaNovaHandler(options)
case "zai":
return new ZAiHandler(options)
case "fireworks":
return new FireworksHandler(options)
case "io-intelligence":
return new IOIntelligenceHandler(options)
case "roo":
// Never throw exceptions from provider constructors
// The provider-proxy server will handle authentication and return appropriate error codes
return new RooHandler(options)
case "featherless":
return new FeatherlessHandler(options)
case "vercel-ai-gateway":
return new VercelAiGatewayHandler(options)
case "minimax":
return new MiniMaxHandler(options)
case "baseten":
return new BasetenHandler(options)
default:
return new AnthropicHandler(options)
}
}