Skip to content

Commit 5d40802

Browse files
committed
Remove ModelInfo objects from settings
1 parent 586e43b commit 5d40802

38 files changed

+664
-1108
lines changed

e2e/src/suite/index.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,6 @@ export async function run() {
2424
apiProvider: "openrouter" as const,
2525
openRouterApiKey: process.env.OPENROUTER_API_KEY!,
2626
openRouterModelId: "google/gemini-2.0-flash-001",
27-
openRouterModelInfo: {
28-
maxTokens: 8192,
29-
contextWindow: 1000000,
30-
supportsImages: true,
31-
supportsPromptCache: false,
32-
inputPrice: 0.1,
33-
outputPrice: 0.4,
34-
thinking: false,
35-
},
3627
})
3728

3829
await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus")

src/activate/registerCommands.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import * as vscode from "vscode"
22
import delay from "delay"
33

44
import { ClineProvider } from "../core/webview/ClineProvider"
5+
import { ContextProxy } from "../core/config/ContextProxy"
6+
7+
import { registerHumanRelayCallback, unregisterHumanRelayCallback, handleHumanRelayResponse } from "./humanRelay"
8+
import { handleNewTask } from "./handleTask"
59

610
/**
711
* Helper to get the visible ClineProvider instance or log if not found.
@@ -15,9 +19,6 @@ export function getVisibleProviderOrLog(outputChannel: vscode.OutputChannel): Cl
1519
return visibleProvider
1620
}
1721

18-
import { registerHumanRelayCallback, unregisterHumanRelayCallback, handleHumanRelayResponse } from "./humanRelay"
19-
import { handleNewTask } from "./handleTask"
20-
2122
// Store panel references in both modes
2223
let sidebarPanel: vscode.WebviewView | undefined = undefined
2324
let tabPanel: vscode.WebviewPanel | undefined = undefined
@@ -142,7 +143,8 @@ export const openClineInNewTab = async ({ context, outputChannel }: Omit<Registe
142143
// deserialize cached webview, but since we use retainContextWhenHidden, we
143144
// don't need to use that event).
144145
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
145-
const tabProvider = new ClineProvider(context, outputChannel, "editor")
146+
const contextProxy = await ContextProxy.getInstance(context)
147+
const tabProvider = new ClineProvider(context, outputChannel, "editor", contextProxy)
146148
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))
147149

148150
// Check if there are any visible text editors, otherwise open a new group

src/api/providers/__tests__/glama.test.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,6 @@ describe("GlamaHandler", () => {
207207
apiModelId: "openai/gpt-4",
208208
glamaModelId: "openai/gpt-4",
209209
glamaApiKey: "test-key",
210-
glamaModelInfo: {
211-
maxTokens: 4096,
212-
contextWindow: 8192,
213-
supportsImages: true,
214-
supportsPromptCache: false,
215-
},
216210
}
217211
const nonAnthropicHandler = new GlamaHandler(nonAnthropicOptions)
218212

src/api/providers/__tests__/openrouter.test.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ describe("OpenRouterHandler", () => {
2424
const mockOptions: ApiHandlerOptions = {
2525
openRouterApiKey: "test-key",
2626
openRouterModelId: "test-model",
27-
openRouterModelInfo: mockOpenRouterModelInfo,
2827
}
2928

3029
beforeEach(() => {
@@ -52,7 +51,6 @@ describe("OpenRouterHandler", () => {
5251

5352
expect(result).toEqual({
5453
id: mockOptions.openRouterModelId,
55-
info: mockOptions.openRouterModelInfo,
5654
maxTokens: 1000,
5755
thinking: undefined,
5856
temperature: 0,
@@ -77,11 +75,6 @@ describe("OpenRouterHandler", () => {
7775
const handler = new OpenRouterHandler({
7876
openRouterApiKey: "test-key",
7977
openRouterModelId: "test-model",
80-
openRouterModelInfo: {
81-
...mockOpenRouterModelInfo,
82-
maxTokens: 128_000,
83-
thinking: true,
84-
},
8578
modelMaxTokens: 32_768,
8679
modelMaxThinkingTokens: 16_384,
8780
})
@@ -188,10 +181,6 @@ describe("OpenRouterHandler", () => {
188181
it("adds cache control for supported models", async () => {
189182
const handler = new OpenRouterHandler({
190183
...mockOptions,
191-
openRouterModelInfo: {
192-
...mockOpenRouterModelInfo,
193-
supportsPromptCache: true,
194-
},
195184
openRouterModelId: "anthropic/claude-3.5-sonnet",
196185
})
197186

src/api/providers/__tests__/requesty.test.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,23 @@ describe("RequestyHandler", () => {
1414
let handler: RequestyHandler
1515
let mockCreate: jest.Mock
1616

17+
const modelInfo: ModelInfo = {
18+
maxTokens: 8192,
19+
contextWindow: 200_000,
20+
supportsImages: true,
21+
supportsComputerUse: true,
22+
supportsPromptCache: true,
23+
inputPrice: 3.0,
24+
outputPrice: 15.0,
25+
cacheWritesPrice: 3.75,
26+
cacheReadsPrice: 0.3,
27+
description:
28+
"Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. Claude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks. Read more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)",
29+
}
30+
1731
const defaultOptions: ApiHandlerOptions = {
1832
requestyApiKey: "test-key",
1933
requestyModelId: "test-model",
20-
requestyModelInfo: {
21-
maxTokens: 8192,
22-
contextWindow: 200_000,
23-
supportsImages: true,
24-
supportsComputerUse: true,
25-
supportsPromptCache: true,
26-
inputPrice: 3.0,
27-
outputPrice: 15.0,
28-
cacheWritesPrice: 3.75,
29-
cacheReadsPrice: 0.3,
30-
description:
31-
"Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. Claude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks. Read more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)",
32-
},
3334
openAiStreamingEnabled: true,
3435
includeMaxTokens: true, // Add this to match the implementation
3536
}
@@ -185,7 +186,7 @@ describe("RequestyHandler", () => {
185186
],
186187
stream: true,
187188
stream_options: { include_usage: true },
188-
max_tokens: defaultOptions.requestyModelInfo?.maxTokens,
189+
max_tokens: modelInfo.maxTokens,
189190
})
190191
})
191192

@@ -279,20 +280,17 @@ describe("RequestyHandler", () => {
279280
const result = handler.getModel()
280281
expect(result).toEqual({
281282
id: defaultOptions.requestyModelId,
282-
info: defaultOptions.requestyModelInfo,
283+
info: modelInfo,
283284
})
284285
})
285286

286287
it("should use sane defaults when no model info provided", () => {
287-
handler = new RequestyHandler({
288-
...defaultOptions,
289-
requestyModelInfo: undefined,
290-
})
291-
288+
handler = new RequestyHandler(defaultOptions)
292289
const result = handler.getModel()
290+
293291
expect(result).toEqual({
294292
id: defaultOptions.requestyModelId,
295-
info: defaultOptions.requestyModelInfo,
293+
info: modelInfo,
296294
})
297295
})
298296
})

src/api/providers/__tests__/unbound.test.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,6 @@ describe("UnboundHandler", () => {
7474
apiModelId: "anthropic/claude-3-5-sonnet-20241022",
7575
unboundApiKey: "test-api-key",
7676
unboundModelId: "anthropic/claude-3-5-sonnet-20241022",
77-
unboundModelInfo: {
78-
description: "Anthropic's Claude 3 Sonnet model",
79-
maxTokens: 8192,
80-
contextWindow: 200000,
81-
supportsPromptCache: true,
82-
inputPrice: 0.01,
83-
outputPrice: 0.02,
84-
},
8577
}
8678
handler = new UnboundHandler(mockOptions)
8779
mockCreate.mockClear()
@@ -220,14 +212,6 @@ describe("UnboundHandler", () => {
220212
apiModelId: "openai/gpt-4o",
221213
unboundApiKey: "test-key",
222214
unboundModelId: "openai/gpt-4o",
223-
unboundModelInfo: {
224-
description: "OpenAI's GPT-4",
225-
maxTokens: undefined,
226-
contextWindow: 128000,
227-
supportsPromptCache: true,
228-
inputPrice: 0.01,
229-
outputPrice: 0.03,
230-
},
231215
}
232216
const nonAnthropicHandler = new UnboundHandler(nonAnthropicOptions)
233217

@@ -254,13 +238,6 @@ describe("UnboundHandler", () => {
254238
apiModelId: "openai/o3-mini",
255239
unboundApiKey: "test-key",
256240
unboundModelId: "openai/o3-mini",
257-
unboundModelInfo: {
258-
maxTokens: undefined,
259-
contextWindow: 128000,
260-
supportsPromptCache: true,
261-
inputPrice: 0.01,
262-
outputPrice: 0.03,
263-
},
264241
}
265242
const openaiHandler = new UnboundHandler(openaiOptions)
266243

@@ -291,7 +268,6 @@ describe("UnboundHandler", () => {
291268
const handlerWithInvalidModel = new UnboundHandler({
292269
...mockOptions,
293270
unboundModelId: "invalid/model",
294-
unboundModelInfo: undefined,
295271
})
296272
const modelInfo = handlerWithInvalidModel.getModel()
297273
expect(modelInfo.id).toBe("anthropic/claude-3-5-sonnet-20241022") // Default model
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import * as path from "path"
2+
import fs from "fs/promises"
3+
4+
import NodeCache from "node-cache"
5+
6+
import { ContextProxy } from "../../../core/config/ContextProxy"
7+
import { getCacheDirectoryPath } from "../../../shared/storagePathManager"
8+
import { fileExistsAtPath } from "../../../utils/fs"
9+
import type { ModelInfo } from "../../../schemas"
10+
import { getOpenRouterModels } from "./openrouter"
11+
import { getRequestyModels } from "./requesty"
12+
import { getGlamaModels } from "./glama"
13+
import { getUnboundModels } from "./unbound"
14+
15+
export type RouterName = "openrouter" | "requesty" | "glama" | "unbound"
16+
17+
export type ModelRecord = Record<string, ModelInfo>
18+
19+
const memoryCache = new NodeCache({
20+
stdTTL: 5 * 60,
21+
checkperiod: 5 * 60,
22+
})
23+
24+
async function writeModels(router: RouterName, data: ModelRecord) {
25+
const filename = `${router}_models.json`
26+
const cacheDir = await getCacheDirectoryPath(ContextProxy.instance.globalStorageUri.fsPath)
27+
await fs.writeFile(path.join(cacheDir, filename), JSON.stringify(data))
28+
}
29+
30+
async function readModels(router: RouterName): Promise<ModelRecord | undefined> {
31+
const filename = `${router}_models.json`
32+
const cacheDir = await getCacheDirectoryPath(ContextProxy.instance.globalStorageUri.fsPath)
33+
const filePath = path.join(cacheDir, filename)
34+
const exists = await fileExistsAtPath(filePath)
35+
return exists ? JSON.parse(await fs.readFile(filePath, "utf8")) : undefined
36+
}
37+
38+
/**
39+
* Get models from the cache or fetch them from the provider and cache them.
40+
* There are two caches:
41+
* 1. Memory cache - This is a simple in-memory cache that is used to store models for a short period of time.
42+
* 2. File cache - This is a file-based cache that is used to store models for a longer period of time.
43+
*
44+
* @param router - The router to fetch models from.
45+
* @returns The models from the cache or the fetched models.
46+
*/
47+
export const getModels = async (router: RouterName): Promise<ModelRecord> => {
48+
let models = memoryCache.get<ModelRecord>(router)
49+
50+
if (models) {
51+
return models
52+
}
53+
54+
switch (router) {
55+
case "openrouter":
56+
models = await getOpenRouterModels()
57+
break
58+
case "requesty":
59+
models = await getRequestyModels()
60+
break
61+
case "glama":
62+
models = await getGlamaModels()
63+
break
64+
case "unbound":
65+
models = await getUnboundModels()
66+
break
67+
}
68+
69+
if (Object.keys(models).length > 0) {
70+
memoryCache.set(router, models)
71+
72+
try {
73+
await writeModels(router, models)
74+
} catch (error) {}
75+
76+
return models
77+
}
78+
79+
try {
80+
models = await readModels(router)
81+
} catch (error) {}
82+
83+
return models ?? {}
84+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import axios from "axios"
2+
3+
import { ModelInfo } from "../../../shared/api"
4+
import { parseApiPrice } from "../../../utils/cost"
5+
6+
export async function getGlamaModels(): Promise<Record<string, ModelInfo>> {
7+
const models: Record<string, ModelInfo> = {}
8+
9+
try {
10+
const response = await axios.get("https://glama.ai/api/gateway/v1/models")
11+
const rawModels = response.data
12+
13+
for (const rawModel of rawModels) {
14+
const modelInfo: ModelInfo = {
15+
maxTokens: rawModel.maxTokensOutput,
16+
contextWindow: rawModel.maxTokensInput,
17+
supportsImages: rawModel.capabilities?.includes("input:image"),
18+
supportsComputerUse: rawModel.capabilities?.includes("computer_use"),
19+
supportsPromptCache: rawModel.capabilities?.includes("caching"),
20+
inputPrice: parseApiPrice(rawModel.pricePerToken?.input),
21+
outputPrice: parseApiPrice(rawModel.pricePerToken?.output),
22+
description: undefined,
23+
cacheWritesPrice: parseApiPrice(rawModel.pricePerToken?.cacheWrite),
24+
cacheReadsPrice: parseApiPrice(rawModel.pricePerToken?.cacheRead),
25+
}
26+
27+
switch (rawModel.id) {
28+
case rawModel.id.startsWith("anthropic/"):
29+
modelInfo.maxTokens = 8192
30+
break
31+
default:
32+
break
33+
}
34+
35+
models[rawModel.id] = modelInfo
36+
}
37+
} catch (error) {
38+
console.error(`Error fetching Glama models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`)
39+
}
40+
41+
return models
42+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { getOpenRouterModels } from "./openrouter"
2+
export { getRequestyModels } from "./requesty"
3+
export { getGlamaModels } from "./glama"
4+
export { getUnboundModels } from "./unbound"

src/api/providers/fetchers/openrouter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const openRouterModelsResponseSchema = z.object({
4646

4747
type OpenRouterModelsResponse = z.infer<typeof openRouterModelsResponseSchema>
4848

49-
export async function getOpenRouterModels(options?: ApiHandlerOptions) {
49+
export async function getOpenRouterModels(options?: ApiHandlerOptions): Promise<Record<string, ModelInfo>> {
5050
const models: Record<string, ModelInfo> = {}
5151
const baseURL = options?.openRouterBaseUrl || "https://openrouter.ai/api/v1"
5252

0 commit comments

Comments
 (0)