mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-11 22:51:26 +00:00
Merge remote-tracking branch 'origin/main' into feat/roo-code-requesty-provider
This commit is contained in:
commit
2449c49358
23 changed files with 320 additions and 88 deletions
|
|
@ -1,7 +1,5 @@
|
|||
import { OpenAiHandler } from "../openai"
|
||||
import { ApiHandlerOptions } from "../../../shared/api"
|
||||
import { ApiStream } from "../../transform/stream"
|
||||
import OpenAI from "openai"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
// Mock OpenAI client
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import {
|
|||
import { ApiHandler, SingleCompletionHandler } from "../index"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
const ANTHROPIC_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
export class AnthropicHandler implements ApiHandler, SingleCompletionHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: Anthropic
|
||||
|
|
@ -44,7 +46,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler {
|
|||
{
|
||||
model: modelId,
|
||||
max_tokens: this.getModel().info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? ANTHROPIC_DEFAULT_TEMPERATURE,
|
||||
system: [{ text: systemPrompt, type: "text", cache_control: { type: "ephemeral" } }], // setting cache breakpoint for system prompt so new tasks can reuse it
|
||||
messages: messages.map((message, index) => {
|
||||
if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
|
||||
|
|
@ -96,7 +98,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler {
|
|||
stream = (await this.client.messages.create({
|
||||
model: modelId,
|
||||
max_tokens: this.getModel().info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? ANTHROPIC_DEFAULT_TEMPERATURE,
|
||||
system: [{ text: systemPrompt, type: "text" }],
|
||||
messages,
|
||||
// tools,
|
||||
|
|
@ -179,7 +181,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const response = await this.client.messages.create({
|
||||
model: this.getModel().id,
|
||||
max_tokens: this.getModel().info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? ANTHROPIC_DEFAULT_TEMPERATURE,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
stream: false,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import { ApiHandlerOptions, BedrockModelId, ModelInfo, bedrockDefaultModelId, be
|
|||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToBedrockConverseMessages, convertToAnthropicMessage } from "../transform/bedrock-converse-format"
|
||||
|
||||
const BEDROCK_DEFAULT_TEMPERATURE = 0.3
|
||||
|
||||
// Define types for stream events based on AWS SDK
|
||||
export interface StreamEvent {
|
||||
messageStart?: {
|
||||
|
|
@ -104,7 +106,7 @@ export class AwsBedrockHandler implements ApiHandler, SingleCompletionHandler {
|
|||
system: [{ text: systemPrompt }],
|
||||
inferenceConfig: {
|
||||
maxTokens: modelConfig.info.maxTokens || 5000,
|
||||
temperature: 0.3,
|
||||
temperature: this.options.modelTemperature ?? BEDROCK_DEFAULT_TEMPERATURE,
|
||||
topP: 0.1,
|
||||
...(this.options.awsUsePromptCache
|
||||
? {
|
||||
|
|
@ -262,7 +264,7 @@ export class AwsBedrockHandler implements ApiHandler, SingleCompletionHandler {
|
|||
]),
|
||||
inferenceConfig: {
|
||||
maxTokens: modelConfig.info.maxTokens || 5000,
|
||||
temperature: 0.3,
|
||||
temperature: this.options.modelTemperature ?? BEDROCK_DEFAULT_TEMPERATURE,
|
||||
topP: 0.1,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, M
|
|||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
const GEMINI_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
export class GeminiHandler implements ApiHandler, SingleCompletionHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: GoogleGenerativeAI
|
||||
|
|
@ -23,7 +25,7 @@ export class GeminiHandler implements ApiHandler, SingleCompletionHandler {
|
|||
contents: messages.map(convertAnthropicMessageToGemini),
|
||||
generationConfig: {
|
||||
// maxOutputTokens: this.getModel().info.maxTokens,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? GEMINI_DEFAULT_TEMPERATURE,
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -60,7 +62,7 @@ export class GeminiHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const result = await model.generateContent({
|
||||
contents: [{ role: "user", parts: [{ text: prompt }] }],
|
||||
generationConfig: {
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? GEMINI_DEFAULT_TEMPERATURE,
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { ApiHandler, SingleCompletionHandler } from "../"
|
|||
import { ApiHandlerOptions, ModelInfo, glamaDefaultModelId, glamaDefaultModelInfo } from "../../shared/api"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import delay from "delay"
|
||||
|
||||
const GLAMA_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
export class GlamaHandler implements ApiHandler, SingleCompletionHandler {
|
||||
private options: ApiHandlerOptions
|
||||
|
|
@ -79,7 +80,7 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler {
|
|||
}
|
||||
|
||||
if (this.supportsTemperature()) {
|
||||
requestOptions.temperature = 0
|
||||
requestOptions.temperature = this.options.modelTemperature ?? GLAMA_DEFAULT_TEMPERATURE
|
||||
}
|
||||
|
||||
const { data: completion, response } = await this.client.chat.completions
|
||||
|
|
@ -172,7 +173,7 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler {
|
|||
}
|
||||
|
||||
if (this.supportsTemperature()) {
|
||||
requestOptions.temperature = 0
|
||||
requestOptions.temperature = this.options.modelTemperature ?? GLAMA_DEFAULT_TEMPERATURE
|
||||
}
|
||||
|
||||
if (this.getModel().id.startsWith("anthropic/")) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../..
|
|||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
const LMSTUDIO_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
export class LmStudioHandler implements ApiHandler, SingleCompletionHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI
|
||||
|
|
@ -27,7 +29,7 @@ export class LmStudioHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const stream = await this.client.chat.completions.create({
|
||||
model: this.getModel().id,
|
||||
messages: openAiMessages,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? LMSTUDIO_DEFAULT_TEMPERATURE,
|
||||
stream: true,
|
||||
})
|
||||
for await (const chunk of stream) {
|
||||
|
|
@ -59,7 +61,7 @@ export class LmStudioHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const response = await this.client.chat.completions.create({
|
||||
model: this.getModel().id,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? LMSTUDIO_DEFAULT_TEMPERATURE,
|
||||
stream: false,
|
||||
})
|
||||
return response.choices[0]?.message.content || ""
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import {
|
|||
import { convertToMistralMessages } from "../transform/mistral-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
const MISTRAL_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
export class MistralHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: Mistral
|
||||
|
|
@ -30,7 +32,7 @@ export class MistralHandler implements ApiHandler {
|
|||
const stream = await this.client.chat.stream({
|
||||
model: this.getModel().id,
|
||||
// max_completion_tokens: this.getModel().info.maxTokens,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? MISTRAL_DEFAULT_TEMPERATURE,
|
||||
messages: [{ role: "system", content: systemPrompt }, ...convertToMistralMessages(messages)],
|
||||
stream: true,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../..
|
|||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./openai"
|
||||
|
||||
const OLLAMA_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
export class OllamaHandler implements ApiHandler, SingleCompletionHandler {
|
||||
private options: ApiHandlerOptions
|
||||
|
|
@ -20,7 +23,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler {
|
|||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const modelId = this.getModel().id
|
||||
const useR1Format = modelId.toLowerCase().includes('deepseek-r1')
|
||||
const useR1Format = modelId.toLowerCase().includes("deepseek-r1")
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...(useR1Format ? convertToR1Format(messages) : convertToOpenAiMessages(messages)),
|
||||
|
|
@ -29,7 +32,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const stream = await this.client.chat.completions.create({
|
||||
model: this.getModel().id,
|
||||
messages: openAiMessages,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? OLLAMA_DEFAULT_TEMPERATURE,
|
||||
stream: true,
|
||||
})
|
||||
for await (const chunk of stream) {
|
||||
|
|
@ -53,11 +56,15 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler {
|
|||
async completePrompt(prompt: string): Promise<string> {
|
||||
try {
|
||||
const modelId = this.getModel().id
|
||||
const useR1Format = modelId.toLowerCase().includes('deepseek-r1')
|
||||
const useR1Format = modelId.toLowerCase().includes("deepseek-r1")
|
||||
const response = await this.client.chat.completions.create({
|
||||
model: this.getModel().id,
|
||||
messages: useR1Format ? convertToR1Format([{ role: "user", content: prompt }]) : [{ role: "user", content: prompt }],
|
||||
temperature: 0,
|
||||
messages: useR1Format
|
||||
? convertToR1Format([{ role: "user", content: prompt }])
|
||||
: [{ role: "user", content: prompt }],
|
||||
temperature:
|
||||
this.options.modelTemperature ??
|
||||
(useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : OLLAMA_DEFAULT_TEMPERATURE),
|
||||
stream: false,
|
||||
})
|
||||
return response.choices[0]?.message.content || ""
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import {
|
|||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
const OPENAI_NATIVE_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI
|
||||
|
|
@ -88,7 +90,7 @@ export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler
|
|||
): ApiStream {
|
||||
const stream = await this.client.chat.completions.create({
|
||||
model: modelId,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? OPENAI_NATIVE_DEFAULT_TEMPERATURE,
|
||||
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
|
|
@ -189,7 +191,7 @@ export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler
|
|||
return {
|
||||
model: modelId,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? OPENAI_NATIVE_DEFAULT_TEMPERATURE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ export interface OpenAiHandlerOptions extends ApiHandlerOptions {
|
|||
defaultHeaders?: Record<string, string>
|
||||
}
|
||||
|
||||
export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6
|
||||
const OPENAI_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
export class OpenAiHandler implements ApiHandler, SingleCompletionHandler {
|
||||
protected options: OpenAiHandlerOptions
|
||||
private client: OpenAI
|
||||
|
|
@ -74,7 +77,9 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler {
|
|||
|
||||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||
model: modelId,
|
||||
temperature: 0,
|
||||
temperature:
|
||||
this.options.modelTemperature ??
|
||||
(deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : OPENAI_DEFAULT_TEMPERATURE),
|
||||
messages: convertedMessages,
|
||||
stream: true as const,
|
||||
stream_options: { include_usage: true },
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefau
|
|||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStreamChunk, ApiStreamUsageChunk } from "../transform/stream"
|
||||
import delay from "delay"
|
||||
import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./openai"
|
||||
|
||||
const OPENROUTER_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
// Add custom interface for OpenRouter params
|
||||
type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & {
|
||||
|
|
@ -115,7 +118,7 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler {
|
|||
break
|
||||
}
|
||||
|
||||
let temperature = 0
|
||||
let defaultTemperature = OPENROUTER_DEFAULT_TEMPERATURE
|
||||
let topP: number | undefined = undefined
|
||||
|
||||
// Handle models based on deepseek-r1
|
||||
|
|
@ -124,9 +127,8 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler {
|
|||
this.getModel().id === "perplexity/sonar-reasoning"
|
||||
) {
|
||||
// Recommended temperature for DeepSeek reasoning models
|
||||
temperature = 0.6
|
||||
// DeepSeek highly recommends using user instead of system
|
||||
// role
|
||||
defaultTemperature = DEEP_SEEK_DEFAULT_TEMPERATURE
|
||||
// DeepSeek highly recommends using user instead of system role
|
||||
openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
// Some provider support topP and 0.95 is value that Deepseek used in their benchmarks
|
||||
topP = 0.95
|
||||
|
|
@ -137,7 +139,7 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const stream = await this.client.chat.completions.create({
|
||||
model: this.getModel().id,
|
||||
max_tokens: maxTokens,
|
||||
temperature: temperature,
|
||||
temperature: this.options.modelTemperature ?? defaultTemperature,
|
||||
top_p: topP,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
|
|
@ -183,31 +185,35 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler {
|
|||
// }
|
||||
}
|
||||
|
||||
await delay(500) // FIXME: necessary delay to ensure generation endpoint is ready
|
||||
// retry fetching generation details
|
||||
let attempt = 0
|
||||
while (attempt++ < 10) {
|
||||
await delay(200) // FIXME: necessary delay to ensure generation endpoint is ready
|
||||
try {
|
||||
const response = await axios.get(`https://openrouter.ai/api/v1/generation?id=${genId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.options.openRouterApiKey}`,
|
||||
},
|
||||
timeout: 5_000, // this request hangs sometimes
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await axios.get(`https://openrouter.ai/api/v1/generation?id=${genId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.options.openRouterApiKey}`,
|
||||
},
|
||||
timeout: 5_000, // this request hangs sometimes
|
||||
})
|
||||
|
||||
const generation = response.data?.data
|
||||
console.log("OpenRouter generation details:", response.data)
|
||||
yield {
|
||||
type: "usage",
|
||||
// cacheWriteTokens: 0,
|
||||
// cacheReadTokens: 0,
|
||||
// openrouter generation endpoint fails often
|
||||
inputTokens: generation?.native_tokens_prompt || 0,
|
||||
outputTokens: generation?.native_tokens_completion || 0,
|
||||
totalCost: generation?.total_cost || 0,
|
||||
fullResponseText,
|
||||
} as OpenRouterApiStreamUsageChunk
|
||||
} catch (error) {
|
||||
// ignore if fails
|
||||
console.error("Error fetching OpenRouter generation details:", error)
|
||||
const generation = response.data?.data
|
||||
console.log("OpenRouter generation details:", response.data)
|
||||
yield {
|
||||
type: "usage",
|
||||
// cacheWriteTokens: 0,
|
||||
// cacheReadTokens: 0,
|
||||
// openrouter generation endpoint fails often
|
||||
inputTokens: generation?.native_tokens_prompt || 0,
|
||||
outputTokens: generation?.native_tokens_completion || 0,
|
||||
totalCost: generation?.total_cost || 0,
|
||||
fullResponseText,
|
||||
} as OpenRouterApiStreamUsageChunk
|
||||
return
|
||||
} catch (error) {
|
||||
// ignore if fails
|
||||
console.error("Error fetching OpenRouter generation details:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
|
|
@ -224,7 +230,7 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const response = await this.client.chat.completions.create({
|
||||
model: this.getModel().id,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? OPENROUTER_DEFAULT_TEMPERATURE,
|
||||
stream: false,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export class UnboundHandler implements ApiHandler, SingleCompletionHandler {
|
|||
{
|
||||
model: this.getModel().id.split("/")[1],
|
||||
max_tokens: maxTokens,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? 0,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
},
|
||||
|
|
@ -146,7 +146,7 @@ export class UnboundHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
|
||||
model: this.getModel().id.split("/")[1],
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? 0,
|
||||
}
|
||||
|
||||
if (this.getModel().id.startsWith("anthropic/")) {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const stream = await this.client.messages.create({
|
||||
model: this.getModel().id,
|
||||
max_tokens: this.getModel().info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? 0,
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
stream: true,
|
||||
|
|
@ -89,7 +89,7 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const response = await this.client.messages.create({
|
||||
model: this.getModel().id,
|
||||
max_tokens: this.getModel().info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
temperature: this.options.modelTemperature ?? 0,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
stream: false,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ export class Cline {
|
|||
this.fuzzyMatchThreshold = fuzzyMatchThreshold ?? 1.0
|
||||
this.providerRef = new WeakRef(provider)
|
||||
this.diffViewProvider = new DiffViewProvider(cwd)
|
||||
this.checkpointsEnabled = enableCheckpoints ?? false
|
||||
this.checkpointsEnabled = process.platform !== "win32" && !!enableCheckpoints
|
||||
|
||||
if (historyItem) {
|
||||
this.taskId = historyItem.id
|
||||
|
|
@ -3240,6 +3240,7 @@ export class Cline {
|
|||
this.checkpointService = await CheckpointService.create({
|
||||
taskId: this.taskId,
|
||||
baseDir: vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? "",
|
||||
log: (message) => this.providerRef.deref()?.log(message),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ type GlobalStateKey =
|
|||
| "requestyModelId"
|
||||
| "requestyModelInfo"
|
||||
| "unboundModelInfo"
|
||||
| "modelTemperature"
|
||||
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
|
|
@ -1619,6 +1620,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
requestyApiKey,
|
||||
requestyModelId,
|
||||
requestyModelInfo,
|
||||
modelTemperature,
|
||||
} = apiConfiguration
|
||||
await this.updateGlobalState("apiProvider", apiProvider)
|
||||
await this.updateGlobalState("apiModelId", apiModelId)
|
||||
|
|
@ -1663,6 +1665,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.storeSecret("requestyApiKey", requestyApiKey)
|
||||
await this.updateGlobalState("requestyModelId", requestyModelId)
|
||||
await this.updateGlobalState("requestyModelInfo", requestyModelInfo)
|
||||
await this.updateGlobalState("modelTemperature", modelTemperature)
|
||||
if (this.cline) {
|
||||
this.cline.api = buildApiHandler(apiConfiguration)
|
||||
}
|
||||
|
|
@ -2513,6 +2516,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
requestyApiKey,
|
||||
requestyModelId,
|
||||
requestyModelInfo,
|
||||
modelTemperature,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<string | undefined>,
|
||||
|
|
@ -2592,6 +2596,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
this.getSecret("requestyApiKey") as Promise<string | undefined>,
|
||||
this.getGlobalState("requestyModelId") as Promise<string | undefined>,
|
||||
this.getGlobalState("requestyModelInfo") as Promise<ModelInfo | undefined>,
|
||||
this.getGlobalState("modelTemperature") as Promise<number | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
|
|
@ -2653,6 +2658,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
requestyApiKey,
|
||||
requestyModelId,
|
||||
requestyModelInfo,
|
||||
modelTemperature,
|
||||
},
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import fs from "fs/promises"
|
|||
import { existsSync } from "fs"
|
||||
import path from "path"
|
||||
|
||||
import debug from "debug"
|
||||
import simpleGit, { SimpleGit, CleanOptions } from "simple-git"
|
||||
|
||||
export type CheckpointServiceOptions = {
|
||||
|
|
@ -246,15 +245,11 @@ export class CheckpointService {
|
|||
}
|
||||
|
||||
public static async create({ taskId, git, baseDir, log = console.log }: CheckpointServiceOptions) {
|
||||
git =
|
||||
git ||
|
||||
simpleGit({
|
||||
baseDir,
|
||||
binary: "git",
|
||||
maxConcurrentProcesses: 1,
|
||||
config: [],
|
||||
trimmed: true,
|
||||
})
|
||||
if (process.platform === "win32") {
|
||||
throw new Error("Checkpoints are not supported on Windows.")
|
||||
}
|
||||
|
||||
git = git || simpleGit({ baseDir })
|
||||
|
||||
const version = await git.version()
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ describe("CheckpointService", () => {
|
|||
let git: SimpleGit
|
||||
let testFile: string
|
||||
let service: CheckpointService
|
||||
let originalPlatform: string
|
||||
|
||||
const initRepo = async ({
|
||||
baseDir,
|
||||
|
|
@ -48,6 +49,19 @@ describe("CheckpointService", () => {
|
|||
return { git, testFile }
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
originalPlatform = process.platform
|
||||
Object.defineProperty(process, "platform", {
|
||||
value: "darwin",
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
Object.defineProperty(process, "platform", {
|
||||
value: originalPlatform,
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
const baseDir = path.join(os.tmpdir(), `checkpoint-service-test-${Date.now()}`)
|
||||
const repo = await initRepo({ baseDir })
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ export interface ApiHandlerOptions {
|
|||
requestyApiKey?: string
|
||||
requestyModelId?: string
|
||||
requestyModelInfo?: ModelInfo
|
||||
modelTemperature?: number
|
||||
}
|
||||
|
||||
export type ApiConfiguration = ApiHandlerOptions & {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { memo, useCallback, useEffect, useMemo, useState } from "react"
|
|||
import { useEvent, useInterval } from "react-use"
|
||||
import { Checkbox, Dropdown, Pane, type DropdownOption } from "vscrui"
|
||||
import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { TemperatureControl } from "./TemperatureControl"
|
||||
import * as vscodemodels from "vscode"
|
||||
|
||||
import {
|
||||
|
|
@ -1390,6 +1391,18 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
|
|||
</>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<TemperatureControl
|
||||
value={apiConfiguration?.modelTemperature}
|
||||
onChange={(value) => {
|
||||
handleInputChange("modelTemperature")({
|
||||
target: { value },
|
||||
})
|
||||
}}
|
||||
maxValue={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{modelIdErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -701,27 +701,29 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: 15 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>⚠️</span>
|
||||
<VSCodeCheckbox
|
||||
checked={checkpointsEnabled}
|
||||
onChange={(e: any) => {
|
||||
setCheckpointsEnabled(e.target.checked)
|
||||
{process.platform !== "win32" && (
|
||||
<div style={{ marginBottom: 15 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>⚠️</span>
|
||||
<VSCodeCheckbox
|
||||
checked={checkpointsEnabled}
|
||||
onChange={(e: any) => {
|
||||
setCheckpointsEnabled(e.target.checked)
|
||||
}}>
|
||||
<span style={{ fontWeight: "500" }}>Enable experimental checkpoints</span>
|
||||
</VSCodeCheckbox>
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<span style={{ fontWeight: "500" }}>Enable experimental checkpoints</span>
|
||||
</VSCodeCheckbox>
|
||||
When enabled, Roo will save a checkpoint whenever a file in the workspace is
|
||||
modified, added or deleted, letting you easily revert to a previous state.
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
When enabled, Roo will save a checkpoint whenever a file in the workspace is modified,
|
||||
added or deleted, letting you easily revert to a previous state.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{Object.entries(experimentConfigsMap)
|
||||
.filter((config) => config[0] !== "DIFF_STRATEGY")
|
||||
|
|
|
|||
80
webview-ui/src/components/settings/TemperatureControl.tsx
Normal file
80
webview-ui/src/components/settings/TemperatureControl.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
interface TemperatureControlProps {
|
||||
value: number | undefined
|
||||
onChange: (value: number | undefined) => void
|
||||
maxValue?: number // Some providers like OpenAI use 0-2 range
|
||||
}
|
||||
|
||||
export const TemperatureControl = ({ value, onChange, maxValue = 1 }: TemperatureControlProps) => {
|
||||
const [isCustomTemperature, setIsCustomTemperature] = useState(value !== undefined)
|
||||
const [inputValue, setInputValue] = useState(value?.toString() ?? "0")
|
||||
|
||||
// Sync internal state with prop changes when switching profiles
|
||||
useEffect(() => {
|
||||
const hasCustomTemperature = value !== undefined
|
||||
setIsCustomTemperature(hasCustomTemperature)
|
||||
setInputValue(value?.toString() ?? "0")
|
||||
}, [value])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={isCustomTemperature}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked
|
||||
setIsCustomTemperature(isChecked)
|
||||
if (!isChecked) {
|
||||
onChange(undefined) // Unset the temperature
|
||||
} else if (value !== undefined) {
|
||||
onChange(value) // Use the value from apiConfiguration, if set
|
||||
}
|
||||
}}>
|
||||
<span style={{ fontWeight: "500" }}>Use custom temperature</span>
|
||||
</VSCodeCheckbox>
|
||||
|
||||
<p style={{ fontSize: "12px", marginTop: "5px", color: "var(--vscode-descriptionForeground)" }}>
|
||||
Controls randomness in the model's responses.
|
||||
</p>
|
||||
|
||||
{isCustomTemperature && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 5,
|
||||
marginBottom: 10,
|
||||
paddingLeft: 10,
|
||||
borderLeft: "2px solid var(--vscode-button-background)",
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
|
||||
<input
|
||||
aria-label="Temperature control text input"
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onBlur={(e) => {
|
||||
const newValue = parseFloat(e.target.value)
|
||||
if (!isNaN(newValue) && newValue >= 0 && newValue <= maxValue) {
|
||||
onChange(newValue)
|
||||
setInputValue(newValue.toString())
|
||||
} else {
|
||||
setInputValue(value?.toString() ?? "0") // Reset to last valid value
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
width: "60px",
|
||||
padding: "4px 8px",
|
||||
border: "1px solid var(--vscode-input-border)",
|
||||
background: "var(--vscode-input-background)",
|
||||
color: "var(--vscode-input-foreground)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p style={{ fontSize: "12px", marginTop: "8px", color: "var(--vscode-descriptionForeground)" }}>
|
||||
Higher values make output more random, lower values make it more deterministic.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
import { TemperatureControl } from "../TemperatureControl"
|
||||
|
||||
describe("TemperatureControl", () => {
|
||||
it("renders with default temperature disabled", () => {
|
||||
const onChange = jest.fn()
|
||||
render(<TemperatureControl value={undefined} onChange={onChange} />)
|
||||
|
||||
const checkbox = screen.getByRole("checkbox")
|
||||
expect(checkbox).not.toBeChecked()
|
||||
expect(screen.queryByRole("textbox")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders with custom temperature enabled", () => {
|
||||
const onChange = jest.fn()
|
||||
render(<TemperatureControl value={0.7} onChange={onChange} />)
|
||||
|
||||
const checkbox = screen.getByRole("checkbox")
|
||||
expect(checkbox).toBeChecked()
|
||||
|
||||
const input = screen.getByRole("textbox")
|
||||
expect(input).toBeInTheDocument()
|
||||
expect(input).toHaveValue("0.7")
|
||||
})
|
||||
|
||||
it("updates when checkbox is toggled", () => {
|
||||
const onChange = jest.fn()
|
||||
render(<TemperatureControl value={0.7} onChange={onChange} />)
|
||||
|
||||
const checkbox = screen.getByRole("checkbox")
|
||||
|
||||
// Uncheck - should clear temperature
|
||||
fireEvent.click(checkbox)
|
||||
expect(onChange).toHaveBeenCalledWith(undefined)
|
||||
|
||||
// Check - should restore previous temperature
|
||||
fireEvent.click(checkbox)
|
||||
expect(onChange).toHaveBeenCalledWith(0.7)
|
||||
})
|
||||
|
||||
it("updates temperature when input loses focus", () => {
|
||||
const onChange = jest.fn()
|
||||
render(<TemperatureControl value={0.7} onChange={onChange} />)
|
||||
|
||||
const input = screen.getByRole("textbox")
|
||||
fireEvent.change(input, { target: { value: "0.8" } })
|
||||
fireEvent.blur(input)
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(0.8)
|
||||
})
|
||||
|
||||
it("respects maxValue prop", () => {
|
||||
const onChange = jest.fn()
|
||||
render(<TemperatureControl value={1.5} onChange={onChange} maxValue={2} />)
|
||||
|
||||
const input = screen.getByRole("textbox")
|
||||
|
||||
// Valid value within max
|
||||
fireEvent.change(input, { target: { value: "1.8" } })
|
||||
fireEvent.blur(input)
|
||||
expect(onChange).toHaveBeenCalledWith(1.8)
|
||||
|
||||
// Invalid value above max
|
||||
fireEvent.change(input, { target: { value: "2.5" } })
|
||||
fireEvent.blur(input)
|
||||
expect(input).toHaveValue("1.5") // Should revert to original value
|
||||
expect(onChange).toHaveBeenCalledTimes(1) // Should not call onChange for invalid value
|
||||
})
|
||||
|
||||
it("syncs checkbox state when value prop changes", () => {
|
||||
const onChange = jest.fn()
|
||||
const { rerender } = render(<TemperatureControl value={0.7} onChange={onChange} />)
|
||||
|
||||
// Initially checked
|
||||
const checkbox = screen.getByRole("checkbox")
|
||||
expect(checkbox).toBeChecked()
|
||||
|
||||
// Update to undefined
|
||||
rerender(<TemperatureControl value={undefined} onChange={onChange} />)
|
||||
expect(checkbox).not.toBeChecked()
|
||||
|
||||
// Update back to a value
|
||||
rerender(<TemperatureControl value={0.5} onChange={onChange} />)
|
||||
expect(checkbox).toBeChecked()
|
||||
})
|
||||
})
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
import { ApiConfiguration, glamaDefaultModelId, openRouterDefaultModelId, unboundDefaultModelId } from "../../../src/shared/api"
|
||||
import {
|
||||
ApiConfiguration,
|
||||
glamaDefaultModelId,
|
||||
openRouterDefaultModelId,
|
||||
unboundDefaultModelId,
|
||||
} from "../../../src/shared/api"
|
||||
import { ModelInfo } from "../../../src/shared/api"
|
||||
export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): string | undefined {
|
||||
if (apiConfiguration) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue