mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Merge branch 'main' into fix/issue-2379-diagnostics-settings
Resolved conflicts by keeping both diagnostics translations and new includeMaxOutputTokens translations
This commit is contained in:
commit
eb7317c6c0
49 changed files with 1223 additions and 124 deletions
|
|
@ -91,47 +91,6 @@
|
|||
</step>
|
||||
|
||||
<step number="3">
|
||||
<name>Create Implementation Plan</name>
|
||||
<instructions>
|
||||
Based on the issue analysis, create a detailed implementation plan:
|
||||
|
||||
For Bug Fixes:
|
||||
1. Reproduce the bug locally (if possible)
|
||||
2. Identify root cause
|
||||
3. Plan the fix approach. The plan should be focused on resolving the issue with a high-quality, targeted fix, while avoiding unrelated changes.
|
||||
4. Identify files to modify.
|
||||
5. Plan test cases to prevent regression.
|
||||
|
||||
For Feature Implementation:
|
||||
1. Break down the feature into components
|
||||
2. Identify all files that need changes
|
||||
3. Plan the implementation approach
|
||||
4. Consider edge cases and error handling
|
||||
5. Plan test coverage
|
||||
|
||||
Present the plan to the user:
|
||||
|
||||
<ask_followup_question>
|
||||
<question>I've analyzed issue #[number]: "[title]"
|
||||
|
||||
Here's my implementation plan to resolve the issue:
|
||||
|
||||
[Detailed plan with steps and affected files]
|
||||
|
||||
This plan focuses on providing a quality fix for the reported problem without introducing unrelated changes.
|
||||
|
||||
Would you like me to proceed with this implementation?</question>
|
||||
<follow_up>
|
||||
<suggest>Yes, proceed with the implementation</suggest>
|
||||
<suggest>Let me review the issue first</suggest>
|
||||
<suggest>Modify the approach for: [specific aspect]</suggest>
|
||||
<suggest>Focus only on: [specific part]</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="4">
|
||||
<name>Explore Codebase and Related Files</name>
|
||||
<instructions>
|
||||
Use codebase_search FIRST to understand the codebase structure and find ALL related files:
|
||||
|
|
@ -188,6 +147,47 @@
|
|||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="4">
|
||||
<name>Create Implementation Plan</name>
|
||||
<instructions>
|
||||
Based on the issue analysis, create a detailed implementation plan:
|
||||
|
||||
For Bug Fixes:
|
||||
1. Reproduce the bug locally (if possible)
|
||||
2. Identify root cause
|
||||
3. Plan the fix approach. The plan should be focused on resolving the issue with a high-quality, targeted fix, while avoiding unrelated changes.
|
||||
4. Identify files to modify.
|
||||
5. Plan test cases to prevent regression.
|
||||
|
||||
For Feature Implementation:
|
||||
1. Break down the feature into components
|
||||
2. Identify all files that need changes
|
||||
3. Plan the implementation approach
|
||||
4. Consider edge cases and error handling
|
||||
5. Plan test coverage
|
||||
|
||||
Present the plan to the user:
|
||||
|
||||
<ask_followup_question>
|
||||
<question>I've analyzed issue #[number]: "[title]"
|
||||
|
||||
Here's my implementation plan to resolve the issue:
|
||||
|
||||
[Detailed plan with steps and affected files]
|
||||
|
||||
This plan focuses on providing a quality fix for the reported problem without introducing unrelated changes.
|
||||
|
||||
Would you like me to proceed with this implementation?</question>
|
||||
<follow_up>
|
||||
<suggest>Yes, proceed with the implementation</suggest>
|
||||
<suggest>Let me review the issue first</suggest>
|
||||
<suggest>Modify the approach for: [specific aspect]</suggest>
|
||||
<suggest>Focus only on: [specific part]</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="5">
|
||||
<name>Implement the Solution</name>
|
||||
<instructions>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { OpenAiHandler } from "../openai"
|
|||
import { ApiHandlerOptions } from "../../../shared/api"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { openAiModelInfoSaneDefaults } from "@roo-code/types"
|
||||
|
||||
const mockCreate = vitest.fn()
|
||||
|
||||
|
|
@ -197,6 +198,113 @@ describe("OpenAiHandler", () => {
|
|||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs.reasoning_effort).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should include max_tokens when includeMaxTokens is true", async () => {
|
||||
const optionsWithMaxTokens: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
includeMaxTokens: true,
|
||||
openAiCustomModelInfo: {
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 4096,
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
}
|
||||
const handlerWithMaxTokens = new OpenAiHandler(optionsWithMaxTokens)
|
||||
const stream = handlerWithMaxTokens.createMessage(systemPrompt, messages)
|
||||
// Consume the stream to trigger the API call
|
||||
for await (const _chunk of stream) {
|
||||
}
|
||||
// Assert the mockCreate was called with max_tokens
|
||||
expect(mockCreate).toHaveBeenCalled()
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs.max_completion_tokens).toBe(4096)
|
||||
})
|
||||
|
||||
it("should not include max_tokens when includeMaxTokens is false", async () => {
|
||||
const optionsWithoutMaxTokens: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
includeMaxTokens: false,
|
||||
openAiCustomModelInfo: {
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 4096,
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
}
|
||||
const handlerWithoutMaxTokens = new OpenAiHandler(optionsWithoutMaxTokens)
|
||||
const stream = handlerWithoutMaxTokens.createMessage(systemPrompt, messages)
|
||||
// Consume the stream to trigger the API call
|
||||
for await (const _chunk of stream) {
|
||||
}
|
||||
// Assert the mockCreate was called without max_tokens
|
||||
expect(mockCreate).toHaveBeenCalled()
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs.max_completion_tokens).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should not include max_tokens when includeMaxTokens is undefined", async () => {
|
||||
const optionsWithUndefinedMaxTokens: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
// includeMaxTokens is not set, should not include max_tokens
|
||||
openAiCustomModelInfo: {
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 4096,
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
}
|
||||
const handlerWithDefaultMaxTokens = new OpenAiHandler(optionsWithUndefinedMaxTokens)
|
||||
const stream = handlerWithDefaultMaxTokens.createMessage(systemPrompt, messages)
|
||||
// Consume the stream to trigger the API call
|
||||
for await (const _chunk of stream) {
|
||||
}
|
||||
// Assert the mockCreate was called without max_tokens
|
||||
expect(mockCreate).toHaveBeenCalled()
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs.max_completion_tokens).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should use user-configured modelMaxTokens instead of model default maxTokens", async () => {
|
||||
const optionsWithUserMaxTokens: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
includeMaxTokens: true,
|
||||
modelMaxTokens: 32000, // User-configured value
|
||||
openAiCustomModelInfo: {
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 4096, // Model's default value (should not be used)
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
}
|
||||
const handlerWithUserMaxTokens = new OpenAiHandler(optionsWithUserMaxTokens)
|
||||
const stream = handlerWithUserMaxTokens.createMessage(systemPrompt, messages)
|
||||
// Consume the stream to trigger the API call
|
||||
for await (const _chunk of stream) {
|
||||
}
|
||||
// Assert the mockCreate was called with user-configured modelMaxTokens (32000), not model default maxTokens (4096)
|
||||
expect(mockCreate).toHaveBeenCalled()
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs.max_completion_tokens).toBe(32000)
|
||||
})
|
||||
|
||||
it("should fallback to model default maxTokens when user modelMaxTokens is not set", async () => {
|
||||
const optionsWithoutUserMaxTokens: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
includeMaxTokens: true,
|
||||
// modelMaxTokens is not set
|
||||
openAiCustomModelInfo: {
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 4096, // Model's default value (should be used as fallback)
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
}
|
||||
const handlerWithoutUserMaxTokens = new OpenAiHandler(optionsWithoutUserMaxTokens)
|
||||
const stream = handlerWithoutUserMaxTokens.createMessage(systemPrompt, messages)
|
||||
// Consume the stream to trigger the API call
|
||||
for await (const _chunk of stream) {
|
||||
}
|
||||
// Assert the mockCreate was called with model default maxTokens (4096) as fallback
|
||||
expect(mockCreate).toHaveBeenCalled()
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs.max_completion_tokens).toBe(4096)
|
||||
})
|
||||
})
|
||||
|
||||
describe("error handling", () => {
|
||||
|
|
@ -336,6 +444,10 @@ describe("OpenAiHandler", () => {
|
|||
},
|
||||
{ path: "/models/chat/completions" },
|
||||
)
|
||||
|
||||
// Verify max_tokens is NOT included when includeMaxTokens is not set
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs).not.toHaveProperty("max_completion_tokens")
|
||||
})
|
||||
|
||||
it("should handle non-streaming responses with Azure AI Inference Service", async () => {
|
||||
|
|
@ -378,6 +490,10 @@ describe("OpenAiHandler", () => {
|
|||
},
|
||||
{ path: "/models/chat/completions" },
|
||||
)
|
||||
|
||||
// Verify max_tokens is NOT included when includeMaxTokens is not set
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs).not.toHaveProperty("max_completion_tokens")
|
||||
})
|
||||
|
||||
it("should handle completePrompt with Azure AI Inference Service", async () => {
|
||||
|
|
@ -391,6 +507,10 @@ describe("OpenAiHandler", () => {
|
|||
},
|
||||
{ path: "/models/chat/completions" },
|
||||
)
|
||||
|
||||
// Verify max_tokens is NOT included when includeMaxTokens is not set
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs).not.toHaveProperty("max_completion_tokens")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -433,4 +553,225 @@ describe("OpenAiHandler", () => {
|
|||
expect(lastCall[0]).not.toHaveProperty("stream_options")
|
||||
})
|
||||
})
|
||||
|
||||
describe("O3 Family Models", () => {
|
||||
const o3Options = {
|
||||
...mockOptions,
|
||||
openAiModelId: "o3-mini",
|
||||
openAiCustomModelInfo: {
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 65536,
|
||||
supportsPromptCache: false,
|
||||
reasoningEffort: "medium" as "low" | "medium" | "high",
|
||||
},
|
||||
}
|
||||
|
||||
it("should handle O3 model with streaming and include max_completion_tokens when includeMaxTokens is true", async () => {
|
||||
const o3Handler = new OpenAiHandler({
|
||||
...o3Options,
|
||||
includeMaxTokens: true,
|
||||
modelMaxTokens: 32000,
|
||||
modelTemperature: 0.5,
|
||||
})
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello!",
|
||||
},
|
||||
]
|
||||
|
||||
const stream = o3Handler.createMessage(systemPrompt, messages)
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "o3-mini",
|
||||
messages: [
|
||||
{
|
||||
role: "developer",
|
||||
content: "Formatting re-enabled\nYou are a helpful assistant.",
|
||||
},
|
||||
{ role: "user", content: "Hello!" },
|
||||
],
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
reasoning_effort: "medium",
|
||||
temperature: 0.5,
|
||||
// O3 models do not support deprecated max_tokens but do support max_completion_tokens
|
||||
max_completion_tokens: 32000,
|
||||
}),
|
||||
{},
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle O3 model with streaming and exclude max_tokens when includeMaxTokens is false", async () => {
|
||||
const o3Handler = new OpenAiHandler({
|
||||
...o3Options,
|
||||
includeMaxTokens: false,
|
||||
modelTemperature: 0.7,
|
||||
})
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello!",
|
||||
},
|
||||
]
|
||||
|
||||
const stream = o3Handler.createMessage(systemPrompt, messages)
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "o3-mini",
|
||||
messages: [
|
||||
{
|
||||
role: "developer",
|
||||
content: "Formatting re-enabled\nYou are a helpful assistant.",
|
||||
},
|
||||
{ role: "user", content: "Hello!" },
|
||||
],
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
reasoning_effort: "medium",
|
||||
temperature: 0.7,
|
||||
}),
|
||||
{},
|
||||
)
|
||||
|
||||
// Verify max_tokens is NOT included
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs).not.toHaveProperty("max_completion_tokens")
|
||||
})
|
||||
|
||||
it("should handle O3 model non-streaming with reasoning_effort and max_completion_tokens when includeMaxTokens is true", async () => {
|
||||
const o3Handler = new OpenAiHandler({
|
||||
...o3Options,
|
||||
openAiStreamingEnabled: false,
|
||||
includeMaxTokens: true,
|
||||
modelTemperature: 0.3,
|
||||
})
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello!",
|
||||
},
|
||||
]
|
||||
|
||||
const stream = o3Handler.createMessage(systemPrompt, messages)
|
||||
const chunks: any[] = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "o3-mini",
|
||||
messages: [
|
||||
{
|
||||
role: "developer",
|
||||
content: "Formatting re-enabled\nYou are a helpful assistant.",
|
||||
},
|
||||
{ role: "user", content: "Hello!" },
|
||||
],
|
||||
reasoning_effort: "medium",
|
||||
temperature: 0.3,
|
||||
// O3 models do not support deprecated max_tokens but do support max_completion_tokens
|
||||
max_completion_tokens: 65536, // Using default maxTokens from o3Options
|
||||
}),
|
||||
{},
|
||||
)
|
||||
|
||||
// Verify stream is not set
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs).not.toHaveProperty("stream")
|
||||
})
|
||||
|
||||
it("should use default temperature of 0 when not specified for O3 models", async () => {
|
||||
const o3Handler = new OpenAiHandler({
|
||||
...o3Options,
|
||||
// No modelTemperature specified
|
||||
})
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello!",
|
||||
},
|
||||
]
|
||||
|
||||
const stream = o3Handler.createMessage(systemPrompt, messages)
|
||||
await stream.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
temperature: 0, // Default temperature
|
||||
}),
|
||||
{},
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle O3 model with Azure AI Inference Service respecting includeMaxTokens", async () => {
|
||||
const o3AzureHandler = new OpenAiHandler({
|
||||
...o3Options,
|
||||
openAiBaseUrl: "https://test.services.ai.azure.com",
|
||||
includeMaxTokens: false, // Should NOT include max_tokens
|
||||
})
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello!",
|
||||
},
|
||||
]
|
||||
|
||||
const stream = o3AzureHandler.createMessage(systemPrompt, messages)
|
||||
await stream.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "o3-mini",
|
||||
}),
|
||||
{ path: "/models/chat/completions" },
|
||||
)
|
||||
|
||||
// Verify max_tokens is NOT included when includeMaxTokens is false
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs).not.toHaveProperty("max_completion_tokens")
|
||||
})
|
||||
|
||||
it("should NOT include max_tokens for O3 model with Azure AI Inference Service even when includeMaxTokens is true", async () => {
|
||||
const o3AzureHandler = new OpenAiHandler({
|
||||
...o3Options,
|
||||
openAiBaseUrl: "https://test.services.ai.azure.com",
|
||||
includeMaxTokens: true, // Should include max_tokens
|
||||
})
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello!",
|
||||
},
|
||||
]
|
||||
|
||||
const stream = o3AzureHandler.createMessage(systemPrompt, messages)
|
||||
await stream.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "o3-mini",
|
||||
// O3 models do not support max_tokens
|
||||
}),
|
||||
{ path: "/models/chat/completions" },
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -158,10 +158,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
...(reasoning && reasoning),
|
||||
}
|
||||
|
||||
// @TODO: Move this to the `getModelParams` function.
|
||||
if (this.options.includeMaxTokens) {
|
||||
requestOptions.max_tokens = modelInfo.maxTokens
|
||||
}
|
||||
// Add max_tokens if needed
|
||||
this.addMaxTokensIfNeeded(requestOptions, modelInfo)
|
||||
|
||||
const stream = await this.client.chat.completions.create(
|
||||
requestOptions,
|
||||
|
|
@ -222,6 +220,9 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
: [systemMessage, ...convertToOpenAiMessages(messages)],
|
||||
}
|
||||
|
||||
// Add max_tokens if needed
|
||||
this.addMaxTokensIfNeeded(requestOptions, modelInfo)
|
||||
|
||||
const response = await this.client.chat.completions.create(
|
||||
requestOptions,
|
||||
this._isAzureAiInference(modelUrl) ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {},
|
||||
|
|
@ -256,12 +257,17 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
async completePrompt(prompt: string): Promise<string> {
|
||||
try {
|
||||
const isAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl)
|
||||
const model = this.getModel()
|
||||
const modelInfo = model.info
|
||||
|
||||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
|
||||
model: this.getModel().id,
|
||||
model: model.id,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
}
|
||||
|
||||
// Add max_tokens if needed
|
||||
this.addMaxTokensIfNeeded(requestOptions, modelInfo)
|
||||
|
||||
const response = await this.client.chat.completions.create(
|
||||
requestOptions,
|
||||
isAzureAiInference ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {},
|
||||
|
|
@ -282,25 +288,34 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
): ApiStream {
|
||||
if (this.options.openAiStreamingEnabled ?? true) {
|
||||
const methodIsAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl)
|
||||
const modelInfo = this.getModel().info
|
||||
const methodIsAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl)
|
||||
|
||||
if (this.options.openAiStreamingEnabled ?? true) {
|
||||
const isGrokXAI = this._isGrokXAI(this.options.openAiBaseUrl)
|
||||
|
||||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||
model: modelId,
|
||||
messages: [
|
||||
{
|
||||
role: "developer",
|
||||
content: `Formatting re-enabled\n${systemPrompt}`,
|
||||
},
|
||||
...convertToOpenAiMessages(messages),
|
||||
],
|
||||
stream: true,
|
||||
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
|
||||
reasoning_effort: modelInfo.reasoningEffort,
|
||||
temperature: this.options.modelTemperature ?? 0,
|
||||
}
|
||||
|
||||
// O3 family models do not support the deprecated max_tokens parameter
|
||||
// but they do support max_completion_tokens (the modern OpenAI parameter)
|
||||
// This allows O3 models to limit response length when includeMaxTokens is enabled
|
||||
this.addMaxTokensIfNeeded(requestOptions, modelInfo)
|
||||
|
||||
const stream = await this.client.chat.completions.create(
|
||||
{
|
||||
model: modelId,
|
||||
messages: [
|
||||
{
|
||||
role: "developer",
|
||||
content: `Formatting re-enabled\n${systemPrompt}`,
|
||||
},
|
||||
...convertToOpenAiMessages(messages),
|
||||
],
|
||||
stream: true,
|
||||
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
|
||||
reasoning_effort: this.getModel().info.reasoningEffort,
|
||||
},
|
||||
requestOptions,
|
||||
methodIsAzureAiInference ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {},
|
||||
)
|
||||
|
||||
|
|
@ -315,9 +330,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
},
|
||||
...convertToOpenAiMessages(messages),
|
||||
],
|
||||
reasoning_effort: modelInfo.reasoningEffort,
|
||||
temperature: this.options.modelTemperature ?? 0,
|
||||
}
|
||||
|
||||
const methodIsAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl)
|
||||
// O3 family models do not support the deprecated max_tokens parameter
|
||||
// but they do support max_completion_tokens (the modern OpenAI parameter)
|
||||
// This allows O3 models to limit response length when includeMaxTokens is enabled
|
||||
this.addMaxTokensIfNeeded(requestOptions, modelInfo)
|
||||
|
||||
const response = await this.client.chat.completions.create(
|
||||
requestOptions,
|
||||
|
|
@ -369,6 +389,25 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
const urlHost = this._getUrlHost(baseUrl)
|
||||
return urlHost.endsWith(".services.ai.azure.com")
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds max_completion_tokens to the request body if needed based on provider configuration
|
||||
* Note: max_tokens is deprecated in favor of max_completion_tokens as per OpenAI documentation
|
||||
* O3 family models handle max_tokens separately in handleO3FamilyMessage
|
||||
*/
|
||||
private addMaxTokensIfNeeded(
|
||||
requestOptions:
|
||||
| OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
||||
| OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming,
|
||||
modelInfo: ModelInfo,
|
||||
): void {
|
||||
// Only add max_completion_tokens if includeMaxTokens is true
|
||||
if (this.options.includeMaxTokens === true) {
|
||||
// Use user-configured modelMaxTokens if available, otherwise fall back to model's default maxTokens
|
||||
// Using max_completion_tokens as max_tokens is deprecated
|
||||
requestOptions.max_completion_tokens = this.options.modelMaxTokens || modelInfo.maxTokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getOpenAiModels(baseUrl?: string, apiKey?: string, openAiHeaders?: Record<string, string>) {
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@
|
|||
"editor/context": [
|
||||
{
|
||||
"submenu": "roo-cline.contextMenu",
|
||||
"group": "navigation"
|
||||
"group": "1"
|
||||
}
|
||||
],
|
||||
"roo-cline.contextMenu": [
|
||||
|
|
@ -196,7 +196,7 @@
|
|||
"terminal/context": [
|
||||
{
|
||||
"submenu": "roo-cline.terminalMenu",
|
||||
"group": "navigation"
|
||||
"group": "2"
|
||||
}
|
||||
],
|
||||
"roo-cline.terminalMenu": [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,262 @@
|
|||
// npx vitest services/code-index/processors/__tests__/file-watcher.spec.ts
|
||||
|
||||
import { vi, describe, it, expect, beforeEach } from "vitest"
|
||||
import { FileWatcher } from "../file-watcher"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("../../cache-manager")
|
||||
vi.mock("../../../core/ignore/RooIgnoreController")
|
||||
vi.mock("ignore")
|
||||
|
||||
// Mock vscode module
|
||||
vi.mock("vscode", () => ({
|
||||
workspace: {
|
||||
createFileSystemWatcher: vi.fn(),
|
||||
workspaceFolders: [
|
||||
{
|
||||
uri: {
|
||||
fsPath: "/mock/workspace",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
RelativePattern: vi.fn().mockImplementation((base, pattern) => ({ base, pattern })),
|
||||
Uri: {
|
||||
file: vi.fn().mockImplementation((path) => ({ fsPath: path })),
|
||||
},
|
||||
EventEmitter: vi.fn().mockImplementation(() => ({
|
||||
event: vi.fn(),
|
||||
fire: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
})),
|
||||
ExtensionContext: vi.fn(),
|
||||
}))
|
||||
|
||||
describe("FileWatcher", () => {
|
||||
let fileWatcher: FileWatcher
|
||||
let mockWatcher: any
|
||||
let mockOnDidCreate: any
|
||||
let mockOnDidChange: any
|
||||
let mockOnDidDelete: any
|
||||
let mockContext: any
|
||||
let mockCacheManager: any
|
||||
let mockEmbedder: any
|
||||
let mockVectorStore: any
|
||||
let mockIgnoreInstance: any
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset all mocks
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Create mock event handlers
|
||||
mockOnDidCreate = vi.fn()
|
||||
mockOnDidChange = vi.fn()
|
||||
mockOnDidDelete = vi.fn()
|
||||
|
||||
// Create mock watcher
|
||||
mockWatcher = {
|
||||
onDidCreate: vi.fn().mockImplementation((handler) => {
|
||||
mockOnDidCreate = handler
|
||||
return { dispose: vi.fn() }
|
||||
}),
|
||||
onDidChange: vi.fn().mockImplementation((handler) => {
|
||||
mockOnDidChange = handler
|
||||
return { dispose: vi.fn() }
|
||||
}),
|
||||
onDidDelete: vi.fn().mockImplementation((handler) => {
|
||||
mockOnDidDelete = handler
|
||||
return { dispose: vi.fn() }
|
||||
}),
|
||||
dispose: vi.fn(),
|
||||
}
|
||||
|
||||
// Mock createFileSystemWatcher to return our mock watcher
|
||||
vi.mocked(vscode.workspace.createFileSystemWatcher).mockReturnValue(mockWatcher)
|
||||
|
||||
// Create mock dependencies
|
||||
mockContext = {
|
||||
subscriptions: [],
|
||||
}
|
||||
|
||||
mockCacheManager = {
|
||||
getHash: vi.fn(),
|
||||
updateHash: vi.fn(),
|
||||
deleteHash: vi.fn(),
|
||||
}
|
||||
|
||||
mockEmbedder = {
|
||||
createEmbeddings: vi.fn().mockResolvedValue({ embeddings: [[0.1, 0.2, 0.3]] }),
|
||||
}
|
||||
|
||||
mockVectorStore = {
|
||||
upsertPoints: vi.fn().mockResolvedValue(undefined),
|
||||
deletePointsByFilePath: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
mockIgnoreInstance = {
|
||||
ignores: vi.fn().mockReturnValue(false),
|
||||
}
|
||||
|
||||
fileWatcher = new FileWatcher(
|
||||
"/mock/workspace",
|
||||
mockContext,
|
||||
mockCacheManager,
|
||||
mockEmbedder,
|
||||
mockVectorStore,
|
||||
mockIgnoreInstance,
|
||||
)
|
||||
})
|
||||
|
||||
describe("file filtering", () => {
|
||||
it("should ignore files in hidden directories on create events", async () => {
|
||||
// Initialize the file watcher
|
||||
await fileWatcher.initialize()
|
||||
|
||||
// Spy on the vector store to see which files are actually processed
|
||||
const processedFiles: string[] = []
|
||||
mockVectorStore.upsertPoints.mockImplementation(async (points: any[]) => {
|
||||
points.forEach((point) => {
|
||||
if (point.payload?.file_path) {
|
||||
processedFiles.push(point.payload.file_path)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Simulate file creation events
|
||||
const testCases = [
|
||||
{ path: "/mock/workspace/src/file.ts", shouldProcess: true },
|
||||
{ path: "/mock/workspace/.git/config", shouldProcess: false },
|
||||
{ path: "/mock/workspace/.hidden/file.ts", shouldProcess: false },
|
||||
{ path: "/mock/workspace/src/.next/static/file.js", shouldProcess: false },
|
||||
{ path: "/mock/workspace/node_modules/package/index.js", shouldProcess: false },
|
||||
{ path: "/mock/workspace/normal/file.js", shouldProcess: true },
|
||||
]
|
||||
|
||||
// Trigger file creation events
|
||||
for (const { path } of testCases) {
|
||||
await mockOnDidCreate({ fsPath: path })
|
||||
}
|
||||
|
||||
// Wait for batch processing
|
||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
||||
|
||||
// Check that files in hidden directories were not processed
|
||||
expect(processedFiles).not.toContain("src/.next/static/file.js")
|
||||
expect(processedFiles).not.toContain(".git/config")
|
||||
expect(processedFiles).not.toContain(".hidden/file.ts")
|
||||
})
|
||||
|
||||
it("should ignore files in hidden directories on change events", async () => {
|
||||
// Initialize the file watcher
|
||||
await fileWatcher.initialize()
|
||||
|
||||
// Track which files are processed
|
||||
const processedFiles: string[] = []
|
||||
mockVectorStore.upsertPoints.mockImplementation(async (points: any[]) => {
|
||||
points.forEach((point) => {
|
||||
if (point.payload?.file_path) {
|
||||
processedFiles.push(point.payload.file_path)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Simulate file change events
|
||||
const testCases = [
|
||||
{ path: "/mock/workspace/src/file.ts", shouldProcess: true },
|
||||
{ path: "/mock/workspace/.vscode/settings.json", shouldProcess: false },
|
||||
{ path: "/mock/workspace/src/.cache/data.json", shouldProcess: false },
|
||||
{ path: "/mock/workspace/dist/bundle.js", shouldProcess: false },
|
||||
]
|
||||
|
||||
// Trigger file change events
|
||||
for (const { path } of testCases) {
|
||||
await mockOnDidChange({ fsPath: path })
|
||||
}
|
||||
|
||||
// Wait for batch processing
|
||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
||||
|
||||
// Check that files in hidden directories were not processed
|
||||
expect(processedFiles).not.toContain(".vscode/settings.json")
|
||||
expect(processedFiles).not.toContain("src/.cache/data.json")
|
||||
})
|
||||
|
||||
it("should ignore files in hidden directories on delete events", async () => {
|
||||
// Initialize the file watcher
|
||||
await fileWatcher.initialize()
|
||||
|
||||
// Track which files are deleted
|
||||
const deletedFiles: string[] = []
|
||||
mockVectorStore.deletePointsByFilePath.mockImplementation(async (filePath: string) => {
|
||||
deletedFiles.push(filePath)
|
||||
})
|
||||
|
||||
// Simulate file deletion events
|
||||
const testCases = [
|
||||
{ path: "/mock/workspace/src/file.ts", shouldProcess: true },
|
||||
{ path: "/mock/workspace/.git/objects/abc123", shouldProcess: false },
|
||||
{ path: "/mock/workspace/.DS_Store", shouldProcess: false },
|
||||
{ path: "/mock/workspace/build/.cache/temp.js", shouldProcess: false },
|
||||
]
|
||||
|
||||
// Trigger file deletion events
|
||||
for (const { path } of testCases) {
|
||||
await mockOnDidDelete({ fsPath: path })
|
||||
}
|
||||
|
||||
// Wait for batch processing
|
||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
||||
|
||||
// Check that files in hidden directories were not processed
|
||||
expect(deletedFiles).not.toContain(".git/objects/abc123")
|
||||
expect(deletedFiles).not.toContain(".DS_Store")
|
||||
expect(deletedFiles).not.toContain("build/.cache/temp.js")
|
||||
})
|
||||
|
||||
it("should handle nested hidden directories correctly", async () => {
|
||||
// Initialize the file watcher
|
||||
await fileWatcher.initialize()
|
||||
|
||||
// Track which files are processed
|
||||
const processedFiles: string[] = []
|
||||
mockVectorStore.upsertPoints.mockImplementation(async (points: any[]) => {
|
||||
points.forEach((point) => {
|
||||
if (point.payload?.file_path) {
|
||||
processedFiles.push(point.payload.file_path)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Test deeply nested hidden directories
|
||||
const testCases = [
|
||||
{ path: "/mock/workspace/src/components/Button.tsx", shouldProcess: true },
|
||||
{ path: "/mock/workspace/src/.hidden/components/Button.tsx", shouldProcess: false },
|
||||
{ path: "/mock/workspace/.hidden/src/components/Button.tsx", shouldProcess: false },
|
||||
{ path: "/mock/workspace/src/components/.hidden/Button.tsx", shouldProcess: false },
|
||||
]
|
||||
|
||||
// Trigger file creation events
|
||||
for (const { path } of testCases) {
|
||||
await mockOnDidCreate({ fsPath: path })
|
||||
}
|
||||
|
||||
// Wait for batch processing
|
||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
||||
|
||||
// Check that files in hidden directories were not processed
|
||||
expect(processedFiles).not.toContain("src/.hidden/components/Button.tsx")
|
||||
expect(processedFiles).not.toContain(".hidden/src/components/Button.tsx")
|
||||
expect(processedFiles).not.toContain("src/components/.hidden/Button.tsx")
|
||||
})
|
||||
})
|
||||
|
||||
describe("dispose", () => {
|
||||
it("should dispose of the watcher when disposed", async () => {
|
||||
await fileWatcher.initialize()
|
||||
fileWatcher.dispose()
|
||||
|
||||
expect(mockWatcher.dispose).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -209,5 +209,38 @@ describe("DirectoryScanner", () => {
|
|||
expect(mockVectorStore.deletePointsByFilePath).toHaveBeenCalledWith("old/file.js")
|
||||
expect(mockCacheManager.deleteHash).toHaveBeenCalledWith("old/file.js")
|
||||
})
|
||||
|
||||
it("should filter out files in hidden directories", async () => {
|
||||
const { listFiles } = await import("../../../glob/list-files")
|
||||
// Mock listFiles to return files including some in hidden directories
|
||||
vi.mocked(listFiles).mockResolvedValue([
|
||||
[
|
||||
"test/file1.js",
|
||||
"test/.hidden/file2.js",
|
||||
".git/config",
|
||||
"src/.next/static/file3.js",
|
||||
"normal/file4.js",
|
||||
],
|
||||
false,
|
||||
])
|
||||
|
||||
// Mock parseFile to track which files are actually processed
|
||||
const processedFiles: string[] = []
|
||||
;(mockCodeParser.parseFile as any).mockImplementation((filePath: string) => {
|
||||
processedFiles.push(filePath)
|
||||
return []
|
||||
})
|
||||
|
||||
await scanner.scanDirectory("/test")
|
||||
|
||||
// Verify that only non-hidden files were processed
|
||||
expect(processedFiles).toEqual(["test/file1.js", "normal/file4.js"])
|
||||
expect(processedFiles).not.toContain("test/.hidden/file2.js")
|
||||
expect(processedFiles).not.toContain(".git/config")
|
||||
expect(processedFiles).not.toContain("src/.next/static/file3.js")
|
||||
|
||||
// Verify the stats
|
||||
expect(mockCodeParser.parseFile).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
import { codeParser } from "./parser"
|
||||
import { CacheManager } from "../cache-manager"
|
||||
import { generateNormalizedAbsolutePath, generateRelativeFilePath } from "../shared/get-relative-path"
|
||||
import { isPathInIgnoredDirectory } from "../../glob/ignore-utils"
|
||||
|
||||
/**
|
||||
* Implementation of the file watcher interface
|
||||
|
|
@ -453,6 +454,15 @@ export class FileWatcher implements IFileWatcher {
|
|||
*/
|
||||
async processFile(filePath: string): Promise<FileProcessingResult> {
|
||||
try {
|
||||
// Check if file is in an ignored directory
|
||||
if (isPathInIgnoredDirectory(filePath)) {
|
||||
return {
|
||||
path: filePath,
|
||||
status: "skipped" as const,
|
||||
reason: "File is in an ignored directory",
|
||||
}
|
||||
}
|
||||
|
||||
// Check if file should be ignored
|
||||
const relativeFilePath = generateRelativeFilePath(filePath)
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
PARSING_CONCURRENCY,
|
||||
BATCH_PROCESSING_CONCURRENCY,
|
||||
} from "../constants"
|
||||
import { isPathInIgnoredDirectory } from "../../glob/ignore-utils"
|
||||
|
||||
export class DirectoryScanner implements IDirectoryScanner {
|
||||
constructor(
|
||||
|
|
@ -61,10 +62,16 @@ export class DirectoryScanner implements IDirectoryScanner {
|
|||
// Filter paths using .rooignore
|
||||
const allowedPaths = ignoreController.filterPaths(filePaths)
|
||||
|
||||
// Filter by supported extensions and ignore patterns
|
||||
// Filter by supported extensions, ignore patterns, and excluded directories
|
||||
const supportedPaths = allowedPaths.filter((filePath) => {
|
||||
const ext = path.extname(filePath).toLowerCase()
|
||||
const relativeFilePath = generateRelativeFilePath(filePath)
|
||||
|
||||
// Check if file is in an ignored directory using the shared helper
|
||||
if (isPathInIgnoredDirectory(filePath)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return scannerExtensions.includes(ext) && !this.ignoreInstance.ignores(relativeFilePath)
|
||||
})
|
||||
|
||||
|
|
|
|||
24
src/services/glob/constants.ts
Normal file
24
src/services/glob/constants.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* List of directories that are typically large and should be ignored
|
||||
* when showing recursive file listings or scanning for code indexing.
|
||||
* This list is shared between list-files.ts and the codebase indexing scanner
|
||||
* to ensure consistent behavior across the application.
|
||||
*/
|
||||
export const DIRS_TO_IGNORE = [
|
||||
"node_modules",
|
||||
"__pycache__",
|
||||
"env",
|
||||
"venv",
|
||||
"target/dependency",
|
||||
"build/dependencies",
|
||||
"dist",
|
||||
"out",
|
||||
"bundle",
|
||||
"vendor",
|
||||
"tmp",
|
||||
"temp",
|
||||
"deps",
|
||||
"pkg",
|
||||
"Pods",
|
||||
".*",
|
||||
]
|
||||
45
src/services/glob/ignore-utils.ts
Normal file
45
src/services/glob/ignore-utils.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { DIRS_TO_IGNORE } from "./constants"
|
||||
|
||||
/**
|
||||
* Checks if a file path should be ignored based on the DIRS_TO_IGNORE patterns.
|
||||
* This function handles special patterns like ".*" for hidden directories.
|
||||
*
|
||||
* @param filePath The file path to check
|
||||
* @returns true if the path should be ignored, false otherwise
|
||||
*/
|
||||
export function isPathInIgnoredDirectory(filePath: string): boolean {
|
||||
// Normalize path separators
|
||||
const normalizedPath = filePath.replace(/\\/g, "/")
|
||||
const pathParts = normalizedPath.split("/")
|
||||
|
||||
// Check each directory in the path against DIRS_TO_IGNORE
|
||||
for (const part of pathParts) {
|
||||
// Skip empty parts (from leading or trailing slashes)
|
||||
if (!part) continue
|
||||
|
||||
// Handle the ".*" pattern for hidden directories
|
||||
if (DIRS_TO_IGNORE.includes(".*") && part.startsWith(".") && part !== ".") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for exact matches
|
||||
if (DIRS_TO_IGNORE.includes(part)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check if path contains any ignored directory pattern
|
||||
for (const dir of DIRS_TO_IGNORE) {
|
||||
if (dir === ".*") {
|
||||
// Already handled above
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if the directory appears in the path
|
||||
if (normalizedPath.includes(`/${dir}/`)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
@ -5,29 +5,7 @@ import * as childProcess from "child_process"
|
|||
import * as vscode from "vscode"
|
||||
import { arePathsEqual } from "../../utils/path"
|
||||
import { getBinPath } from "../../services/ripgrep"
|
||||
|
||||
/**
|
||||
* List of directories that are typically large and should be ignored
|
||||
* when showing recursive file listings
|
||||
*/
|
||||
const DIRS_TO_IGNORE = [
|
||||
"node_modules",
|
||||
"__pycache__",
|
||||
"env",
|
||||
"venv",
|
||||
"target/dependency",
|
||||
"build/dependencies",
|
||||
"dist",
|
||||
"out",
|
||||
"bundle",
|
||||
"vendor",
|
||||
"tmp",
|
||||
"temp",
|
||||
"deps",
|
||||
"pkg",
|
||||
"Pods",
|
||||
".*",
|
||||
]
|
||||
import { DIRS_TO_IGNORE } from "./constants"
|
||||
|
||||
/**
|
||||
* List files in a directory, with optional recursive traversal
|
||||
|
|
|
|||
|
|
@ -164,6 +164,16 @@ export const OpenAICompatible = ({
|
|||
onChange={handleInputChange("openAiStreamingEnabled", noTransform)}>
|
||||
{t("settings:modelInfo.enableStreaming")}
|
||||
</Checkbox>
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={apiConfiguration?.includeMaxTokens ?? true}
|
||||
onChange={handleInputChange("includeMaxTokens", noTransform)}>
|
||||
{t("settings:includeMaxOutputTokens")}
|
||||
</Checkbox>
|
||||
<div className="text-sm text-vscode-descriptionForeground ml-6">
|
||||
{t("settings:includeMaxOutputTokensDescription")}
|
||||
</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
checked={apiConfiguration?.openAiUseAzure ?? false}
|
||||
onChange={handleInputChange("openAiUseAzure", noTransform)}>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,314 @@
|
|||
import React from "react"
|
||||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
import { OpenAICompatible } from "../OpenAICompatible"
|
||||
import { ProviderSettings } from "@roo-code/types"
|
||||
|
||||
// Mock the vscrui Checkbox component
|
||||
jest.mock("vscrui", () => ({
|
||||
Checkbox: ({ children, checked, onChange }: any) => (
|
||||
<label data-testid={`checkbox-${children?.toString().replace(/\s+/g, "-").toLowerCase()}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={() => onChange(!checked)} // Toggle the checked state
|
||||
data-testid={`checkbox-input-${children?.toString().replace(/\s+/g, "-").toLowerCase()}`}
|
||||
/>
|
||||
{children}
|
||||
</label>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock the VSCodeTextField and VSCodeButton components
|
||||
jest.mock("@vscode/webview-ui-toolkit/react", () => ({
|
||||
VSCodeTextField: ({
|
||||
children,
|
||||
value,
|
||||
onInput,
|
||||
placeholder,
|
||||
className,
|
||||
style,
|
||||
"data-testid": dataTestId,
|
||||
...rest
|
||||
}: any) => {
|
||||
return (
|
||||
<div
|
||||
data-testid={dataTestId ? `${dataTestId}-text-field` : "vscode-text-field"}
|
||||
className={className}
|
||||
style={style}>
|
||||
{children}
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => onInput && onInput(e)}
|
||||
placeholder={placeholder}
|
||||
data-testid={dataTestId}
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
VSCodeButton: ({ children, onClick, appearance, title }: any) => (
|
||||
<button onClick={onClick} title={title} data-testid={`vscode-button-${appearance}`}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock the translation hook
|
||||
jest.mock("@src/i18n/TranslationContext", () => ({
|
||||
useAppTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock the UI components
|
||||
jest.mock("@src/components/ui", () => ({
|
||||
Button: ({ children, onClick }: any) => <button onClick={onClick}>{children}</button>,
|
||||
}))
|
||||
|
||||
// Mock other components
|
||||
jest.mock("../../ModelPicker", () => ({
|
||||
ModelPicker: () => <div data-testid="model-picker">Model Picker</div>,
|
||||
}))
|
||||
|
||||
jest.mock("../../R1FormatSetting", () => ({
|
||||
R1FormatSetting: () => <div data-testid="r1-format-setting">R1 Format Setting</div>,
|
||||
}))
|
||||
|
||||
jest.mock("../../ThinkingBudget", () => ({
|
||||
ThinkingBudget: () => <div data-testid="thinking-budget">Thinking Budget</div>,
|
||||
}))
|
||||
|
||||
// Mock react-use
|
||||
jest.mock("react-use", () => ({
|
||||
useEvent: jest.fn(),
|
||||
}))
|
||||
|
||||
describe("OpenAICompatible Component - includeMaxTokens checkbox", () => {
|
||||
const mockSetApiConfigurationField = jest.fn()
|
||||
const mockOrganizationAllowList = {
|
||||
allowAll: true,
|
||||
providers: {},
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("Checkbox Rendering", () => {
|
||||
it("should render the includeMaxTokens checkbox", () => {
|
||||
const apiConfiguration: Partial<ProviderSettings> = {
|
||||
includeMaxTokens: true,
|
||||
}
|
||||
|
||||
render(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfiguration as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Check that the checkbox is rendered
|
||||
const checkbox = screen.getByTestId("checkbox-settings:includemaxoutputtokens")
|
||||
expect(checkbox).toBeInTheDocument()
|
||||
|
||||
// Check that the description text is rendered
|
||||
expect(screen.getByText("settings:includeMaxOutputTokensDescription")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should render the checkbox with correct translation keys", () => {
|
||||
const apiConfiguration: Partial<ProviderSettings> = {
|
||||
includeMaxTokens: true,
|
||||
}
|
||||
|
||||
render(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfiguration as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Check that the correct translation key is used for the label
|
||||
expect(screen.getByText("settings:includeMaxOutputTokens")).toBeInTheDocument()
|
||||
|
||||
// Check that the correct translation key is used for the description
|
||||
expect(screen.getByText("settings:includeMaxOutputTokensDescription")).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe("Initial State", () => {
|
||||
it("should show checkbox as checked when includeMaxTokens is true", () => {
|
||||
const apiConfiguration: Partial<ProviderSettings> = {
|
||||
includeMaxTokens: true,
|
||||
}
|
||||
|
||||
render(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfiguration as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
const checkboxInput = screen.getByTestId("checkbox-input-settings:includemaxoutputtokens")
|
||||
expect(checkboxInput).toBeChecked()
|
||||
})
|
||||
|
||||
it("should show checkbox as unchecked when includeMaxTokens is false", () => {
|
||||
const apiConfiguration: Partial<ProviderSettings> = {
|
||||
includeMaxTokens: false,
|
||||
}
|
||||
|
||||
render(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfiguration as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
const checkboxInput = screen.getByTestId("checkbox-input-settings:includemaxoutputtokens")
|
||||
expect(checkboxInput).not.toBeChecked()
|
||||
})
|
||||
|
||||
it("should default to checked when includeMaxTokens is undefined", () => {
|
||||
const apiConfiguration: Partial<ProviderSettings> = {
|
||||
// includeMaxTokens is not defined
|
||||
}
|
||||
|
||||
render(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfiguration as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
const checkboxInput = screen.getByTestId("checkbox-input-settings:includemaxoutputtokens")
|
||||
expect(checkboxInput).toBeChecked()
|
||||
})
|
||||
|
||||
it("should default to checked when includeMaxTokens is null", () => {
|
||||
const apiConfiguration: Partial<ProviderSettings> = {
|
||||
includeMaxTokens: null as any,
|
||||
}
|
||||
|
||||
render(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfiguration as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
const checkboxInput = screen.getByTestId("checkbox-input-settings:includemaxoutputtokens")
|
||||
expect(checkboxInput).toBeChecked()
|
||||
})
|
||||
})
|
||||
|
||||
describe("User Interaction", () => {
|
||||
it("should call handleInputChange with correct parameters when checkbox is clicked from checked to unchecked", () => {
|
||||
const apiConfiguration: Partial<ProviderSettings> = {
|
||||
includeMaxTokens: true,
|
||||
}
|
||||
|
||||
render(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfiguration as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
const checkboxInput = screen.getByTestId("checkbox-input-settings:includemaxoutputtokens")
|
||||
fireEvent.click(checkboxInput)
|
||||
|
||||
// Verify setApiConfigurationField was called with correct parameters
|
||||
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("includeMaxTokens", false)
|
||||
})
|
||||
|
||||
it("should call handleInputChange with correct parameters when checkbox is clicked from unchecked to checked", () => {
|
||||
const apiConfiguration: Partial<ProviderSettings> = {
|
||||
includeMaxTokens: false,
|
||||
}
|
||||
|
||||
render(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfiguration as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
const checkboxInput = screen.getByTestId("checkbox-input-settings:includemaxoutputtokens")
|
||||
fireEvent.click(checkboxInput)
|
||||
|
||||
// Verify setApiConfigurationField was called with correct parameters
|
||||
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("includeMaxTokens", true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Component Updates", () => {
|
||||
it("should update checkbox state when apiConfiguration changes", () => {
|
||||
const apiConfigurationInitial: Partial<ProviderSettings> = {
|
||||
includeMaxTokens: true,
|
||||
}
|
||||
|
||||
const { rerender } = render(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfigurationInitial as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Verify initial state
|
||||
let checkboxInput = screen.getByTestId("checkbox-input-settings:includemaxoutputtokens")
|
||||
expect(checkboxInput).toBeChecked()
|
||||
|
||||
// Update with new configuration
|
||||
const apiConfigurationUpdated: Partial<ProviderSettings> = {
|
||||
includeMaxTokens: false,
|
||||
}
|
||||
|
||||
rerender(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfigurationUpdated as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Verify updated state
|
||||
checkboxInput = screen.getByTestId("checkbox-input-settings:includemaxoutputtokens")
|
||||
expect(checkboxInput).not.toBeChecked()
|
||||
})
|
||||
})
|
||||
|
||||
describe("UI Structure", () => {
|
||||
it("should render the checkbox with description in correct structure", () => {
|
||||
const apiConfiguration: Partial<ProviderSettings> = {
|
||||
includeMaxTokens: true,
|
||||
}
|
||||
|
||||
render(
|
||||
<OpenAICompatible
|
||||
apiConfiguration={apiConfiguration as ProviderSettings}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
organizationAllowList={mockOrganizationAllowList}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Check that the checkbox and description are in a div container
|
||||
const checkbox = screen.getByTestId("checkbox-settings:includemaxoutputtokens")
|
||||
const parentDiv = checkbox.closest("div")
|
||||
expect(parentDiv).toBeInTheDocument()
|
||||
|
||||
// Check that the description has the correct styling classes
|
||||
const description = screen.getByText("settings:includeMaxOutputTokensDescription")
|
||||
expect(description).toHaveClass("text-sm", "text-vscode-descriptionForeground", "ml-6")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Instal·lat",
|
||||
"settings": "Configuració",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Incloure tokens màxims de sortida",
|
||||
"includeMaxOutputTokensDescription": "Enviar el paràmetre de tokens màxims de sortida a les sol·licituds API. Alguns proveïdors poden no admetre això."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Installiert",
|
||||
"settings": "Einstellungen",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Maximale Ausgabe-Tokens einbeziehen",
|
||||
"includeMaxOutputTokensDescription": "Sende den Parameter für maximale Ausgabe-Tokens in API-Anfragen. Einige Anbieter unterstützen dies möglicherweise nicht."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Installed",
|
||||
"settings": "Settings",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"labels": {
|
||||
"customArn": "Custom ARN",
|
||||
"useCustomArn": "Use custom ARN..."
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Include max output tokens",
|
||||
"includeMaxOutputTokensDescription": "Send max output tokens parameter in API requests. Some providers may not support this."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Instalado",
|
||||
"settings": "Configuración",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Incluir tokens máximos de salida",
|
||||
"includeMaxOutputTokensDescription": "Enviar parámetro de tokens máximos de salida en solicitudes API. Algunos proveedores pueden no soportar esto."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Installé",
|
||||
"settings": "Paramètres",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Inclure les tokens de sortie maximum",
|
||||
"includeMaxOutputTokensDescription": "Envoyer le paramètre de tokens de sortie maximum dans les requêtes API. Certains fournisseurs peuvent ne pas supporter cela."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "इंस्टॉल किया गया",
|
||||
"settings": "सेटिंग्स",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "अधिकतम आउटपुट टोकन शामिल करें",
|
||||
"includeMaxOutputTokensDescription": "API अनुरोधों में अधिकतम आउटपुट टोकन पैरामीटर भेजें। कुछ प्रदाता इसका समर्थन नहीं कर सकते हैं।"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Terinstal",
|
||||
"settings": "Pengaturan",
|
||||
|
|
|
|||
|
|
@ -658,5 +658,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Sertakan token output maksimum",
|
||||
"includeMaxOutputTokensDescription": "Kirim parameter token output maksimum dalam permintaan API. Beberapa provider mungkin tidak mendukung ini."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Installati",
|
||||
"settings": "Impostazioni",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Includi token di output massimi",
|
||||
"includeMaxOutputTokensDescription": "Invia il parametro dei token di output massimi nelle richieste API. Alcuni provider potrebbero non supportarlo."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "インストール済み",
|
||||
"settings": "設定",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "最大出力トークンを含める",
|
||||
"includeMaxOutputTokensDescription": "APIリクエストで最大出力トークンパラメータを送信します。一部のプロバイダーはこれをサポートしていない場合があります。"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "설치됨",
|
||||
"settings": "설정",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "최대 출력 토큰 포함",
|
||||
"includeMaxOutputTokensDescription": "API 요청에서 최대 출력 토큰 매개변수를 전송합니다. 일부 제공업체는 이를 지원하지 않을 수 있습니다."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Geïnstalleerd",
|
||||
"settings": "Instellingen",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Maximale output tokens opnemen",
|
||||
"includeMaxOutputTokensDescription": "Stuur maximale output tokens parameter in API-verzoeken. Sommige providers ondersteunen dit mogelijk niet."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Zainstalowane",
|
||||
"settings": "Ustawienia",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Uwzględnij maksymalne tokeny wyjściowe",
|
||||
"includeMaxOutputTokensDescription": "Wyślij parametr maksymalnych tokenów wyjściowych w żądaniach API. Niektórzy dostawcy mogą tego nie obsługiwać."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Instalado",
|
||||
"settings": "Configurações",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Incluir tokens máximos de saída",
|
||||
"includeMaxOutputTokensDescription": "Enviar parâmetro de tokens máximos de saída nas solicitações de API. Alguns provedores podem não suportar isso."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Установлено",
|
||||
"settings": "Настройки",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Включить максимальные выходные токены",
|
||||
"includeMaxOutputTokensDescription": "Отправлять параметр максимальных выходных токенов в API-запросах. Некоторые провайдеры могут не поддерживать это."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Yüklü",
|
||||
"settings": "Ayarlar",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Maksimum çıktı tokenlerini dahil et",
|
||||
"includeMaxOutputTokensDescription": "API isteklerinde maksimum çıktı token parametresini gönder. Bazı sağlayıcılar bunu desteklemeyebilir."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "Đã cài đặt",
|
||||
"settings": "Cài đặt",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "Bao gồm token đầu ra tối đa",
|
||||
"includeMaxOutputTokensDescription": "Gửi tham số token đầu ra tối đa trong các yêu cầu API. Một số nhà cung cấp có thể không hỗ trợ điều này."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "已安装",
|
||||
"settings": "设置",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "包含最大输出 Token 数",
|
||||
"includeMaxOutputTokensDescription": "在 API 请求中发送最大输出 Token 参数。某些提供商可能不支持此功能。"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Marketplace",
|
||||
"title": "Roo Marketplace",
|
||||
"tabs": {
|
||||
"installed": "已安裝",
|
||||
"settings": "設定",
|
||||
|
|
|
|||
|
|
@ -629,5 +629,7 @@
|
|||
"label": "Diagnostics filter",
|
||||
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includeMaxOutputTokens": "包含最大輸出 Token 數",
|
||||
"includeMaxOutputTokensDescription": "在 API 請求中傳送最大輸出 Token 參數。某些提供商可能不支援此功能。"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue