forked from RooCodeInc/Roo-Code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroo.ts
More file actions
202 lines (170 loc) · 7.37 KB
/
roo.ts
File metadata and controls
202 lines (170 loc) · 7.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import { RooModelsResponseSchema, type ModelInfo } from "@roo-code/types"
import type { ModelRecord } from "../../../shared/api"
import { parseApiPrice } from "../../../shared/cost"
import { DEFAULT_HEADERS } from "../constants"
import { resolveVersionedSettings, type VersionedSettings } from "./versionedSettings"
/**
* Fetches available models from the Datacoves Copilot Cloud provider
*
* @param baseUrl The base URL of the Datacoves Copilot Cloud provider
* @param apiKey The API key (session token) for the Datacoves Copilot Cloud provider
* @returns A promise that resolves to a record of model IDs to model info
* @throws Will throw an error if the request fails or the response is not as expected.
*/
export async function getRooModels(baseUrl: string, apiKey?: string): Promise<ModelRecord> {
// Construct the models endpoint URL early so it's available in catch block for logging
// Strip trailing /v1 or /v1/ to avoid /v1/v1/models
const normalizedBase = baseUrl.replace(/\/?v1\/?$/, "")
const url = `${normalizedBase}/v1/models`
try {
const headers: Record<string, string> = {
"Content-Type": "application/json",
...DEFAULT_HEADERS,
}
if (apiKey) {
headers["Authorization"] = `Bearer ${apiKey}`
}
// Use fetch with AbortController for better timeout handling
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 10000)
try {
const response = await fetch(url, {
headers,
signal: controller.signal,
})
if (!response.ok) {
// Log detailed error information
let errorBody = ""
try {
errorBody = await response.text()
} catch {
errorBody = "(unable to read response body)"
}
console.error(`[getRooModels] HTTP error:`, {
status: response.status,
statusText: response.statusText,
url,
body: errorBody,
})
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const data = await response.json()
const models: ModelRecord = {}
// Validate response against schema
const parsed = RooModelsResponseSchema.safeParse(data)
if (!parsed.success) {
console.error("Error fetching Datacoves Copilot Cloud models: Unexpected response format", data)
console.error("Validation errors:", parsed.error.format())
throw new Error("Failed to fetch Datacoves Copilot Cloud models: Unexpected response format.")
}
// Process the validated model data
for (const model of parsed.data.data) {
const modelId = model.id
if (!modelId) continue
// Extract model data from the validated API response
// All required fields are guaranteed by the schema
const contextWindow = model.context_window
const maxTokens = model.max_tokens
const tags = model.tags || []
const pricing = model.pricing
// Determine if the model supports images based on tags
const supportsImages = tags.includes("vision")
// Determine if the model supports reasoning effort based on tags
const supportsReasoningEffort = tags.includes("reasoning")
// Determine if the model requires reasoning effort based on tags
const requiredReasoningEffort = tags.includes("reasoning-required")
// Determine if the model supports native tool calling based on tags
const supportsNativeTools = tags.includes("tool-use")
// Determine if the model should hide vendor/company identity (stealth mode)
const isStealthModel = tags.includes("stealth")
// Parse pricing (API returns strings, convert to numbers)
const inputPrice = parseApiPrice(pricing.input)
const outputPrice = parseApiPrice(pricing.output)
const cacheReadPrice = pricing.input_cache_read ? parseApiPrice(pricing.input_cache_read) : undefined
const cacheWritePrice = pricing.input_cache_write ? parseApiPrice(pricing.input_cache_write) : undefined
// Build the base model info from API response
const baseModelInfo = {
maxTokens,
contextWindow,
supportsImages,
supportsReasoningEffort,
requiredReasoningEffort,
supportsNativeTools,
supportsPromptCache: Boolean(cacheReadPrice !== undefined),
inputPrice,
outputPrice,
cacheWritesPrice: cacheWritePrice,
cacheReadsPrice: cacheReadPrice,
description: model.description || model.name,
deprecated: model.deprecated || false,
isFree: tags.includes("free"),
defaultTemperature: model.default_temperature,
defaultToolProtocol: "native" as const,
isStealthModel: isStealthModel || undefined,
}
// Apply API-provided settings on top of base model info
// Settings allow the proxy to dynamically configure model-specific options
// like includedTools, excludedTools, reasoningEffort, etc.
//
// Two fields are used for backward compatibility:
// - `settings`: Plain values that work with all client versions (e.g., { includedTools: ['search_replace'] })
// - `versionedSettings`: Version-keyed settings (e.g., { '3.36.4': { includedTools: ['search_replace'] } })
//
// New clients check versionedSettings first - if a matching version is found, those settings are used.
// Otherwise, falls back to plain `settings`. Old clients only see `settings`.
const apiSettings = model.settings as Record<string, unknown> | undefined
const apiVersionedSettings = model.versionedSettings as VersionedSettings | undefined
// Start with base model info
let modelInfo: ModelInfo = { ...baseModelInfo }
// Try to resolve versioned settings first (finds highest version <= current plugin version)
// If versioned settings match, use them exclusively (they contain all necessary settings)
// Otherwise fall back to plain settings for backward compatibility
if (apiVersionedSettings) {
const resolvedVersionedSettings = resolveVersionedSettings<Partial<ModelInfo>>(apiVersionedSettings)
if (Object.keys(resolvedVersionedSettings).length > 0) {
// Versioned settings found - use them exclusively
modelInfo = { ...modelInfo, ...resolvedVersionedSettings }
} else if (apiSettings) {
// No matching versioned settings - fall back to plain settings
modelInfo = { ...modelInfo, ...(apiSettings as Partial<ModelInfo>) }
}
} else if (apiSettings) {
// No versioned settings at all - use plain settings
modelInfo = { ...modelInfo, ...(apiSettings as Partial<ModelInfo>) }
}
models[modelId] = modelInfo
}
return models
} finally {
clearTimeout(timeoutId)
}
} catch (error: any) {
// Enhanced error logging
console.error("[getRooModels] Error fetching Datacoves Copilot Cloud models:", {
message: error.message || String(error),
name: error.name,
stack: error.stack,
url,
hasApiKey: Boolean(apiKey),
})
// Handle abort/timeout
if (error.name === "AbortError") {
throw new Error("Failed to fetch Datacoves Copilot Cloud models: Request timed out after 10 seconds.")
}
// Handle fetch errors
if (error.message?.includes("HTTP")) {
throw new Error(
`Failed to fetch Datacoves Copilot Cloud models: ${error.message}. Check base URL and API key.`,
)
}
// Handle network errors
if (error instanceof TypeError) {
throw new Error(
"Failed to fetch Datacoves Copilot Cloud models: No response from server. Check Datacoves Copilot Cloud server status and base URL.",
)
}
throw new Error(
`Failed to fetch Datacoves Copilot Cloud models: ${error.message || "An unknown error occurred."}`,
)
}
}