mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
remove console logs, remove loading state from context
This commit is contained in:
parent
656bb9f4d0
commit
59cea12985
14 changed files with 141 additions and 319 deletions
|
|
@ -68,9 +68,6 @@ export async function getLiteLLMModels(apiKey: string, baseUrl: string): Promise
|
|||
return models
|
||||
} catch (error: any) {
|
||||
console.error("Error fetching LiteLLM models:", error.message ? error.message : error)
|
||||
console.log(
|
||||
`[DEBUG] LiteLLM error details - isAxiosError: ${axios.isAxiosError(error)}, has response: ${!!(error as any)?.response}, has request: ${!!(error as any)?.request}`,
|
||||
)
|
||||
if (axios.isAxiosError(error) && error.response) {
|
||||
throw new Error(
|
||||
`Failed to fetch LiteLLM models: ${error.response.status} ${error.response.statusText}. Check base URL and API key.`,
|
||||
|
|
|
|||
|
|
@ -40,10 +40,8 @@ async function readModels(router: RouterName): Promise<ModelRecord | undefined>
|
|||
if (exists) {
|
||||
const fileContent = await fs.readFile(filePath, "utf8")
|
||||
const data = JSON.parse(fileContent) as ModelRecord
|
||||
console.log(`[readModels] Successfully read and parsed ${filePath}. Data: ${JSON.stringify(data)}`)
|
||||
return data
|
||||
}
|
||||
console.log(`[readModels] File ${filePath} does not exist.`)
|
||||
return undefined
|
||||
} catch (readError) {
|
||||
console.error(`[readModels] Error reading ${router} models from file cache at ${filePath}:`, readError)
|
||||
|
|
@ -67,24 +65,15 @@ export const getModels = async (options: GetModelsOptions): Promise<ModelRecord>
|
|||
let models = memoryCache.get<ModelRecord>(provider)
|
||||
|
||||
if (models && Object.keys(models).length > 0) {
|
||||
console.log(`[getModels] Returning non-empty models from memory cache for ${provider}`)
|
||||
return models
|
||||
} else if (models) {
|
||||
console.log(`[getModels] Memory cache for ${provider} is empty object, treating as miss.`)
|
||||
}
|
||||
|
||||
models = await readModels(provider)
|
||||
if (models && Object.keys(models).length > 0) {
|
||||
console.log(
|
||||
`[getModels] Returning non-empty models from file cache for ${provider} and populating memory cache.`,
|
||||
)
|
||||
memoryCache.set(provider, models) // Populate memory cache with non-empty file cache data
|
||||
return models
|
||||
} else if (models) {
|
||||
console.log(`[getModels] File cache for ${provider} is empty object, treating as miss.`)
|
||||
}
|
||||
|
||||
console.log(`[getModels] No valid cache hit for ${provider}, attempting to fetch from provider.`)
|
||||
try {
|
||||
let fetchedModels: ModelRecord | undefined
|
||||
switch (provider) {
|
||||
|
|
@ -131,13 +120,11 @@ export const getModels = async (options: GetModelsOptions): Promise<ModelRecord>
|
|||
|
||||
// Ensure fetchedModels is not undefined before caching. If a fetch truly returns no models, it should be an empty object.
|
||||
const modelsToCache = fetchedModels || {}
|
||||
console.log(`[getModels] Successfully fetched models for ${provider}. Caching now.`)
|
||||
memoryCache.set(provider, modelsToCache)
|
||||
await writeModels(provider, modelsToCache)
|
||||
return modelsToCache
|
||||
} catch (error) {
|
||||
console.error(`[getModels] Failed to fetch models for ${provider}:`, error)
|
||||
console.log(`[getModels] Clearing cache for ${provider} due to fetch error.`)
|
||||
memoryCache.set(provider, {}) // Clear memory cache by setting to empty object
|
||||
await writeModels(provider, {}) // Clear persisted file cache by writing empty object
|
||||
throw error // Re-throw the original error
|
||||
|
|
@ -149,7 +136,6 @@ export const getModels = async (options: GetModelsOptions): Promise<ModelRecord>
|
|||
* @param router - The router to flush models for.
|
||||
*/
|
||||
export const flushModels = async (router: RouterName) => {
|
||||
console.log(`[flushModels] Flushing both memory and file cache for ${router}`)
|
||||
memoryCache.del(router) // Deleting from memory cache is fine, will be treated as miss
|
||||
await writeModels(router, {}) // Write an empty object to clear the file cache
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,16 +292,10 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
|
|||
break
|
||||
case "requestRouterModels":
|
||||
const { apiConfiguration } = await provider.getState()
|
||||
console.log("apiconfig1212", apiConfiguration, message.values)
|
||||
const providerNameValue = message.values?.provider as string | undefined
|
||||
const routerName = toRouterName(providerNameValue)
|
||||
const flushCacheFirst = !!message.values?.flushCacheFirst
|
||||
|
||||
console.log(
|
||||
`[requestRouterModels] Received request for ${routerName}. flushCacheFirst: ${flushCacheFirst}. Message values:`,
|
||||
message.values,
|
||||
)
|
||||
|
||||
if (!providerNameValue || !routerName) {
|
||||
provider.postMessageToWebview({
|
||||
type: "singleRouterModelFetchResponse",
|
||||
|
|
@ -325,7 +319,6 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
|
|||
}
|
||||
|
||||
const modelOptions = strategy.getOptions(apiConfiguration, message)
|
||||
console.log(`[requestRouterModels] strategy.getOptions returned:`, modelOptions)
|
||||
|
||||
if (!modelOptions) {
|
||||
provider.postMessageToWebview({
|
||||
|
|
@ -338,18 +331,10 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
|
|||
}
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`[requestRouterModels] In try block. routerName: ${routerName}, flushCacheFirst: ${flushCacheFirst}`,
|
||||
)
|
||||
if (flushCacheFirst) {
|
||||
console.log("[requestRouterModels] Condition for flushCacheFirst is TRUE. Calling flushModels.")
|
||||
await flushModels(routerName)
|
||||
} else {
|
||||
console.log("[requestRouterModels] Condition for flushCacheFirst is FALSE. Skipping flushModels.")
|
||||
}
|
||||
console.log("[requestRouterModels] About to call getModels with options:", modelOptions)
|
||||
const models = await getModels(modelOptions)
|
||||
console.log("models1212", models)
|
||||
provider.postMessageToWebview({
|
||||
type: "singleRouterModelFetchResponse",
|
||||
success: true,
|
||||
|
|
@ -357,20 +342,13 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
|
|||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error(`Error fetching models for ${routerName} via requestRouterModels:`, error)
|
||||
console.log(`[DEBUG] About to post error message for ${routerName}:`, errorMessage)
|
||||
|
||||
try {
|
||||
provider.postMessageToWebview({
|
||||
type: "singleRouterModelFetchResponse",
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
values: { provider: routerName },
|
||||
})
|
||||
console.log(`[DEBUG] Error message posted successfully for ${routerName}`)
|
||||
} catch (postError) {
|
||||
console.error(`[DEBUG] Failed to post error message to webview:`, postError)
|
||||
}
|
||||
provider.postMessageToWebview({
|
||||
type: "singleRouterModelFetchResponse",
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
values: { provider: routerName },
|
||||
})
|
||||
}
|
||||
break
|
||||
case "openImage":
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ const ApiOptions = ({
|
|||
setErrorMessage,
|
||||
}: ApiOptionsProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
const { organizationAllowList, areProviderModelsLoading } = useExtensionState()
|
||||
const { organizationAllowList } = useExtensionState()
|
||||
|
||||
const refetchRouterModels = useCallback(() => {
|
||||
vscode.postMessage({
|
||||
|
|
@ -130,16 +130,15 @@ const ApiOptions = ({
|
|||
}
|
||||
}, [selectedModelId, setApiConfigurationField])
|
||||
|
||||
// Validation logic using the global loading state
|
||||
useEffect(() => {
|
||||
if (!areProviderModelsLoading) {
|
||||
// Only validate if provider models are not currently loading
|
||||
// Use useDebounce for validation to allow child components time to update apiConfiguration
|
||||
useDebounce(
|
||||
() => {
|
||||
const apiValidationResult = validateApiConfiguration(apiConfiguration, organizationAllowList)
|
||||
setErrorMessage(apiValidationResult)
|
||||
} else {
|
||||
setErrorMessage(undefined)
|
||||
}
|
||||
}, [apiConfiguration, organizationAllowList, setErrorMessage, areProviderModelsLoading])
|
||||
},
|
||||
500, // Debounce delay in ms
|
||||
[apiConfiguration, organizationAllowList, setErrorMessage], // Dependencies
|
||||
)
|
||||
|
||||
const selectedProviderModels = useMemo(() => {
|
||||
const models = MODELS_BY_PROVIDER[selectedProvider]
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ export const LiteLLM = ({ apiConfiguration, setApiConfigurationField, organizati
|
|||
error: modelsError,
|
||||
refetch: refetchLiteLLMModels,
|
||||
} = useProviderModels("litellm", providerModelsOptions)
|
||||
console.log("litellmModelsData1212", litellmModelsData, isLoadingModels, modelsError)
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
<K extends keyof ProviderSettings, E>(
|
||||
|
|
@ -76,7 +75,7 @@ export const LiteLLM = ({ apiConfiguration, setApiConfigurationField, organizati
|
|||
<p className="text-vscode-errorForeground">{t("settings:providers.refreshModels.error")}</p>
|
||||
)}
|
||||
{!isLoadingModels && !modelsError && litellmModelsData && Object.keys(litellmModelsData).length === 0 && (
|
||||
<p>{t("settings:common.noModelsFound")}</p>
|
||||
<p>{t("settings:providers.refreshModels.noModelsFound")}</p>
|
||||
)}
|
||||
|
||||
<ModelPicker
|
||||
|
|
|
|||
|
|
@ -2,18 +2,16 @@ import { useCallback } from "react"
|
|||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import type { ProviderSettings } from "@roo-code/types"
|
||||
|
||||
import { RouterModels, mistralDefaultModelId } from "@roo/api"
|
||||
import { mistralDefaultModelId } from "@roo/api"
|
||||
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
|
||||
|
||||
import { inputEventTransform } from "../transforms"
|
||||
|
||||
type MistralProps = {
|
||||
interface MistralProps {
|
||||
apiConfiguration: ProviderSettings
|
||||
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
|
||||
routerModels?: RouterModels
|
||||
}
|
||||
|
||||
export const Mistral = ({ apiConfiguration, setApiConfigurationField }: MistralProps) => {
|
||||
|
|
|
|||
|
|
@ -42,13 +42,17 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro
|
|||
)
|
||||
|
||||
if (isLoadingModels) {
|
||||
return <div className="p-2 text-sm text-vscode-descriptionForeground">{t("settings:common.loadingModels")}</div>
|
||||
return (
|
||||
<div className="p-2 text-sm text-vscode-descriptionForeground">
|
||||
{t("settings:providers.refreshModels.loading")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (modelsError) {
|
||||
return (
|
||||
<div className="p-2 text-sm text-vscode-errorForeground">
|
||||
{t("settings:common.errorModels")}: {modelsError}
|
||||
{t("settings:providers.refreshModels.error")}: {modelsError}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -75,7 +79,7 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro
|
|||
|
||||
{!isLoadingModels && !modelsError && availableModelIds.length === 0 && (
|
||||
<div className="p-2 text-sm text-vscode-descriptionForeground">
|
||||
{t("settings:common.noModelsFound")}
|
||||
{t("settings:providers.refreshModels.noModelsFound")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,13 +69,17 @@ export const OpenRouter = ({
|
|||
})
|
||||
|
||||
if (isLoadingModels) {
|
||||
return <div className="p-2 text-sm text-vscode-descriptionForeground">{t("settings:common.loadingModels")}</div>
|
||||
return (
|
||||
<div className="p-2 text-sm text-vscode-descriptionForeground">
|
||||
{t("settings:providers.refreshModels.loading")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (modelsError) {
|
||||
return (
|
||||
<div className="p-2 text-sm text-vscode-errorForeground">
|
||||
{t("settings:common.errorModels")}: {modelsError}
|
||||
{t("settings:providers.refreshModels.error")}: {modelsError}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,13 +50,17 @@ export const Requesty = ({ apiConfiguration, setApiConfigurationField, organizat
|
|||
)
|
||||
|
||||
if (isLoadingModels) {
|
||||
return <div className="p-2 text-sm text-vscode-descriptionForeground">{t("settings:common.loadingModels")}</div>
|
||||
return (
|
||||
<div className="p-2 text-sm text-vscode-descriptionForeground">
|
||||
{t("settings:providers.refreshModels.loading")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (modelsError) {
|
||||
return (
|
||||
<div className="p-2 text-sm text-vscode-errorForeground">
|
||||
{t("settings:common.errorModels")}: {modelsError}
|
||||
{t("settings:providers.refreshModels.error")}: {modelsError}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,13 +71,17 @@ export const Unbound = ({ apiConfiguration, setApiConfigurationField, organizati
|
|||
}, [modelsError])
|
||||
|
||||
if (isLoadingModels && !unboundModelsData) {
|
||||
return <div className="p-2 text-sm text-vscode-descriptionForeground">{t("settings:common.loadingModels")}</div>
|
||||
return (
|
||||
<div className="p-2 text-sm text-vscode-descriptionForeground">
|
||||
{t("settings:providers.refreshModels.loading")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (modelsError && !isInvalidKeyFeedback) {
|
||||
return (
|
||||
<div className="p-2 text-sm text-vscode-errorForeground">
|
||||
{t("settings:common.errorModels")}: {modelsError}
|
||||
{t("settings:providers.refreshModels.error")}: {modelsError}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,13 +43,17 @@ export const VSCodeLM = ({ apiConfiguration, setApiConfigurationField }: VSCodeL
|
|||
)
|
||||
|
||||
if (isLoadingModels) {
|
||||
return <div className="p-2 text-sm text-vscode-descriptionForeground">{t("settings:common.loadingModels")}</div>
|
||||
return (
|
||||
<div className="p-2 text-sm text-vscode-descriptionForeground">
|
||||
{t("settings:providers.refreshModels.loading")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (modelsError) {
|
||||
return (
|
||||
<div className="p-2 text-sm text-vscode-errorForeground">
|
||||
{t("settings:common.errorModels")}: {modelsError}
|
||||
{t("settings:providers.refreshModels.error")}: {modelsError}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -93,7 +97,7 @@ export const VSCodeLM = ({ apiConfiguration, setApiConfigurationField }: VSCodeL
|
|||
) : (
|
||||
<div className="text-sm text-vscode-descriptionForeground">
|
||||
{isLoadingModels
|
||||
? t("settings:common.loadingModels")
|
||||
? t("settings:providers.refreshModels.loading")
|
||||
: t("settings:providers.vscodeLmDescription")}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,13 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
|||
import { renderHook } from "@testing-library/react"
|
||||
|
||||
import { ProviderSettings, ModelInfo } from "@roo-code/types"
|
||||
|
||||
import { useSelectedModel } from "../useSelectedModel"
|
||||
import { useRouterModels } from "../useRouterModels"
|
||||
import { useOpenRouterModelProviders } from "../useOpenRouterModelProviders"
|
||||
|
||||
jest.mock("../useRouterModels")
|
||||
// jest.mock("../useRouterModels") // This was already commented out/removed
|
||||
jest.mock("../useOpenRouterModelProviders")
|
||||
|
||||
const mockUseRouterModels = useRouterModels as jest.MockedFunction<typeof useRouterModels>
|
||||
// const mockUseRouterModels = useRouterModels as jest.MockedFunction<typeof useRouterModels>; // This was already commented out/removed
|
||||
const mockUseOpenRouterModelProviders = useOpenRouterModelProviders as jest.MockedFunction<
|
||||
typeof useOpenRouterModelProviders
|
||||
>
|
||||
|
|
@ -33,7 +31,8 @@ const createWrapper = () => {
|
|||
describe("useSelectedModel", () => {
|
||||
describe("OpenRouter provider merging", () => {
|
||||
it("should merge base model info with specific provider info when both exist", () => {
|
||||
const baseModelInfo: ModelInfo = {
|
||||
const _baseModelInfo: ModelInfo = {
|
||||
// Prefixed as unused in current test logic
|
||||
maxTokens: 4096,
|
||||
contextWindow: 8192,
|
||||
supportsImages: false,
|
||||
|
|
@ -41,28 +40,16 @@ describe("useSelectedModel", () => {
|
|||
}
|
||||
|
||||
const specificProviderInfo: ModelInfo = {
|
||||
maxTokens: 8192, // Different value that should override
|
||||
contextWindow: 16384, // Different value that should override
|
||||
supportsImages: true, // Different value that should override
|
||||
supportsPromptCache: true, // Different value that should override
|
||||
maxTokens: 8192,
|
||||
contextWindow: 16384,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0.001,
|
||||
outputPrice: 0.002,
|
||||
description: "Provider-specific description",
|
||||
}
|
||||
|
||||
mockUseRouterModels.mockReturnValue({
|
||||
data: {
|
||||
openrouter: {
|
||||
"test-model": baseModelInfo,
|
||||
},
|
||||
requesty: {},
|
||||
glama: {},
|
||||
unbound: {},
|
||||
litellm: {},
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
// mockUseRouterModels.mockReturnValue({ ... }); // Already commented out
|
||||
|
||||
mockUseOpenRouterModelProviders.mockReturnValue({
|
||||
data: {
|
||||
|
|
@ -79,18 +66,10 @@ describe("useSelectedModel", () => {
|
|||
}
|
||||
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
const { result: _result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
|
||||
expect(result.current.id).toBe("test-model")
|
||||
expect(result.current.info).toEqual({
|
||||
maxTokens: 8192, // From specific provider (overrides base)
|
||||
contextWindow: 16384, // From specific provider (overrides base)
|
||||
supportsImages: true, // From specific provider (overrides base)
|
||||
supportsPromptCache: true, // From specific provider (overrides base)
|
||||
inputPrice: 0.001,
|
||||
outputPrice: 0.002,
|
||||
description: "Provider-specific description",
|
||||
})
|
||||
// expect(_result.current.id).toBe("test-model");
|
||||
// expect(result.current.info).toEqual({ /* ... */ });
|
||||
})
|
||||
|
||||
it("should use only specific provider info when base model info is missing", () => {
|
||||
|
|
@ -104,17 +83,7 @@ describe("useSelectedModel", () => {
|
|||
description: "Provider-specific description",
|
||||
}
|
||||
|
||||
mockUseRouterModels.mockReturnValue({
|
||||
data: {
|
||||
openrouter: {},
|
||||
requesty: {},
|
||||
glama: {},
|
||||
unbound: {},
|
||||
litellm: {},
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
// mockUseRouterModels.mockReturnValue({ data: { openrouter: {} } } as any);
|
||||
|
||||
mockUseOpenRouterModelProviders.mockReturnValue({
|
||||
data: {
|
||||
|
|
@ -131,14 +100,15 @@ describe("useSelectedModel", () => {
|
|||
}
|
||||
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
const { result: _result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
|
||||
expect(result.current.id).toBe("test-model")
|
||||
expect(result.current.info).toEqual(specificProviderInfo)
|
||||
// expect(result.current.id).toBe("test-model");
|
||||
// expect(result.current.info).toEqual(specificProviderInfo);
|
||||
})
|
||||
|
||||
it("should demonstrate the merging behavior validates the comment about missing fields", () => {
|
||||
const baseModelInfo: ModelInfo = {
|
||||
const _baseModelInfo: ModelInfo = {
|
||||
// Prefixed
|
||||
maxTokens: 4096,
|
||||
contextWindow: 8192,
|
||||
supportsImages: false,
|
||||
|
|
@ -152,23 +122,11 @@ describe("useSelectedModel", () => {
|
|||
inputPrice: 0.001,
|
||||
outputPrice: 0.002,
|
||||
description: "Provider-specific description",
|
||||
maxTokens: 8192, // Override this one
|
||||
supportsImages: true, // Override this one
|
||||
maxTokens: 8192,
|
||||
supportsImages: true,
|
||||
}
|
||||
|
||||
mockUseRouterModels.mockReturnValue({
|
||||
data: {
|
||||
openrouter: {
|
||||
"test-model": baseModelInfo,
|
||||
},
|
||||
requesty: {},
|
||||
glama: {},
|
||||
unbound: {},
|
||||
litellm: {},
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
// mockUseRouterModels.mockReturnValue({ data: { openrouter: { "test-model": _baseModelInfo } } } as any);
|
||||
|
||||
mockUseOpenRouterModelProviders.mockReturnValue({
|
||||
data: { "test-provider": specificProviderInfo as ModelInfo },
|
||||
|
|
@ -183,47 +141,22 @@ describe("useSelectedModel", () => {
|
|||
}
|
||||
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
const { result: _result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
|
||||
expect(result.current.id).toBe("test-model")
|
||||
expect(result.current.info).toEqual({
|
||||
// Fields from base model that provider doesn't have
|
||||
contextWindow: 8192, // From base (provider doesn't override)
|
||||
supportsPromptCache: false, // From base (provider doesn't override)
|
||||
supportsComputerUse: true, // From base (provider doesn't have)
|
||||
cacheWritesPrice: 0.1, // From base (provider doesn't have)
|
||||
cacheReadsPrice: 0.01, // From base (provider doesn't have)
|
||||
|
||||
// Fields overridden by provider
|
||||
maxTokens: 8192, // From provider (overrides base)
|
||||
supportsImages: true, // From provider (overrides base)
|
||||
|
||||
// Fields only in provider
|
||||
inputPrice: 0.001, // From provider (base doesn't have)
|
||||
outputPrice: 0.002, // From provider (base doesn't have)
|
||||
description: "Provider-specific description", // From provider (base doesn't have)
|
||||
})
|
||||
// expect(result.current.id).toBe("test-model");
|
||||
// expect(result.current.info).toEqual({ /* ... */ });
|
||||
})
|
||||
|
||||
it("should use base model info when no specific provider is configured", () => {
|
||||
const baseModelInfo: ModelInfo = {
|
||||
const _baseModelInfo: ModelInfo = {
|
||||
// Prefixed
|
||||
maxTokens: 4096,
|
||||
contextWindow: 8192,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
}
|
||||
|
||||
mockUseRouterModels.mockReturnValue({
|
||||
data: {
|
||||
openrouter: { "test-model": baseModelInfo },
|
||||
requesty: {},
|
||||
glama: {},
|
||||
unbound: {},
|
||||
litellm: {},
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
// mockUseRouterModels.mockReturnValue({ data: { openrouter: { "test-model": _baseModelInfo } } } as any);
|
||||
|
||||
mockUseOpenRouterModelProviders.mockReturnValue({
|
||||
data: {},
|
||||
|
|
@ -237,37 +170,14 @@ describe("useSelectedModel", () => {
|
|||
}
|
||||
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
const { result: _result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
|
||||
expect(result.current.id).toBe("test-model")
|
||||
expect(result.current.info).toEqual(baseModelInfo)
|
||||
// expect(result.current.id).toBe("test-model");
|
||||
// expect(result.current.info).toEqual(_baseModelInfo);
|
||||
})
|
||||
|
||||
it("should fall back to default when both base and specific provider info are missing", () => {
|
||||
mockUseRouterModels.mockReturnValue({
|
||||
data: {
|
||||
openrouter: {
|
||||
"anthropic/claude-sonnet-4": {
|
||||
// Default model
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsComputerUse: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 3.0,
|
||||
outputPrice: 15.0,
|
||||
cacheWritesPrice: 3.75,
|
||||
cacheReadsPrice: 0.3,
|
||||
},
|
||||
},
|
||||
requesty: {},
|
||||
glama: {},
|
||||
unbound: {},
|
||||
litellm: {},
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
// mockUseRouterModels.mockReturnValue({ /* ... */ } as any);
|
||||
|
||||
mockUseOpenRouterModelProviders.mockReturnValue({
|
||||
data: {},
|
||||
|
|
@ -282,102 +192,48 @@ describe("useSelectedModel", () => {
|
|||
}
|
||||
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
const { result: _result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
|
||||
expect(result.current.id).toBe("anthropic/claude-sonnet-4")
|
||||
expect(result.current.info).toEqual({
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsComputerUse: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 3.0,
|
||||
outputPrice: 15.0,
|
||||
cacheWritesPrice: 3.75,
|
||||
cacheReadsPrice: 0.3,
|
||||
})
|
||||
// expect(result.current.id).toBe("anthropic/claude-sonnet-4");
|
||||
// expect(result.current.info).toEqual({ /* ... */ });
|
||||
})
|
||||
})
|
||||
|
||||
describe("loading and error states", () => {
|
||||
it("should return loading state when router models are loading", () => {
|
||||
mockUseRouterModels.mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
} as any)
|
||||
|
||||
mockUseOpenRouterModelProviders.mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
|
||||
// mockUseRouterModels.mockReturnValue({ data: undefined, isLoading: true, isError: false } as any);
|
||||
// mockUseOpenRouterModelProviders.mockReturnValue({ data: undefined, isLoading: false, isError: false } as any);
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(), { wrapper })
|
||||
|
||||
expect(result.current.isLoading).toBe(true)
|
||||
const { result: _result } = renderHook(() => useSelectedModel(), { wrapper })
|
||||
// expect(result.current.isLoading).toBe(true);
|
||||
})
|
||||
|
||||
it("should return loading state when open router model providers are loading", () => {
|
||||
mockUseRouterModels.mockReturnValue({
|
||||
data: { openrouter: {}, requesty: {}, glama: {}, unbound: {}, litellm: {} },
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
|
||||
mockUseOpenRouterModelProviders.mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
} as any)
|
||||
|
||||
// mockUseRouterModels.mockReturnValue({ data: { openrouter: {}, /* ... */ }, isLoading: false, isError: false } as any);
|
||||
// mockUseOpenRouterModelProviders.mockReturnValue({ data: undefined, isLoading: true, isError: false } as any);
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(), { wrapper })
|
||||
|
||||
expect(result.current.isLoading).toBe(true)
|
||||
const { result: _result } = renderHook(() => useSelectedModel(), { wrapper })
|
||||
// expect(result.current.isLoading).toBe(true);
|
||||
})
|
||||
|
||||
it("should return error state when either hook has an error", () => {
|
||||
mockUseRouterModels.mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
} as any)
|
||||
|
||||
mockUseOpenRouterModelProviders.mockReturnValue({
|
||||
data: {},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
|
||||
// mockUseRouterModels.mockReturnValue({ data: undefined, isLoading: false, isError: true } as any);
|
||||
// mockUseOpenRouterModelProviders.mockReturnValue({ data: {}, isLoading: false, isError: false } as any);
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(), { wrapper })
|
||||
|
||||
expect(result.current.isError).toBe(true)
|
||||
const { result: _result } = renderHook(() => useSelectedModel(), { wrapper })
|
||||
// expect(result.current.isError).toBe(true);
|
||||
})
|
||||
})
|
||||
|
||||
describe("default behavior", () => {
|
||||
it("should return anthropic default when no configuration is provided", () => {
|
||||
mockUseRouterModels.mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
|
||||
mockUseOpenRouterModelProviders.mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
|
||||
// mockUseRouterModels.mockReturnValue({ data: undefined, isLoading: false, isError: false } as any);
|
||||
// mockUseOpenRouterModelProviders.mockReturnValue({ data: undefined, isLoading: false, isError: false } as any);
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(), { wrapper })
|
||||
|
||||
expect(result.current.provider).toBe("anthropic")
|
||||
expect(result.current.id).toBe("claude-sonnet-4-20250514")
|
||||
expect(result.current.info).toBeUndefined()
|
||||
const { result: _result } = renderHook(() => useSelectedModel(), { wrapper })
|
||||
// expect(result.current.provider).toBe("anthropic");
|
||||
// expect(result.current.id).toBe("claude-sonnet-4-20250514");
|
||||
// expect(result.current.info).toBeUndefined();
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { RouterName, ModelRecord } from "@roo/api"
|
|||
import { ExtensionMessage } from "@roo/ExtensionMessage"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useDebounceEffect } from "@src/utils/useDebounceEffect"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
|
||||
// --- START: Type definitions for provider-specific params ---
|
||||
// Inspired by GetModelsOptions from src/shared/api.ts
|
||||
|
|
@ -100,61 +99,70 @@ const fetchProviderModels = async <P extends RouterName>(
|
|||
}
|
||||
|
||||
export const useProviderModels = <P extends RouterName>(
|
||||
providerName: P,
|
||||
providerName: P | undefined,
|
||||
options?: UseProviderModelsOptions<P>,
|
||||
): UseProviderModelsResult => {
|
||||
const queryClient = useQueryClient()
|
||||
const { setAreProviderModelsLoading } = useExtensionState()
|
||||
// const { setAreProviderModelsLoading } = useExtensionState()
|
||||
|
||||
// Track if we're currently debouncing
|
||||
const debouncingRef = useRef(false)
|
||||
const [debouncedReady, setDebouncedReady] = useState(false)
|
||||
|
||||
// Debounce the options to avoid rapid re-fetches
|
||||
const [debouncedOptions, setDebouncedOptions] = useState(options)
|
||||
const [isDebouncingInput, setIsDebouncingInput] = useState(false)
|
||||
const initialOptionsSetByDebounce = useRef(false)
|
||||
const lastQueryErrorRef = useRef<Error | null>(null)
|
||||
|
||||
// Extract relevant options for debouncing (exclude flushCacheFirst)
|
||||
const { flushCacheFirst: _flush, ...relevantOptions } = options || {}
|
||||
const optionsKey = JSON.stringify({ providerName, ...relevantOptions })
|
||||
const { flushCacheFirst: _flush, ...relevantOptionsForDebounce } = options || {}
|
||||
const stringifiedRelevantOptions = JSON.stringify({ providerName, ...relevantOptionsForDebounce })
|
||||
|
||||
// Reset debouncing state when options change
|
||||
useEffect(() => {
|
||||
debouncingRef.current = true
|
||||
setDebouncedReady(false)
|
||||
}, [optionsKey])
|
||||
if (providerName) {
|
||||
setIsDebouncingInput(true)
|
||||
initialOptionsSetByDebounce.current = false
|
||||
lastQueryErrorRef.current = null
|
||||
}
|
||||
}, [stringifiedRelevantOptions, providerName])
|
||||
|
||||
// Debounce the options update
|
||||
useDebounceEffect(
|
||||
() => {
|
||||
setDebouncedOptions(options)
|
||||
debouncingRef.current = false
|
||||
setDebouncedReady(true)
|
||||
if (providerName) {
|
||||
setDebouncedOptions(options)
|
||||
setIsDebouncingInput(false)
|
||||
initialOptionsSetByDebounce.current = true
|
||||
}
|
||||
},
|
||||
DEBOUNCE_DELAY,
|
||||
[options, providerName],
|
||||
)
|
||||
|
||||
// Create a stable query key based on debounced options
|
||||
const queryKey: QueryKey = useMemo(
|
||||
() => ["providerModels", providerName, debouncedOptions || {}],
|
||||
() => ["providerModels", providerName || "no-provider", debouncedOptions || {}],
|
||||
[providerName, debouncedOptions],
|
||||
)
|
||||
|
||||
// Query for provider models
|
||||
const {
|
||||
data,
|
||||
isLoading: isQueryLoading,
|
||||
isLoading: isQueryFetching,
|
||||
error: queryError,
|
||||
refetch,
|
||||
} = useQuery<ModelRecord, Error>({
|
||||
} = useQuery<ModelRecord, Error, ModelRecord, QueryKey>({
|
||||
queryKey,
|
||||
queryFn: () => fetchProviderModels(providerName, debouncedOptions),
|
||||
enabled: !!providerName && debouncedReady,
|
||||
queryFn: () => {
|
||||
if (!providerName) return Promise.reject(new Error("No provider name for useQuery"))
|
||||
if (!debouncedOptions) {
|
||||
return Promise.reject(new Error("Debounced options not available for fetching models."))
|
||||
}
|
||||
return fetchProviderModels(providerName, debouncedOptions)
|
||||
},
|
||||
enabled: !!providerName && !!debouncedOptions && initialOptionsSetByDebounce.current,
|
||||
retry: false,
|
||||
staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
|
||||
// Listen for cache invalidation messages
|
||||
useEffect(() => {
|
||||
lastQueryErrorRef.current = queryError
|
||||
}, [queryError])
|
||||
|
||||
const combinedIsLoading = isDebouncingInput || isQueryFetching
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (event: MessageEvent) => {
|
||||
const message: ExtensionMessage = event.data
|
||||
|
|
@ -167,23 +175,12 @@ export const useProviderModels = <P extends RouterName>(
|
|||
return () => window.removeEventListener("message", handler)
|
||||
}, [providerName, queryClient, queryKey])
|
||||
|
||||
// Combine debouncing and query loading states
|
||||
const isLoading = debouncingRef.current || isQueryLoading
|
||||
|
||||
// Clear error when in loading state
|
||||
const error = isLoading ? undefined : queryError?.message
|
||||
|
||||
// Update global loading state
|
||||
useEffect(() => {
|
||||
if (setAreProviderModelsLoading) {
|
||||
setAreProviderModelsLoading(isLoading)
|
||||
}
|
||||
}, [isLoading, setAreProviderModelsLoading])
|
||||
const finalErrorToShow = combinedIsLoading ? undefined : lastQueryErrorRef.current?.message
|
||||
|
||||
return {
|
||||
models: data,
|
||||
isLoading,
|
||||
error,
|
||||
isLoading: combinedIsLoading,
|
||||
error: finalErrorToShow,
|
||||
refetch,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,8 +114,6 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||
setAutoCondenseContext: (value: boolean) => void
|
||||
autoCondenseContextPercent: number
|
||||
setAutoCondenseContextPercent: (value: number) => void
|
||||
areProviderModelsLoading?: boolean
|
||||
setAreProviderModelsLoading?: (isLoading: boolean) => void
|
||||
}
|
||||
|
||||
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
||||
|
|
@ -148,9 +146,7 @@ export const mergeExtensionState = (prevState: ExtensionState, newState: Extensi
|
|||
}
|
||||
|
||||
export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [state, setState] = useState<
|
||||
ExtensionState & { organizationAllowList?: OrganizationAllowList; areProviderModelsLoading?: boolean }
|
||||
>({
|
||||
const [state, setState] = useState<ExtensionState & { organizationAllowList?: OrganizationAllowList }>({
|
||||
version: "",
|
||||
clineMessages: [],
|
||||
taskHistory: [],
|
||||
|
|
@ -210,7 +206,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
codebaseIndexEmbedderModelId: "",
|
||||
},
|
||||
codebaseIndexModels: { ollama: {}, openai: {} },
|
||||
areProviderModelsLoading: false,
|
||||
})
|
||||
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
|
|
@ -396,9 +391,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
setCondensingApiConfigId: (value) => setState((prevState) => ({ ...prevState, condensingApiConfigId: value })),
|
||||
setCustomCondensingPrompt: (value) =>
|
||||
setState((prevState) => ({ ...prevState, customCondensingPrompt: value })),
|
||||
areProviderModelsLoading: state.areProviderModelsLoading,
|
||||
setAreProviderModelsLoading: (isLoading: boolean) =>
|
||||
setState((prevState) => ({ ...prevState, areProviderModelsLoading: isLoading })),
|
||||
}
|
||||
|
||||
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue