mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Enabled reasoning in Roo provider (#8874)
This commit is contained in:
parent
3cbdbc2af6
commit
b72d55ef45
28 changed files with 1223 additions and 9 deletions
|
|
@ -65,6 +65,7 @@ export const modelInfoSchema = z.object({
|
|||
supportsTemperature: z.boolean().optional(),
|
||||
requiredReasoningBudget: z.boolean().optional(),
|
||||
supportsReasoningEffort: z.boolean().optional(),
|
||||
requiredReasoningEffort: z.boolean().optional(),
|
||||
supportedParameters: z.array(modelParametersSchema).optional(),
|
||||
inputPrice: z.number().optional(),
|
||||
outputPrice: z.number().optional(),
|
||||
|
|
|
|||
|
|
@ -86,6 +86,28 @@ vitest.mock("../../../i18n", () => ({
|
|||
}),
|
||||
}))
|
||||
|
||||
// Mock model cache
|
||||
vitest.mock("../../providers/fetchers/modelCache", () => ({
|
||||
getModels: vitest.fn(),
|
||||
flushModels: vitest.fn(),
|
||||
getModelsFromCache: vitest.fn((provider: string) => {
|
||||
if (provider === "roo") {
|
||||
return {
|
||||
"xai/grok-code-fast-1": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
supportsReasoningEffort: true, // Enable reasoning for tests
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}),
|
||||
}))
|
||||
|
||||
// Import after mocks are set up
|
||||
import { RooHandler } from "../roo"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
|
|
@ -446,4 +468,132 @@ describe("RooHandler", () => {
|
|||
expect(handler).toBeInstanceOf(RooHandler)
|
||||
})
|
||||
})
|
||||
|
||||
describe("reasoning effort support", () => {
|
||||
it("should include reasoning with enabled: false when not enabled", async () => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: mockOptions.apiModelId,
|
||||
messages: expect.any(Array),
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
reasoning: { enabled: false },
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should include reasoning with enabled: false when explicitly disabled", async () => {
|
||||
handler = new RooHandler({
|
||||
...mockOptions,
|
||||
enableReasoningEffort: false,
|
||||
})
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reasoning: { enabled: false },
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should include reasoning with enabled: true and effort: low", async () => {
|
||||
handler = new RooHandler({
|
||||
...mockOptions,
|
||||
reasoningEffort: "low",
|
||||
})
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reasoning: { enabled: true, effort: "low" },
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should include reasoning with enabled: true and effort: medium", async () => {
|
||||
handler = new RooHandler({
|
||||
...mockOptions,
|
||||
reasoningEffort: "medium",
|
||||
})
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reasoning: { enabled: true, effort: "medium" },
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should include reasoning with enabled: true and effort: high", async () => {
|
||||
handler = new RooHandler({
|
||||
...mockOptions,
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reasoning: { enabled: true, effort: "high" },
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should not include reasoning for minimal (treated as none)", async () => {
|
||||
handler = new RooHandler({
|
||||
...mockOptions,
|
||||
reasoningEffort: "minimal",
|
||||
})
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
// minimal should result in no reasoning parameter
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs.reasoning).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should handle enableReasoningEffort: false overriding reasoningEffort setting", async () => {
|
||||
handler = new RooHandler({
|
||||
...mockOptions,
|
||||
enableReasoningEffort: false,
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// Consume stream
|
||||
}
|
||||
|
||||
// When explicitly disabled, should send enabled: false regardless of effort setting
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reasoning: { enabled: false },
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
477
src/api/providers/fetchers/__tests__/roo.spec.ts
Normal file
477
src/api/providers/fetchers/__tests__/roo.spec.ts
Normal file
|
|
@ -0,0 +1,477 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
||||
import { getRooModels } from "../roo"
|
||||
|
||||
// Mock fetch globally
|
||||
const mockFetch = vi.fn()
|
||||
global.fetch = mockFetch as any
|
||||
|
||||
describe("getRooModels", () => {
|
||||
const baseUrl = "https://api.roocode.com/proxy"
|
||||
const apiKey = "test-api-key"
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it("should fetch and parse models successfully", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "xai/grok-code-fast-1",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "xai",
|
||||
name: "Grok Code Fast 1",
|
||||
description: "Fast coding model",
|
||||
context_window: 262144,
|
||||
max_tokens: 16384,
|
||||
type: "language",
|
||||
tags: ["vision", "reasoning"],
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
input_cache_read: "0.00005",
|
||||
input_cache_write: "0.0001",
|
||||
},
|
||||
deprecated: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"https://api.roocode.com/proxy/v1/models",
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
expect(models).toEqual({
|
||||
"xai/grok-code-fast-1": {
|
||||
maxTokens: 16384,
|
||||
contextWindow: 262144,
|
||||
supportsImages: true,
|
||||
supportsReasoningEffort: true,
|
||||
requiredReasoningEffort: false,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 100, // 0.0001 * 1_000_000
|
||||
outputPrice: 200, // 0.0002 * 1_000_000
|
||||
cacheWritesPrice: 100, // 0.0001 * 1_000_000
|
||||
cacheReadsPrice: 50, // 0.00005 * 1_000_000
|
||||
description: "Fast coding model",
|
||||
deprecated: false,
|
||||
isFree: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle reasoning-required tag", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/reasoning-required-model",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Reasoning Required Model",
|
||||
description: "Model that requires reasoning",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
tags: ["reasoning", "reasoning-required"],
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(models["test/reasoning-required-model"]).toEqual({
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsReasoningEffort: true,
|
||||
requiredReasoningEffort: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 100, // 0.0001 * 1_000_000
|
||||
outputPrice: 200, // 0.0002 * 1_000_000
|
||||
cacheWritesPrice: undefined,
|
||||
cacheReadsPrice: undefined,
|
||||
description: "Model that requires reasoning",
|
||||
deprecated: false,
|
||||
isFree: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle models without required_reasoning_effort field", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/normal-model",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Normal Model",
|
||||
description: "Normal model without reasoning",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(models["test/normal-model"]).toEqual({
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsReasoningEffort: false,
|
||||
requiredReasoningEffort: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 100, // 0.0001 * 1_000_000
|
||||
outputPrice: 200, // 0.0002 * 1_000_000
|
||||
cacheWritesPrice: undefined,
|
||||
cacheReadsPrice: undefined,
|
||||
description: "Normal model without reasoning",
|
||||
deprecated: false,
|
||||
isFree: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("should work without API key", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/public-model",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Public Model",
|
||||
description: "Public model",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl)
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"https://api.roocode.com/proxy/v1/models",
|
||||
expect.objectContaining({
|
||||
headers: expect.not.objectContaining({
|
||||
Authorization: expect.anything(),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
expect(models["test/public-model"]).toBeDefined()
|
||||
})
|
||||
|
||||
it("should handle HTTP errors", async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 401,
|
||||
statusText: "Unauthorized",
|
||||
})
|
||||
|
||||
await expect(getRooModels(baseUrl, apiKey)).rejects.toThrow(
|
||||
"Failed to fetch Roo Code Cloud models: HTTP 401: Unauthorized",
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle timeout", async () => {
|
||||
const abortError = new Error("AbortError")
|
||||
abortError.name = "AbortError"
|
||||
|
||||
mockFetch.mockRejectedValueOnce(abortError)
|
||||
|
||||
await expect(getRooModels(baseUrl, apiKey)).rejects.toThrow(
|
||||
"Failed to fetch Roo Code Cloud models: Request timed out",
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle invalid response format", async () => {
|
||||
const invalidResponse = {
|
||||
invalid: "data",
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => invalidResponse,
|
||||
})
|
||||
|
||||
await expect(getRooModels(baseUrl, apiKey)).rejects.toThrow(
|
||||
"Failed to fetch Roo Code Cloud models: Unexpected response format",
|
||||
)
|
||||
})
|
||||
|
||||
it("should normalize base URL correctly", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
await getRooModels("https://api.roocode.com/proxy/v1", apiKey)
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith("https://api.roocode.com/proxy/v1/models", expect.any(Object))
|
||||
})
|
||||
|
||||
it("should handle deprecated models", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/deprecated-model",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Deprecated Model",
|
||||
description: "Old model",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
},
|
||||
deprecated: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(models["test/deprecated-model"].deprecated).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect vision support from tags", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/vision-model",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Vision Model",
|
||||
description: "Model with vision",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
tags: ["vision"],
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(models["test/vision-model"].supportsImages).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect reasoning support from tags", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/reasoning-model",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Reasoning Model",
|
||||
description: "Model with reasoning",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
tags: ["reasoning"],
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(models["test/reasoning-model"].supportsReasoningEffort).toBe(true)
|
||||
})
|
||||
|
||||
it("should handle models with cache pricing", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/cache-model",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Cache Model",
|
||||
description: "Model with cache",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
input_cache_read: "0.00005",
|
||||
input_cache_write: "0.0001",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(models["test/cache-model"].supportsPromptCache).toBe(true)
|
||||
expect(models["test/cache-model"].cacheReadsPrice).toBe(50) // 0.00005 * 1_000_000
|
||||
expect(models["test/cache-model"].cacheWritesPrice).toBe(100) // 0.0001 * 1_000_000
|
||||
})
|
||||
|
||||
it("should skip models without ID", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Invalid Model",
|
||||
description: "Model without ID",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(Object.keys(models)).toHaveLength(0)
|
||||
})
|
||||
|
||||
it("should use model name as description fallback", async () => {
|
||||
const mockResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{
|
||||
id: "test/no-description",
|
||||
object: "model",
|
||||
created: 1234567890,
|
||||
owned_by: "test",
|
||||
name: "Model Name",
|
||||
description: "",
|
||||
context_window: 128000,
|
||||
max_tokens: 8192,
|
||||
type: "language",
|
||||
pricing: {
|
||||
input: "0.0001",
|
||||
output: "0.0002",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
})
|
||||
|
||||
const models = await getRooModels(baseUrl, apiKey)
|
||||
|
||||
expect(models["test/no-description"].description).toBe("Model Name")
|
||||
})
|
||||
|
||||
it("should handle network errors", async () => {
|
||||
mockFetch.mockRejectedValueOnce(new TypeError("Network error"))
|
||||
|
||||
await expect(getRooModels(baseUrl, apiKey)).rejects.toThrow(
|
||||
"Failed to fetch Roo Code Cloud models: No response from server",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -71,6 +71,12 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
|
|||
// Determine if the model supports images based on tags
|
||||
const supportsImages = tags.includes("vision")
|
||||
|
||||
// Determine if the model supports reasoning effort based on tags
|
||||
const supportsReasoningEffort = tags.includes("reasoning")
|
||||
|
||||
// Determine if the model requires reasoning effort based on tags
|
||||
const requiredReasoningEffort = tags.includes("reasoning-required")
|
||||
|
||||
// Parse pricing (API returns strings, convert to numbers)
|
||||
const inputPrice = parseApiPrice(pricing.input)
|
||||
const outputPrice = parseApiPrice(pricing.output)
|
||||
|
|
@ -81,6 +87,8 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
|
|||
maxTokens,
|
||||
contextWindow,
|
||||
supportsImages,
|
||||
supportsReasoningEffort,
|
||||
requiredReasoningEffort,
|
||||
supportsPromptCache: Boolean(cacheReadPrice !== undefined),
|
||||
inputPrice,
|
||||
outputPrice,
|
||||
|
|
|
|||
|
|
@ -6,11 +6,16 @@ import { CloudService } from "@roo-code/cloud"
|
|||
|
||||
import type { ApiHandlerOptions, ModelRecord } from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { getModelParams } from "../transform/model-params"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import type { RooReasoningParams } from "../transform/reasoning"
|
||||
import { getRooReasoning } from "../transform/reasoning"
|
||||
|
||||
import type { ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { DEFAULT_HEADERS } from "./constants"
|
||||
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
|
||||
import { getModels, flushModels, getModelsFromCache } from "../providers/fetchers/modelCache"
|
||||
import { handleOpenAIError } from "./utils/openai-error-handler"
|
||||
|
||||
// Extend OpenAI's CompletionUsage to include Roo specific fields
|
||||
interface RooUsage extends OpenAI.CompletionUsage {
|
||||
|
|
@ -18,6 +23,11 @@ interface RooUsage extends OpenAI.CompletionUsage {
|
|||
cost?: number
|
||||
}
|
||||
|
||||
// Add custom interface for Roo params to support reasoning
|
||||
type RooChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParamsStreaming & {
|
||||
reasoning?: RooReasoningParams
|
||||
}
|
||||
|
||||
export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
||||
private authStateListener?: (state: { state: AuthState }) => void
|
||||
private fetcherBaseURL: string
|
||||
|
|
@ -78,6 +88,51 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
}
|
||||
}
|
||||
|
||||
protected override createStream(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
requestOptions?: OpenAI.RequestOptions,
|
||||
) {
|
||||
const { id: model, info } = this.getModel()
|
||||
|
||||
// Get model parameters including reasoning
|
||||
const params = getModelParams({
|
||||
format: "openai",
|
||||
modelId: model,
|
||||
model: info,
|
||||
settings: this.options,
|
||||
defaultTemperature: this.defaultTemperature,
|
||||
})
|
||||
|
||||
// Get Roo-specific reasoning parameters
|
||||
const reasoning = getRooReasoning({
|
||||
model: info,
|
||||
reasoningBudget: params.reasoningBudget,
|
||||
reasoningEffort: params.reasoningEffort,
|
||||
settings: this.options,
|
||||
})
|
||||
|
||||
const max_tokens = params.maxTokens ?? undefined
|
||||
const temperature = params.temperature ?? this.defaultTemperature
|
||||
|
||||
const rooParams: RooChatCompletionParams = {
|
||||
model,
|
||||
max_tokens,
|
||||
temperature,
|
||||
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
...(reasoning && { reasoning }),
|
||||
}
|
||||
|
||||
try {
|
||||
return this.client.chat.completions.create(rooParams, requestOptions)
|
||||
} catch (error) {
|
||||
throw handleOpenAIError(error, this.providerName)
|
||||
}
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
|
|
@ -96,19 +151,28 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
const delta = chunk.choices[0]?.delta
|
||||
|
||||
if (delta) {
|
||||
if (delta.content) {
|
||||
// Check for reasoning content (similar to OpenRouter)
|
||||
if ("reasoning" in delta && delta.reasoning && typeof delta.reasoning === "string") {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
type: "reasoning",
|
||||
text: delta.reasoning,
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for reasoning_content for backward compatibility
|
||||
if ("reasoning_content" in delta && typeof delta.reasoning_content === "string") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
text: delta.reasoning_content,
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
|
|
@ -163,6 +227,7 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
maxTokens: 16_384,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
supportsReasoningEffort: false,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ import {
|
|||
getOpenRouterReasoning,
|
||||
getAnthropicReasoning,
|
||||
getOpenAiReasoning,
|
||||
getRooReasoning,
|
||||
GetModelReasoningOptions,
|
||||
OpenRouterReasoningParams,
|
||||
AnthropicReasoningParams,
|
||||
OpenAiReasoningParams,
|
||||
RooReasoningParams,
|
||||
} from "../reasoning"
|
||||
|
||||
describe("reasoning.ts", () => {
|
||||
|
|
@ -761,4 +763,133 @@ describe("reasoning.ts", () => {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("getRooReasoning", () => {
|
||||
it("should return undefined when model does not support reasoning effort", () => {
|
||||
const options = { ...baseOptions }
|
||||
const result = getRooReasoning(options)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should return enabled: false when enableReasoningEffort is explicitly false", () => {
|
||||
const modelWithSupported: ModelInfo = {
|
||||
...baseModel,
|
||||
supportsReasoningEffort: true,
|
||||
}
|
||||
|
||||
const settingsWithDisabled: ProviderSettings = {
|
||||
enableReasoningEffort: false,
|
||||
}
|
||||
|
||||
const options = {
|
||||
...baseOptions,
|
||||
model: modelWithSupported,
|
||||
settings: settingsWithDisabled,
|
||||
}
|
||||
|
||||
const result = getRooReasoning(options)
|
||||
expect(result).toEqual({ enabled: false })
|
||||
})
|
||||
|
||||
it("should return enabled: true with effort when reasoningEffort is provided", () => {
|
||||
const modelWithSupported: ModelInfo = {
|
||||
...baseModel,
|
||||
supportsReasoningEffort: true,
|
||||
}
|
||||
|
||||
const settingsWithEffort: ProviderSettings = {
|
||||
reasoningEffort: "high",
|
||||
}
|
||||
|
||||
const options = {
|
||||
...baseOptions,
|
||||
model: modelWithSupported,
|
||||
settings: settingsWithEffort,
|
||||
reasoningEffort: "high" as const,
|
||||
}
|
||||
|
||||
const result = getRooReasoning(options)
|
||||
expect(result).toEqual({ enabled: true, effort: "high" })
|
||||
})
|
||||
|
||||
it("should return enabled: false when reasoningEffort is undefined (None selected)", () => {
|
||||
const modelWithSupported: ModelInfo = {
|
||||
...baseModel,
|
||||
supportsReasoningEffort: true,
|
||||
}
|
||||
|
||||
const options = {
|
||||
...baseOptions,
|
||||
model: modelWithSupported,
|
||||
settings: {},
|
||||
reasoningEffort: undefined,
|
||||
}
|
||||
|
||||
const result = getRooReasoning(options)
|
||||
expect(result).toEqual({ enabled: false })
|
||||
})
|
||||
|
||||
it("should not return reasoning params for minimal effort", () => {
|
||||
const modelWithSupported: ModelInfo = {
|
||||
...baseModel,
|
||||
supportsReasoningEffort: true,
|
||||
}
|
||||
|
||||
const settingsWithMinimal: ProviderSettings = {
|
||||
reasoningEffort: "minimal",
|
||||
}
|
||||
|
||||
const options = {
|
||||
...baseOptions,
|
||||
model: modelWithSupported,
|
||||
settings: settingsWithMinimal,
|
||||
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
|
||||
}
|
||||
|
||||
const result = getRooReasoning(options)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should handle all valid reasoning effort values", () => {
|
||||
const efforts: Array<"low" | "medium" | "high"> = ["low", "medium", "high"]
|
||||
|
||||
efforts.forEach((effort) => {
|
||||
const modelWithSupported: ModelInfo = {
|
||||
...baseModel,
|
||||
supportsReasoningEffort: true,
|
||||
}
|
||||
|
||||
const settingsWithEffort: ProviderSettings = {
|
||||
reasoningEffort: effort,
|
||||
}
|
||||
|
||||
const options = {
|
||||
...baseOptions,
|
||||
model: modelWithSupported,
|
||||
settings: settingsWithEffort,
|
||||
reasoningEffort: effort,
|
||||
}
|
||||
|
||||
const result = getRooReasoning(options)
|
||||
expect(result).toEqual({ enabled: true, effort })
|
||||
})
|
||||
})
|
||||
|
||||
it("should return enabled: false when model supports reasoning but no effort is provided", () => {
|
||||
const modelWithSupported: ModelInfo = {
|
||||
...baseModel,
|
||||
supportsReasoningEffort: true,
|
||||
}
|
||||
|
||||
const options = {
|
||||
...baseOptions,
|
||||
model: modelWithSupported,
|
||||
settings: {},
|
||||
reasoningEffort: undefined,
|
||||
}
|
||||
|
||||
const result = getRooReasoning(options)
|
||||
expect(result).toEqual({ enabled: false })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ export type OpenRouterReasoningParams = {
|
|||
exclude?: boolean
|
||||
}
|
||||
|
||||
export type RooReasoningParams = {
|
||||
enabled?: boolean
|
||||
effort?: ReasoningEffortWithMinimal
|
||||
}
|
||||
|
||||
export type AnthropicReasoningParams = BetaThinkingConfigParam
|
||||
|
||||
export type OpenAiReasoningParams = { reasoning_effort: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"] }
|
||||
|
|
@ -39,6 +44,36 @@ export const getOpenRouterReasoning = ({
|
|||
: undefined
|
||||
: undefined
|
||||
|
||||
export const getRooReasoning = ({
|
||||
model,
|
||||
reasoningEffort,
|
||||
settings,
|
||||
}: GetModelReasoningOptions): RooReasoningParams | undefined => {
|
||||
// Check if model supports reasoning effort
|
||||
if (!model.supportsReasoningEffort) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// If enableReasoningEffort is explicitly false, return enabled: false
|
||||
if (settings.enableReasoningEffort === false) {
|
||||
return { enabled: false }
|
||||
}
|
||||
|
||||
// If reasoning effort is provided, return it with enabled: true
|
||||
if (reasoningEffort && reasoningEffort !== "minimal") {
|
||||
return { enabled: true, effort: reasoningEffort }
|
||||
}
|
||||
|
||||
// If reasoningEffort is explicitly undefined (None selected), disable reasoning
|
||||
// This ensures we explicitly tell the backend not to use reasoning
|
||||
if (reasoningEffort === undefined) {
|
||||
return { enabled: false }
|
||||
}
|
||||
|
||||
// Default: no reasoning parameter (reasoning not enabled)
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const getAnthropicReasoning = ({
|
||||
model,
|
||||
reasoningBudget,
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ import { inputEventTransform, noTransform } from "./transforms"
|
|||
import { ModelInfoView } from "./ModelInfoView"
|
||||
import { ApiErrorMessage } from "./ApiErrorMessage"
|
||||
import { ThinkingBudget } from "./ThinkingBudget"
|
||||
import { SimpleThinkingBudget } from "./SimpleThinkingBudget"
|
||||
import { Verbosity } from "./Verbosity"
|
||||
import { DiffSettingsControl } from "./DiffSettingsControl"
|
||||
import { TodoListSettingsControl } from "./TodoListSettingsControl"
|
||||
|
|
@ -747,12 +748,21 @@ const ApiOptions = ({
|
|||
</>
|
||||
)}
|
||||
|
||||
<ThinkingBudget
|
||||
key={`${selectedProvider}-${selectedModelId}`}
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
modelInfo={selectedModelInfo}
|
||||
/>
|
||||
{selectedProvider === "roo" ? (
|
||||
<SimpleThinkingBudget
|
||||
key={`${selectedProvider}-${selectedModelId}`}
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
modelInfo={selectedModelInfo}
|
||||
/>
|
||||
) : (
|
||||
<ThinkingBudget
|
||||
key={`${selectedProvider}-${selectedModelId}`}
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
modelInfo={selectedModelInfo}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Gate Verbosity UI by capability flag */}
|
||||
{selectedModelInfo?.supportsVerbosity && (
|
||||
|
|
|
|||
107
webview-ui/src/components/settings/SimpleThinkingBudget.tsx
Normal file
107
webview-ui/src/components/settings/SimpleThinkingBudget.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { useEffect } from "react"
|
||||
|
||||
import { type ProviderSettings, type ModelInfo, type ReasoningEffort, reasoningEfforts } from "@roo-code/types"
|
||||
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui"
|
||||
|
||||
interface SimpleThinkingBudgetProps {
|
||||
apiConfiguration: ProviderSettings
|
||||
setApiConfigurationField: <K extends keyof ProviderSettings>(
|
||||
field: K,
|
||||
value: ProviderSettings[K],
|
||||
isUserAction?: boolean,
|
||||
) => void
|
||||
modelInfo?: ModelInfo
|
||||
}
|
||||
|
||||
// Extended type to include "none" option
|
||||
type ReasoningEffortWithNone = ReasoningEffort | "none"
|
||||
|
||||
export const SimpleThinkingBudget = ({
|
||||
apiConfiguration,
|
||||
setApiConfigurationField,
|
||||
modelInfo,
|
||||
}: SimpleThinkingBudgetProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
// Check model capabilities
|
||||
const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort
|
||||
const isReasoningEffortRequired = !!modelInfo && modelInfo.requiredReasoningEffort
|
||||
|
||||
// Build available reasoning efforts list
|
||||
// Include "none" option unless reasoning effort is required
|
||||
const baseEfforts = [...reasoningEfforts] as ReasoningEffort[]
|
||||
const availableReasoningEfforts: ReadonlyArray<ReasoningEffortWithNone> = isReasoningEffortRequired
|
||||
? baseEfforts
|
||||
: (["none", ...baseEfforts] as ReasoningEffortWithNone[])
|
||||
|
||||
// Default reasoning effort - use model's default if available, otherwise "medium"
|
||||
const modelDefaultReasoningEffort = modelInfo?.reasoningEffort as ReasoningEffort | undefined
|
||||
const defaultReasoningEffort: ReasoningEffortWithNone = isReasoningEffortRequired
|
||||
? modelDefaultReasoningEffort || "medium"
|
||||
: "none"
|
||||
|
||||
// Current reasoning effort - treat undefined/null as "none"
|
||||
const currentReasoningEffort: ReasoningEffortWithNone =
|
||||
(apiConfiguration.reasoningEffort as ReasoningEffort | undefined) || defaultReasoningEffort
|
||||
|
||||
// Set default reasoning effort when model supports it and no value is set
|
||||
useEffect(() => {
|
||||
if (isReasoningEffortSupported && !apiConfiguration.reasoningEffort) {
|
||||
// Only set a default if reasoning is required, otherwise leave as undefined (which maps to "none")
|
||||
if (isReasoningEffortRequired && defaultReasoningEffort !== "none") {
|
||||
setApiConfigurationField("reasoningEffort", defaultReasoningEffort as ReasoningEffort, false)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
isReasoningEffortSupported,
|
||||
isReasoningEffortRequired,
|
||||
apiConfiguration.reasoningEffort,
|
||||
defaultReasoningEffort,
|
||||
setApiConfigurationField,
|
||||
])
|
||||
|
||||
if (!modelInfo || !isReasoningEffortSupported) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1" data-testid="simple-reasoning-effort">
|
||||
<div className="flex justify-between items-center">
|
||||
<label className="block font-medium mb-1">{t("settings:providers.reasoningEffort.label")}</label>
|
||||
</div>
|
||||
<Select
|
||||
value={currentReasoningEffort}
|
||||
onValueChange={(value: ReasoningEffortWithNone) => {
|
||||
// If "none" is selected, clear the reasoningEffort field
|
||||
if (value === "none") {
|
||||
setApiConfigurationField("reasoningEffort", undefined)
|
||||
} else {
|
||||
setApiConfigurationField("reasoningEffort", value as ReasoningEffort)
|
||||
}
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
currentReasoningEffort
|
||||
? currentReasoningEffort === "none"
|
||||
? t("settings:providers.reasoningEffort.none")
|
||||
: t(`settings:providers.reasoningEffort.${currentReasoningEffort}`)
|
||||
: t("settings:common.select")
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableReasoningEfforts.map((value) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{value === "none"
|
||||
? t("settings:providers.reasoningEffort.none")
|
||||
: t(`settings:providers.reasoningEffort.${value}`)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest"
|
||||
import { render, screen } from "@testing-library/react"
|
||||
import { SimpleThinkingBudget } from "../SimpleThinkingBudget"
|
||||
import type { ProviderSettings, ModelInfo } from "@roo-code/types"
|
||||
|
||||
// Mock the translation hook
|
||||
vi.mock("@src/i18n/TranslationContext", () => ({
|
||||
useAppTranslation: () => ({
|
||||
t: (key: string) => {
|
||||
const translations: Record<string, string> = {
|
||||
"settings:providers.reasoningEffort.label": "Model Reasoning Effort",
|
||||
"settings:providers.reasoningEffort.none": "None",
|
||||
"settings:providers.reasoningEffort.low": "Low",
|
||||
"settings:providers.reasoningEffort.medium": "Medium",
|
||||
"settings:providers.reasoningEffort.high": "High",
|
||||
"settings:common.select": "Select",
|
||||
}
|
||||
return translations[key] || key
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock the useSelectedModel hook
|
||||
vi.mock("@src/components/ui/hooks/useSelectedModel", () => ({
|
||||
useSelectedModel: () => ({ id: "test-model" }),
|
||||
}))
|
||||
|
||||
describe("SimpleThinkingBudget", () => {
|
||||
const mockSetApiConfigurationField = vi.fn()
|
||||
|
||||
const baseApiConfiguration: ProviderSettings = {
|
||||
apiProvider: "roo",
|
||||
}
|
||||
|
||||
const modelWithReasoningEffort: ModelInfo = {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: true,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should not render when model does not support reasoning effort", () => {
|
||||
const modelWithoutReasoningEffort: ModelInfo = {
|
||||
...modelWithReasoningEffort,
|
||||
supportsReasoningEffort: false,
|
||||
}
|
||||
|
||||
const { container } = render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={baseApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={modelWithoutReasoningEffort}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
|
||||
it("should not render when modelInfo is undefined", () => {
|
||||
const { container } = render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={baseApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={undefined}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
|
||||
it("should render with None option when reasoning effort is not required", () => {
|
||||
render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={baseApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={modelWithReasoningEffort}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId("simple-reasoning-effort")).toBeInTheDocument()
|
||||
expect(screen.getByText("Model Reasoning Effort")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should not render None option when reasoning effort is required", () => {
|
||||
const modelWithRequiredReasoningEffort: ModelInfo = {
|
||||
...modelWithReasoningEffort,
|
||||
requiredReasoningEffort: true,
|
||||
}
|
||||
|
||||
render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={baseApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={modelWithRequiredReasoningEffort}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId("simple-reasoning-effort")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should set default reasoning effort when required and no value is set", () => {
|
||||
const modelWithRequiredReasoningEffort: ModelInfo = {
|
||||
...modelWithReasoningEffort,
|
||||
requiredReasoningEffort: true,
|
||||
reasoningEffort: "high",
|
||||
}
|
||||
|
||||
render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={baseApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={modelWithRequiredReasoningEffort}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Should set default reasoning effort
|
||||
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("reasoningEffort", "high", false)
|
||||
})
|
||||
|
||||
it("should not set default reasoning effort when not required", () => {
|
||||
render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={baseApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={modelWithReasoningEffort}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Should not set any default value
|
||||
expect(mockSetApiConfigurationField).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should include None option in available efforts when not required", () => {
|
||||
render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={baseApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={modelWithReasoningEffort}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Component should render with the select
|
||||
expect(screen.getByRole("combobox")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should exclude None option when reasoning effort is required", () => {
|
||||
const modelWithRequiredReasoningEffort: ModelInfo = {
|
||||
...modelWithReasoningEffort,
|
||||
requiredReasoningEffort: true,
|
||||
}
|
||||
|
||||
render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={baseApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={modelWithRequiredReasoningEffort}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Component should render with the select
|
||||
expect(screen.getByRole("combobox")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should display current reasoning effort value", () => {
|
||||
render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={{ ...baseApiConfiguration, reasoningEffort: "low" }}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={modelWithReasoningEffort}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText("Low")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should display None when no reasoning effort is set", () => {
|
||||
render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={baseApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={modelWithReasoningEffort}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText("None")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should use model default reasoning effort when required and available", () => {
|
||||
const modelWithDefaultEffort: ModelInfo = {
|
||||
...modelWithReasoningEffort,
|
||||
requiredReasoningEffort: true,
|
||||
reasoningEffort: "medium",
|
||||
}
|
||||
|
||||
render(
|
||||
<SimpleThinkingBudget
|
||||
apiConfiguration={baseApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
modelInfo={modelWithDefaultEffort}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("reasoningEffort", "medium", false)
|
||||
})
|
||||
})
|
||||
1
webview-ui/src/i18n/locales/ca/settings.json
generated
1
webview-ui/src/i18n/locales/ca/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Esforç de raonament del model",
|
||||
"none": "Cap",
|
||||
"minimal": "Mínim (el més ràpid)",
|
||||
"high": "Alt",
|
||||
"medium": "Mitjà",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/de/settings.json
generated
1
webview-ui/src/i18n/locales/de/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Modell-Denkaufwand",
|
||||
"none": "Keine",
|
||||
"minimal": "Minimal (schnellste)",
|
||||
"high": "Hoch",
|
||||
"medium": "Mittel",
|
||||
|
|
|
|||
|
|
@ -465,6 +465,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Model Reasoning Effort",
|
||||
"none": "None",
|
||||
"minimal": "Minimal (Fastest)",
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/es/settings.json
generated
1
webview-ui/src/i18n/locales/es/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Esfuerzo de razonamiento del modelo",
|
||||
"none": "Ninguno",
|
||||
"minimal": "Mínimo (el más rápido)",
|
||||
"high": "Alto",
|
||||
"medium": "Medio",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/fr/settings.json
generated
1
webview-ui/src/i18n/locales/fr/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Effort de raisonnement du modèle",
|
||||
"none": "Aucun",
|
||||
"minimal": "Minimal (le plus rapide)",
|
||||
"high": "Élevé",
|
||||
"medium": "Moyen",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/hi/settings.json
generated
1
webview-ui/src/i18n/locales/hi/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "मॉडल तर्क प्रयास",
|
||||
"none": "कोई नहीं",
|
||||
"minimal": "न्यूनतम (सबसे तेज़)",
|
||||
"high": "उच्च",
|
||||
"medium": "मध्यम",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/id/settings.json
generated
1
webview-ui/src/i18n/locales/id/settings.json
generated
|
|
@ -464,6 +464,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Upaya Reasoning Model",
|
||||
"none": "Tidak Ada",
|
||||
"minimal": "Minimal (Tercepat)",
|
||||
"high": "Tinggi",
|
||||
"medium": "Sedang",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/it/settings.json
generated
1
webview-ui/src/i18n/locales/it/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Sforzo di ragionamento del modello",
|
||||
"none": "Nessuno",
|
||||
"minimal": "Minimo (più veloce)",
|
||||
"high": "Alto",
|
||||
"medium": "Medio",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ja/settings.json
generated
1
webview-ui/src/i18n/locales/ja/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "モデル推論の労力",
|
||||
"none": "なし",
|
||||
"minimal": "最小 (最速)",
|
||||
"high": "高",
|
||||
"medium": "中",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ko/settings.json
generated
1
webview-ui/src/i18n/locales/ko/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "모델 추론 노력",
|
||||
"none": "없음",
|
||||
"minimal": "최소 (가장 빠름)",
|
||||
"high": "높음",
|
||||
"medium": "중간",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/nl/settings.json
generated
1
webview-ui/src/i18n/locales/nl/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Model redeneervermogen",
|
||||
"none": "Geen",
|
||||
"minimal": "Minimaal (Snelst)",
|
||||
"high": "Hoog",
|
||||
"medium": "Middel",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/pl/settings.json
generated
1
webview-ui/src/i18n/locales/pl/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Wysiłek rozumowania modelu",
|
||||
"none": "Brak",
|
||||
"minimal": "Minimalny (najszybszy)",
|
||||
"high": "Wysoki",
|
||||
"medium": "Średni",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
1
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Esforço de raciocínio do modelo",
|
||||
"none": "Nenhum",
|
||||
"minimal": "Mínimo (mais rápido)",
|
||||
"high": "Alto",
|
||||
"medium": "Médio",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ru/settings.json
generated
1
webview-ui/src/i18n/locales/ru/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Усилия по рассуждению модели",
|
||||
"none": "Нет",
|
||||
"minimal": "Минимальный (самый быстрый)",
|
||||
"high": "Высокие",
|
||||
"medium": "Средние",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/tr/settings.json
generated
1
webview-ui/src/i18n/locales/tr/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Model Akıl Yürütme Çabası",
|
||||
"none": "Yok",
|
||||
"minimal": "Minimal (en hızlı)",
|
||||
"high": "Yüksek",
|
||||
"medium": "Orta",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/vi/settings.json
generated
1
webview-ui/src/i18n/locales/vi/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Nỗ lực suy luận của mô hình",
|
||||
"none": "Không",
|
||||
"minimal": "Tối thiểu (nhanh nhất)",
|
||||
"high": "Cao",
|
||||
"medium": "Trung bình",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
1
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "模型推理强度",
|
||||
"none": "无",
|
||||
"minimal": "最小 (最快)",
|
||||
"high": "高",
|
||||
"medium": "中",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
1
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
|
|
@ -460,6 +460,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "模型推理強度",
|
||||
"none": "無",
|
||||
"minimal": "最小 (最快)",
|
||||
"high": "高",
|
||||
"medium": "中",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue