mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
Add dedicated Requesty provider (#1677)
* feat: Add dedicated Requesty provider * Update ExtensionStateContext.tsx --------- Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
This commit is contained in:
parent
9bddd9a846
commit
3cacd57949
9 changed files with 188 additions and 0 deletions
5
.changeset/big-plums-wave.md
Normal file
5
.changeset/big-plums-wave.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Adding Requesty API Provider
|
||||
|
|
@ -11,6 +11,7 @@ import { GeminiHandler } from "./providers/gemini"
|
|||
import { OpenAiNativeHandler } from "./providers/openai-native"
|
||||
import { ApiStream } from "./transform/stream"
|
||||
import { DeepSeekHandler } from "./providers/deepseek"
|
||||
import { RequestyHandler } from "./providers/requesty"
|
||||
import { QwenHandler } from "./providers/qwen"
|
||||
import { MistralHandler } from "./providers/mistral"
|
||||
import { VsCodeLmHandler } from "./providers/vscode-lm"
|
||||
|
|
@ -48,6 +49,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
|||
return new OpenAiNativeHandler(options)
|
||||
case "deepseek":
|
||||
return new DeepSeekHandler(options)
|
||||
case "requesty":
|
||||
return new RequestyHandler(options)
|
||||
case "qwen":
|
||||
return new QwenHandler(options)
|
||||
case "mistral":
|
||||
|
|
|
|||
75
src/api/providers/requesty.ts
Normal file
75
src/api/providers/requesty.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
|
||||
export class RequestyHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://router.requesty.ai/v1",
|
||||
apiKey: this.options.requestyApiKey,
|
||||
})
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const modelId = this.options.requestyModelId ?? ""
|
||||
const isDeepseekReasoner = modelId.includes("deepseek-reasoner")
|
||||
|
||||
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
if (isDeepseekReasoner) {
|
||||
openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
}
|
||||
|
||||
const stream = await this.client.chat.completions.create({
|
||||
model: modelId,
|
||||
messages: openAiMessages,
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
})
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: (delta.reasoning_content as string | undefined) || "",
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
return {
|
||||
id: this.options.requestyModelId ?? "",
|
||||
info: openAiModelInfoSaneDefaults,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ type SecretKey =
|
|||
| "geminiApiKey"
|
||||
| "openAiNativeApiKey"
|
||||
| "deepSeekApiKey"
|
||||
| "requestyApiKey"
|
||||
| "qwenApiKey"
|
||||
| "mistralApiKey"
|
||||
| "authToken"
|
||||
|
|
@ -80,6 +81,7 @@ type GlobalStateKey =
|
|||
| "liteLlmBaseUrl"
|
||||
| "liteLlmModelId"
|
||||
| "qwenApiLine"
|
||||
| "requestyModelId"
|
||||
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
|
|
@ -442,6 +444,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
geminiApiKey,
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
requestyApiKey,
|
||||
requestyModelId,
|
||||
qwenApiKey,
|
||||
mistralApiKey,
|
||||
azureApiVersion,
|
||||
|
|
@ -474,6 +478,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.storeSecret("geminiApiKey", geminiApiKey)
|
||||
await this.storeSecret("openAiNativeApiKey", openAiNativeApiKey)
|
||||
await this.storeSecret("deepSeekApiKey", deepSeekApiKey)
|
||||
await this.storeSecret("requestyApiKey", requestyApiKey)
|
||||
await this.storeSecret("qwenApiKey", qwenApiKey)
|
||||
await this.storeSecret("mistralApiKey", mistralApiKey)
|
||||
await this.updateGlobalState("azureApiVersion", azureApiVersion)
|
||||
|
|
@ -483,6 +488,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.updateGlobalState("liteLlmBaseUrl", liteLlmBaseUrl)
|
||||
await this.updateGlobalState("liteLlmModelId", liteLlmModelId)
|
||||
await this.updateGlobalState("qwenApiLine", qwenApiLine)
|
||||
await this.updateGlobalState("requestyModelId", requestyModelId)
|
||||
if (this.cline) {
|
||||
this.cline.api = buildApiHandler(message.apiConfiguration)
|
||||
}
|
||||
|
|
@ -1371,6 +1377,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
geminiApiKey,
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
requestyApiKey,
|
||||
requestyModelId,
|
||||
qwenApiKey,
|
||||
mistralApiKey,
|
||||
azureApiVersion,
|
||||
|
|
@ -1414,6 +1422,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
this.getSecret("geminiApiKey") as Promise<string | undefined>,
|
||||
this.getSecret("openAiNativeApiKey") as Promise<string | undefined>,
|
||||
this.getSecret("deepSeekApiKey") as Promise<string | undefined>,
|
||||
this.getSecret("requestyApiKey") as Promise<string | undefined>,
|
||||
this.getGlobalState("requestyModelId") as Promise<string | undefined>,
|
||||
this.getSecret("qwenApiKey") as Promise<string | undefined>,
|
||||
this.getSecret("mistralApiKey") as Promise<string | undefined>,
|
||||
this.getGlobalState("azureApiVersion") as Promise<string | undefined>,
|
||||
|
|
@ -1474,6 +1484,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
geminiApiKey,
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
requestyApiKey,
|
||||
requestyModelId,
|
||||
qwenApiKey,
|
||||
qwenApiLine,
|
||||
mistralApiKey,
|
||||
|
|
@ -1571,6 +1583,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
"geminiApiKey",
|
||||
"openAiNativeApiKey",
|
||||
"deepSeekApiKey",
|
||||
"requestyApiKey",
|
||||
"qwenApiKey",
|
||||
"mistralApiKey",
|
||||
"authToken",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export type ApiProvider =
|
|||
| "lmstudio"
|
||||
| "gemini"
|
||||
| "openai-native"
|
||||
| "requesty"
|
||||
| "deepseek"
|
||||
| "qwen"
|
||||
| "mistral"
|
||||
|
|
@ -40,6 +41,8 @@ export interface ApiHandlerOptions {
|
|||
geminiApiKey?: string
|
||||
openAiNativeApiKey?: string
|
||||
deepSeekApiKey?: string
|
||||
requestyApiKey?: string
|
||||
requestyModelId?: string
|
||||
qwenApiKey?: string
|
||||
mistralApiKey?: string
|
||||
azureApiVersion?: string
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
<VSCodeOption value="bedrock">AWS Bedrock</VSCodeOption>
|
||||
<VSCodeOption value="openai-native">OpenAI</VSCodeOption>
|
||||
<VSCodeOption value="openai">OpenAI Compatible</VSCodeOption>
|
||||
<VSCodeOption value="requesty">Requesty</VSCodeOption>
|
||||
<VSCodeOption value="vscode-lm">VS Code LM API</VSCodeOption>
|
||||
<VSCodeOption value="lmstudio">LM Studio</VSCodeOption>
|
||||
<VSCodeOption value="ollama">Ollama</VSCodeOption>
|
||||
|
|
@ -673,6 +674,37 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "requesty" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.requestyApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("requestyApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>API Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.requestyModelId || ""}
|
||||
style={{ width: "100%" }}
|
||||
onInput={handleInputChange("requestyModelId")}
|
||||
placeholder={"Enter Model ID..."}>
|
||||
<span style={{ fontWeight: 500 }}>Model ID</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude
|
||||
models. Less capable models may not work as expected.)
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "vscode-lm" && (
|
||||
<div>
|
||||
<DropdownContainer zIndex={DROPDOWN_Z_INDEX - 2} className="dropdown-container">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
import { describe, it, expect, vi } from "vitest"
|
||||
import ApiOptions from "../ApiOptions"
|
||||
import { ExtensionStateContextProvider } from "../../../context/ExtensionStateContext"
|
||||
|
||||
vi.mock("../../../context/ExtensionStateContext", async (importOriginal) => {
|
||||
const actual = await importOriginal()
|
||||
return {
|
||||
...actual,
|
||||
// your mocked methods
|
||||
useExtensionState: vi.fn(() => ({
|
||||
apiConfiguration: {
|
||||
apiProvider: "requesty",
|
||||
requestyApiKey: "",
|
||||
requestyModelId: "",
|
||||
},
|
||||
setApiConfiguration: vi.fn(),
|
||||
uriScheme: "vscode",
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
describe("ApiOptions Component", () => {
|
||||
vi.clearAllMocks()
|
||||
const mockPostMessage = vi.fn()
|
||||
const mockSetApiConfiguration = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
global.vscode = { postMessage: mockPostMessage } as any
|
||||
})
|
||||
|
||||
it("renders Requesty API Key input", () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ApiOptions showModelOptions={true} />
|
||||
</ExtensionStateContextProvider>,
|
||||
)
|
||||
const apiKeyInput = screen.getByPlaceholderText("Enter API Key...")
|
||||
expect(apiKeyInput).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders Requesty Model ID input", () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ApiOptions showModelOptions={true} />
|
||||
</ExtensionStateContextProvider>,
|
||||
)
|
||||
const modelIdInput = screen.getByPlaceholderText("Enter Model ID...")
|
||||
expect(modelIdInput).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
|
@ -67,6 +67,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
|||
config.geminiApiKey,
|
||||
config.openAiNativeApiKey,
|
||||
config.deepSeekApiKey,
|
||||
config.requestyApiKey,
|
||||
config.qwenApiKey,
|
||||
config.mistralApiKey,
|
||||
config.vsCodeLmModelSelector,
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
|
|||
return "You must provide a valid base URL, API key, and model ID."
|
||||
}
|
||||
break
|
||||
case "requesty":
|
||||
if (!apiConfiguration.requestyApiKey || !apiConfiguration.requestyModelId) {
|
||||
return "You must provide a valid API key or choose a different provider."
|
||||
}
|
||||
break
|
||||
case "ollama":
|
||||
if (!apiConfiguration.ollamaModelId) {
|
||||
return "You must provide a valid model ID."
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue