Skip to content

Commit a00f24e

Browse files
authored
Requesty: Add model info (RooCodeInc#2190)
Adding model information to Requesty provider. - Add a new model picker component for Requesty. - Enable controlling thinking budget via a slider IMPORTANT: Model information is fetched ONLY(!) when the user chooses "requesty" as their provider to avoid any boot latency.
1 parent 238654e commit a00f24e

File tree

11 files changed

+584
-27
lines changed

11 files changed

+584
-27
lines changed

.changeset/rude-apes-behave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Add model info to the Requesty provider

src/api/providers/requesty.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Anthropic } from "@anthropic-ai/sdk"
22
import OpenAI from "openai"
3-
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api"
3+
import { ApiHandlerOptions, ModelInfo, requestyDefaultModelId, requestyDefaultModelInfo } from "../../shared/api"
44
import { ApiHandler } from "../index"
55
import { withRetry } from "../retry"
66
import { convertToOpenAiMessages } from "../transform/openai-format"
7+
import { calculateApiCostOpenAI } from "../../utils/cost"
78
import { ApiStream } from "../transform/stream"
89

910
export class RequestyHandler implements ApiHandler {
@@ -24,21 +25,34 @@ export class RequestyHandler implements ApiHandler {
2425

2526
@withRetry()
2627
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
27-
const modelId = this.options.requestyModelId ?? ""
28+
const model = this.getModel()
2829

2930
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
3031
{ role: "system", content: systemPrompt },
3132
...convertToOpenAiMessages(messages),
3233
]
3334

35+
const reasoningEffort = this.options.o3MiniReasoningEffort || "medium"
36+
const reasoning = { reasoning_effort: reasoningEffort }
37+
const reasoningArgs = model.id === "openai/o3-mini" ? reasoning : {}
38+
39+
const thinkingBudget = this.options.thinkingBudgetTokens || 0
40+
const thinking =
41+
thinkingBudget > 0
42+
? { thinking: { type: "enabled", budget_tokens: thinkingBudget } }
43+
: { thinking: { type: "disabled" } }
44+
const thinkingArgs = model.id.includes("claude-3-7-sonnet") ? thinking : {}
45+
3446
// @ts-ignore-next-line
3547
const stream = await this.client.chat.completions.create({
36-
model: modelId,
48+
model: model.id,
49+
max_tokens: model.info.maxTokens || undefined,
3750
messages: openAiMessages,
3851
temperature: 0,
3952
stream: true,
4053
stream_options: { include_usage: true },
41-
...(modelId === "openai/o3-mini" ? { reasoning_effort: this.options.o3MiniReasoningEffort || "medium" } : {}),
54+
...reasoningArgs,
55+
...thinkingArgs,
4256
})
4357

4458
for await (const chunk of stream) {
@@ -73,7 +87,7 @@ export class RequestyHandler implements ApiHandler {
7387
const outputTokens = usage.completion_tokens || 0
7488
const cacheWriteTokens = usage.prompt_tokens_details?.caching_tokens || undefined
7589
const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens || undefined
76-
const totalCost = 0 // TODO: Replace with calculateApiCostOpenAI(model.info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
90+
const totalCost = calculateApiCostOpenAI(model.info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
7791

7892
yield {
7993
type: "usage",
@@ -88,9 +102,11 @@ export class RequestyHandler implements ApiHandler {
88102
}
89103

90104
getModel(): { id: string; info: ModelInfo } {
91-
return {
92-
id: this.options.requestyModelId ?? "",
93-
info: openAiModelInfoSaneDefaults,
105+
const modelId = this.options.requestyModelId
106+
const modelInfo = this.options.requestyModelInfo
107+
if (modelId && modelInfo) {
108+
return { id: modelId, info: modelInfo }
94109
}
110+
return { id: requestyDefaultModelId, info: requestyDefaultModelInfo }
95111
}
96112
}

src/core/controller/index.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,9 @@ export class Controller {
487487
case "refreshOpenRouterModels":
488488
await this.refreshOpenRouterModels()
489489
break
490+
case "refreshRequestyModels":
491+
await this.refreshRequestyModels()
492+
break
490493
case "refreshOpenAiModels":
491494
const { apiConfiguration } = await getAllExtensionState(this.context)
492495
const openAiModels = await this.getOpenAiModels(apiConfiguration.openAiBaseUrl, apiConfiguration.openAiApiKey)
@@ -983,6 +986,7 @@ export class Controller {
983986
break
984987
case "requesty":
985988
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.requestyModelId)
989+
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.requestyModelInfo)
986990
break
987991
}
988992

@@ -1024,6 +1028,7 @@ export class Controller {
10241028
break
10251029
case "requesty":
10261030
await updateGlobalState(this.context, "requestyModelId", newModelId)
1031+
await updateGlobalState(this.context, "requestyModelInfo", newModelInfo)
10271032
break
10281033
}
10291034

@@ -1621,6 +1626,52 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
16211626
return models
16221627
}
16231628

1629+
async refreshRequestyModels() {
1630+
const parsePrice = (price: any) => {
1631+
if (price) {
1632+
return parseFloat(price) * 1_000_000
1633+
}
1634+
return undefined
1635+
}
1636+
1637+
let models: Record<string, ModelInfo> = {}
1638+
try {
1639+
const apiKey = await getSecret(this.context, "requestyApiKey")
1640+
const headers = {
1641+
Authorization: `Bearer ${apiKey}`,
1642+
}
1643+
const response = await axios.get("https://router.requesty.ai/v1/models", { headers })
1644+
if (response.data?.data) {
1645+
for (const model of response.data.data) {
1646+
const modelInfo: ModelInfo = {
1647+
maxTokens: model.max_output_tokens || undefined,
1648+
contextWindow: model.context_window,
1649+
supportsImages: model.supports_vision || undefined,
1650+
supportsComputerUse: model.supports_computer_use || undefined,
1651+
supportsPromptCache: model.supports_caching || undefined,
1652+
inputPrice: parsePrice(model.input_price),
1653+
outputPrice: parsePrice(model.output_price),
1654+
cacheWritesPrice: parsePrice(model.caching_price),
1655+
cacheReadsPrice: parsePrice(model.cached_price),
1656+
description: model.description,
1657+
}
1658+
models[model.id] = modelInfo
1659+
}
1660+
console.log("Requesty models fetched", models)
1661+
} else {
1662+
console.error("Invalid response from Requesty API")
1663+
}
1664+
} catch (error) {
1665+
console.error("Error fetching Requesty models:", error)
1666+
}
1667+
1668+
await this.postMessageToWebview({
1669+
type: "requestyModels",
1670+
requestyModels: models,
1671+
})
1672+
return models
1673+
}
1674+
16241675
// Context menus and code actions
16251676

16261677
getFileMentionFromPath(filePath: string) {

src/core/storage/state-keys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export type GlobalStateKey =
6161
| "liteLlmUsePromptCache"
6262
| "qwenApiLine"
6363
| "requestyModelId"
64+
| "requestyModelInfo"
6465
| "togetherModelId"
6566
| "mcpMarketplaceCatalog"
6667
| "telemetrySetting"

src/core/storage/state.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
8383
deepSeekApiKey,
8484
requestyApiKey,
8585
requestyModelId,
86+
requestyModelInfo,
8687
togetherApiKey,
8788
togetherModelId,
8889
qwenApiKey,
@@ -150,6 +151,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
150151
getSecret(context, "deepSeekApiKey") as Promise<string | undefined>,
151152
getSecret(context, "requestyApiKey") as Promise<string | undefined>,
152153
getGlobalState(context, "requestyModelId") as Promise<string | undefined>,
154+
getGlobalState(context, "requestyModelInfo") as Promise<ModelInfo | undefined>,
153155
getSecret(context, "togetherApiKey") as Promise<string | undefined>,
154156
getGlobalState(context, "togetherModelId") as Promise<string | undefined>,
155157
getSecret(context, "qwenApiKey") as Promise<string | undefined>,
@@ -256,6 +258,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
256258
deepSeekApiKey,
257259
requestyApiKey,
258260
requestyModelId,
261+
requestyModelInfo,
259262
togetherApiKey,
260263
togetherModelId,
261264
qwenApiKey,
@@ -329,6 +332,7 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
329332
deepSeekApiKey,
330333
requestyApiKey,
331334
requestyModelId,
335+
requestyModelInfo,
332336
togetherApiKey,
333337
togetherModelId,
334338
qwenApiKey,
@@ -397,6 +401,7 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
397401
await updateGlobalState(context, "liteLlmUsePromptCache", liteLlmUsePromptCache)
398402
await updateGlobalState(context, "qwenApiLine", qwenApiLine)
399403
await updateGlobalState(context, "requestyModelId", requestyModelId)
404+
await updateGlobalState(context, "requestyModelInfo", requestyModelInfo)
400405
await updateGlobalState(context, "togetherModelId", togetherModelId)
401406
await storeSecret(context, "asksageApiKey", asksageApiKey)
402407
await updateGlobalState(context, "asksageApiUrl", asksageApiUrl)

src/shared/ExtensionMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface ExtensionMessage {
2424
| "partialMessage"
2525
| "openRouterModels"
2626
| "openAiModels"
27+
| "requestyModels"
2728
| "mcpServers"
2829
| "relinquishControl"
2930
| "vsCodeLmModels"
@@ -69,6 +70,7 @@ export interface ExtensionMessage {
6970
partialMessage?: ClineMessage
7071
openRouterModels?: Record<string, ModelInfo>
7172
openAiModels?: string[]
73+
requestyModels?: Record<string, ModelInfo>
7274
mcpServers?: McpServer[]
7375
customToken?: string
7476
mcpMarketplaceCatalog?: McpMarketplaceCatalog

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface WebviewMessage {
3030
| "openMention"
3131
| "cancelTask"
3232
| "refreshOpenRouterModels"
33+
| "refreshRequestyModels"
3334
| "refreshOpenAiModels"
3435
| "openMcpSettings"
3536
| "restartMcpServer"

src/shared/api.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export interface ApiHandlerOptions {
6060
deepSeekApiKey?: string
6161
requestyApiKey?: string
6262
requestyModelId?: string
63+
requestyModelInfo?: ModelInfo
6364
togetherApiKey?: string
6465
togetherModelId?: string
6566
qwenApiKey?: string
@@ -1554,3 +1555,19 @@ export const sambanovaModels = {
15541555
outputPrice: 1.5,
15551556
},
15561557
} as const satisfies Record<string, ModelInfo>
1558+
1559+
// Requesty
1560+
// https://requesty.ai/models
1561+
export const requestyDefaultModelId = "anthropic/claude-3-7-sonnet-latest"
1562+
export const requestyDefaultModelInfo: ModelInfo = {
1563+
maxTokens: 8192,
1564+
contextWindow: 200_000,
1565+
supportsImages: true,
1566+
supportsComputerUse: false,
1567+
supportsPromptCache: true,
1568+
inputPrice: 3.0,
1569+
outputPrice: 15.0,
1570+
cacheWritesPrice: 3.75,
1571+
cacheReadsPrice: 0.3,
1572+
description: "Anthropic's most intelligent model. Highest level of intelligence and capability.",
1573+
}

webview-ui/src/components/settings/ApiOptions.tsx

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import {
3232
openAiNativeModels,
3333
openRouterDefaultModelId,
3434
openRouterDefaultModelInfo,
35+
requestyDefaultModelId,
36+
requestyDefaultModelInfo,
3537
mainlandQwenModels,
3638
internationalQwenModels,
3739
mainlandQwenDefaultModelId,
@@ -56,6 +58,7 @@ import { getAsVar, VSC_DESCRIPTION_FOREGROUND } from "@/utils/vscStyles"
5658
import VSCodeButtonLink from "@/components/common/VSCodeButtonLink"
5759
import OpenRouterModelPicker, { ModelDescriptionMarkdown, OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
5860
import { ClineAccountInfoCard } from "./ClineAccountInfoCard"
61+
import RequestyModelPicker from "./RequestyModelPicker"
5962

6063
interface ApiOptionsProps {
6164
showModelOptions: boolean
@@ -1050,24 +1053,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
10501053
placeholder="Enter API Key...">
10511054
<span style={{ fontWeight: 500 }}>API Key</span>
10521055
</VSCodeTextField>
1053-
<VSCodeTextField
1054-
value={apiConfiguration?.requestyModelId || ""}
1055-
style={{ width: "100%" }}
1056-
onInput={handleInputChange("requestyModelId")}
1057-
placeholder={"Enter Model ID..."}>
1058-
<span style={{ fontWeight: 500 }}>Model ID</span>
1059-
</VSCodeTextField>
1060-
<p
1061-
style={{
1062-
fontSize: "12px",
1063-
marginTop: 3,
1064-
color: "var(--vscode-descriptionForeground)",
1065-
}}>
1066-
<span style={{ color: "var(--vscode-errorForeground)" }}>
1067-
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude
1068-
models. Less capable models may not work as expected.)
1069-
</span>
1070-
</p>
1056+
{!apiConfiguration?.requestyApiKey && <a href="https://app.requesty.ai/manage-api">Get API Key</a>}
10711057
</div>
10721058
)}
10731059

@@ -1561,6 +1547,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
15611547
{(selectedProvider === "openrouter" || selectedProvider === "cline") && showModelOptions && (
15621548
<OpenRouterModelPicker isPopup={isPopup} />
15631549
)}
1550+
{selectedProvider === "requesty" && showModelOptions && <RequestyModelPicker isPopup={isPopup} />}
15641551

15651552
{modelIdErrorMessage && (
15661553
<p
@@ -1814,6 +1801,12 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
18141801
selectedModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId,
18151802
selectedModelInfo: apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo,
18161803
}
1804+
case "requesty":
1805+
return {
1806+
selectedProvider: provider,
1807+
selectedModelId: apiConfiguration?.requestyModelId || requestyDefaultModelId,
1808+
selectedModelInfo: apiConfiguration?.requestyModelInfo || requestyDefaultModelInfo,
1809+
}
18171810
case "cline":
18181811
return {
18191812
selectedProvider: provider,

0 commit comments

Comments
 (0)